From e12f0c902172d4b269928cda823e2a0aacee4c87 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Mon, 8 Sep 2025 16:58:17 +0200 Subject: [PATCH] wip: opening learner --- C/opening_learner/Makefile | 30 +++ C/opening_learner/README.md | 26 +++ C/opening_learner/check_build.sh | 28 +++ C/opening_learner/chess.c | 296 ++++++++++++++++++++++++++++++ C/opening_learner/chess.h | 56 ++++++ C/opening_learner/chess.o | Bin 0 -> 13784 bytes C/opening_learner/engine.c | 141 ++++++++++++++ C/opening_learner/engine.h | 34 ++++ C/opening_learner/engine.o | Bin 0 -> 7920 bytes C/opening_learner/gui.c | 158 ++++++++++++++++ C/opening_learner/gui.h | 36 ++++ C/opening_learner/gui.o | Bin 0 -> 7152 bytes C/opening_learner/main.c | 193 +++++++++++++++++++ C/opening_learner/main.o | Bin 0 -> 7984 bytes C/opening_learner/mistakes.c | 65 +++++++ C/opening_learner/mistakes.h | 28 +++ C/opening_learner/mistakes.o | Bin 0 -> 4016 bytes C/opening_learner/mistakes.txt | 12 ++ C/opening_learner/opening_learner | Bin 0 -> 40168 bytes C/opening_learner/run.sh | 43 +++++ 20 files changed, 1146 insertions(+) create mode 100644 C/opening_learner/Makefile create mode 100644 C/opening_learner/README.md create mode 100755 C/opening_learner/check_build.sh create mode 100644 C/opening_learner/chess.c create mode 100644 C/opening_learner/chess.h create mode 100644 C/opening_learner/chess.o create mode 100644 C/opening_learner/engine.c create mode 100644 C/opening_learner/engine.h create mode 100644 C/opening_learner/engine.o create mode 100644 C/opening_learner/gui.c create mode 100644 C/opening_learner/gui.h create mode 100644 C/opening_learner/gui.o create mode 100644 C/opening_learner/main.c create mode 100644 C/opening_learner/main.o create mode 100644 C/opening_learner/mistakes.c create mode 100644 C/opening_learner/mistakes.h create mode 100644 C/opening_learner/mistakes.o create mode 100644 C/opening_learner/mistakes.txt create mode 100755 C/opening_learner/opening_learner create mode 100755 C/opening_learner/run.sh diff --git a/C/opening_learner/Makefile b/C/opening_learner/Makefile new file mode 100644 index 0000000..aa0546e --- /dev/null +++ b/C/opening_learner/Makefile @@ -0,0 +1,30 @@ +CC := gcc +CFLAGS := -O2 -Wall -Wextra -std=c11 + +# SDL2 flags: require sdl2-config (no fallback) +SDL2CONF := $(shell command -v sdl2-config 2>/dev/null) +ifeq ($(SDL2CONF),) +$(error sdl2-config not found. Please install SDL2 development package.) +endif +SDL_CFLAGS := $(shell sdl2-config --cflags) +SDL_LDFLAGS := $(shell sdl2-config --libs) + +SRC := main.c gui.c engine.c chess.c mistakes.c +OBJ := $(SRC:.c=.o) +BIN := opening_learner + +.PHONY: all clean run + +all: $(BIN) + +$(BIN): $(OBJ) + $(CC) $(CFLAGS) -o $@ $^ $(SDL_LDFLAGS) + +%.o: %.c + $(CC) $(CFLAGS) $(SDL_CFLAGS) -c -o $@ $< + +run: $(BIN) + ./$(BIN) + +clean: + rm -f $(OBJ) $(BIN) diff --git a/C/opening_learner/README.md b/C/opening_learner/README.md new file mode 100644 index 0000000..31176c2 --- /dev/null +++ b/C/opening_learner/README.md @@ -0,0 +1,26 @@ +# Opening Learner (C + SDL2) + +- Click a piece, then click a destination to move. +- Thick board outline, board uses non-pure colors. +- Uses local Stockfish or asmfish via UCI. +- Logs mistakes to `mistakes.txt` and lets you revisit them with the `m` key. + +Build and check: + +```sh +./check_build.sh +``` + +Run: + +```sh +./opening_learner +``` + +Tips: +- ESC clears selection. +- Press `m` to cycle to a stored mistake position and practice the best move there. +- If you play Black, the board flips so Black is at the bottom. + +Notes: +- Rendering avoids TTF dependency; pieces are clear, high-contrast geometric glyphs. diff --git a/C/opening_learner/check_build.sh b/C/opening_learner/check_build.sh new file mode 100755 index 0000000..e6ab5b2 --- /dev/null +++ b/C/opening_learner/check_build.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +echo "Checking for engine (stockfish or asmfish)" +if command -v stockfish >/dev/null 2>&1; then + echo "Found stockfish" +elif command -v asmfish >/dev/null 2>&1; then + echo "Found asmfish" +else + echo "Error: Neither stockfish nor asmfish found in PATH." >&2 + exit 1 +fi + +echo "Checking for SDL2 dev (sdl2-config)" +if command -v sdl2-config >/dev/null 2>&1; then + echo "Found sdl2-config" +else + echo "Error: sdl2-config not found. Install SDL2 dev (e.g., libsdl2-dev)." >&2 + exit 2 +fi + +echo "Building project" +make clean +make -j + +echo "Build OK" diff --git a/C/opening_learner/chess.c b/C/opening_learner/chess.c new file mode 100644 index 0000000..f5e0ed2 --- /dev/null +++ b/C/opening_learner/chess.c @@ -0,0 +1,296 @@ +#include "chess.h" +#include +#include +#include +#include + +static const int knight_offsets[8] = {15, 17, -15, -17, 10, -10, 6, -6}; +static const int bishop_dirs[4] = {9, 7, -9, -7}; +static const int rook_dirs[4] = {8, -8, 1, -1}; +static const int king_dirs[8] = {8,-8,1,-1,9,7,-9,-7}; + +static inline int file_of(int sq){return sq%8;} +static inline int rank_of(int sq){return sq/8;} +static inline bool on_board(int sq){return sq>=0 && sq<64;} +static inline bool same_color(char a, char b){return (isupper(a)&&isupper(b))||(islower(a)&&islower(b));} +static inline bool is_white(char p){return isupper((unsigned char)p);} + +void chess_init_start(Position *pos){ + // a1..h1 (0..7) are white back rank; rank 8 at indexes 56..63 are black back rank + const char *start = "RNBQKBNRPPPPPPPP................................pppppppprnbqkbnr"; + for (int i=0;i<64;i++) pos->board[i] = start[i]; + pos->white_to_move = true; + pos->castle_wk = pos->castle_wq = pos->castle_bk = pos->castle_bq = true; + pos->ep_square = -1; pos->halfmove_clock = 0; pos->fullmove_number = 1; +} + +void chess_copy(Position *dst, const Position *src){ *dst = *src; } + +static bool is_empty(const Position *p, int sq){ return p->board[sq]=='.'; } + +bool chess_square_attacked(const Position *pos, int sq, bool by_white){ + // pawns + int r = rank_of(sq), f = file_of(sq); + if (by_white){ + int s1 = (r-1)*8 + (f-1); if (f>0 && r>0 && on_board(s1) && pos->board[s1]=='P') return true; + int s2 = (r-1)*8 + (f+1); if (f<7 && r>0 && on_board(s2) && pos->board[s2]=='P') return true; + } else { + int s1 = (r+1)*8 + (f-1); if (f>0 && r<7 && on_board(s1) && pos->board[s1]=='p') return true; + int s2 = (r+1)*8 + (f+1); if (f<7 && r<7 && on_board(s2) && pos->board[s2]=='p') return true; + } + // knights + for (int i=0;i<8;i++){ + int t = sq + knight_offsets[i]; + if (!on_board(t)) continue; + int df = file_of(t)-f; int dr = rank_of(t)-r; if (df< -2||df>2||dr<-2||dr>2) continue; // edge wrap guard + char pc = pos->board[t]; + if (by_white && pc=='N') return true; + if (!by_white && pc=='n') return true; + } + // bishops/queens + for (int d=0; d<4; d++){ + int off = bishop_dirs[d]; int t = sq + off; + while (on_board(t) && abs(file_of(t)-f)==abs(rank_of(t)-r)){ + char pc = pos->board[t]; + if (pc!='.') { if (by_white && (pc=='B'||pc=='Q')) return true; if (!by_white && (pc=='b'||pc=='q')) return true; break; } + t += off; + } + } + // rooks/queens + for (int d=0; d<4; d++){ + int off = rook_dirs[d]; int t = sq + off; + while (on_board(t) && (file_of(t)==f || rank_of(t)==r)){ + char pc = pos->board[t]; + if (pc!='.') { if (by_white && (pc=='R'||pc=='Q')) return true; if (!by_white && (pc=='r'||pc=='q')) return true; break; } + t += off; + } + } + // king + for (int i=0;i<8;i++){ + int t=sq+king_dirs[i]; if (!on_board(t)) continue; if (abs(file_of(t)-f)>1||abs(rank_of(t)-r)>1) continue; + char pc = pos->board[t]; if (by_white && pc=='K') return true; if (!by_white && pc=='k') return true; + } + return false; +} + +bool chess_is_in_check(const Position *pos, bool white){ + int ks = -1; char k = white? 'K' : 'k'; + for (int i=0;i<64;i++) if (pos->board[i]==k) { ks = i; break; } + if (ks==-1) return false; // malformed + return chess_square_attacked(pos, ks, !white); +} + +static void add_move_if_legal(const Position *pos, int from, int to, char promo, Move *out, size_t *n, size_t max){ + if (*n >= max) return; + Position tmp; chess_copy(&tmp, pos); + Move m = {0}; m.from=from; m.to=to; m.promo=promo; m.moved=pos->board[from]; m.captured=pos->board[to]; + int prev_ep = tmp.ep_square; m.prev_ep = prev_ep; m.prev_wk=tmp.castle_wk; m.prev_wq=tmp.castle_wq; m.prev_bk=tmp.castle_bk; m.prev_bq=tmp.castle_bq; m.prev_halfmove=tmp.halfmove_clock; + if (!chess_make_move(&tmp, &m)) return; + if (chess_is_in_check(&tmp, !tmp.white_to_move)) return; // after make, side switched + out[(*n)++] = m; // store pseudo move with added flags from make_move +} + +size_t chess_generate_legal_moves(const Position *pos, Move *out, size_t max){ + size_t n=0; + bool white = pos->white_to_move; + for (int sq=0; sq<64; sq++){ + char p = pos->board[sq]; if (p=='.') continue; if (white != is_white(p)) continue; + int f=file_of(sq), r=rank_of(sq); + switch (tolower(p)){ + case 'p': { + int dir = white? 8 : -8; int start_rank = white? 1 : 6; int prom_rank = white? 6 : 1; + int one = sq + dir; + if (on_board(one) && is_empty(pos, one)){ + if (r==prom_rank){ + const char *pr = white? "QRBN" : "qrbn"; + for (int i=0;i<4;i++) add_move_if_legal(pos, sq, one, pr[i], out, &n, max); + } else add_move_if_legal(pos, sq, one, 0, out, &n, max); + // two + int two = sq + 2*dir; if (r==start_rank && is_empty(pos, two)) add_move_if_legal(pos, sq, two, 0, out, &n, max); + } + // captures + int caps[2] = { dir+1, dir-1 }; + for (int i=0;i<2;i++){ + int t = sq + caps[i]; if (!on_board(t)) continue; if (abs(file_of(t)-f)!=1) continue; + if (!is_empty(pos, t) && !same_color(pos->board[sq], pos->board[t])){ + if (r==prom_rank){ const char *pr = white? "QRBN" : "qrbn"; for (int j=0;j<4;j++) add_move_if_legal(pos, sq, t, pr[j], out, &n, max); } + else add_move_if_legal(pos, sq, t, 0, out, &n, max); + } + } + // en passant + if (pos->ep_square!=-1){ + int ep = pos->ep_square; if (abs(file_of(ep)-f)==1 && (ep == sq+dir+1 || ep==sq+dir-1)) add_move_if_legal(pos, sq, ep, 0, out, &n, max); + } + } break; + case 'n': { + for (int i=0;i<8;i++){ + int t = sq + knight_offsets[i]; if (!on_board(t)) continue; if (abs(file_of(t)-f)>2 || abs(rank_of(t)-r)>2) continue; if (!is_empty(pos, t) && same_color(p, pos->board[t])) continue; add_move_if_legal(pos, sq, t, 0, out, &n, max); + } + } break; + case 'b': { + for (int d=0; d<4; d++){ + int off = bishop_dirs[d]; int t = sq + off; while (on_board(t) && abs(file_of(t)-f)==abs(rank_of(t)-r)){ + if (!is_empty(pos, t)){ if (!same_color(p, pos->board[t])) add_move_if_legal(pos, sq, t, 0, out, &n, max); break; } + add_move_if_legal(pos, sq, t, 0, out, &n, max); t += off; + } + } + } break; + case 'r': { + for (int d=0; d<4; d++){ + int off = rook_dirs[d]; int t = sq + off; while (on_board(t) && (file_of(t)==f || rank_of(t)==r)){ + if (!is_empty(pos, t)){ if (!same_color(p, pos->board[t])) add_move_if_legal(pos, sq, t, 0, out, &n, max); break; } + add_move_if_legal(pos, sq, t, 0, out, &n, max); t += off; + } + } + } break; + case 'q': { + for (int d=0; d<4; d++){ + int off = bishop_dirs[d]; int t = sq + off; while (on_board(t) && abs(file_of(t)-f)==abs(rank_of(t)-r)){ + if (!is_empty(pos, t)){ if (!same_color(p, pos->board[t])) add_move_if_legal(pos, sq, t, 0, out, &n, max); break; } + add_move_if_legal(pos, sq, t, 0, out, &n, max); t += off; + } + } + for (int d=0; d<4; d++){ + int off = rook_dirs[d]; int t = sq + off; while (on_board(t) && (file_of(t)==f || rank_of(t)==r)){ + if (!is_empty(pos, t)){ if (!same_color(p, pos->board[t])) add_move_if_legal(pos, sq, t, 0, out, &n, max); break; } + add_move_if_legal(pos, sq, t, 0, out, &n, max); t += off; + } + } + } break; + case 'k': { + for (int i=0;i<8;i++){ + int t = sq + king_dirs[i]; if (!on_board(t)) continue; if (abs(file_of(t)-f)>1||abs(rank_of(t)-r)>1) continue; if (!is_empty(pos, t) && same_color(p, pos->board[t])) continue; add_move_if_legal(pos, sq, t, 0, out, &n, max); + } + // castling + if (white){ + if (pos->castle_wk && pos->board[5]=='.' && pos->board[6]=='.' && !chess_square_attacked(pos,4,false) && !chess_square_attacked(pos,5,false) && !chess_square_attacked(pos,6,false)) add_move_if_legal(pos, 4, 6, 0, out, &n, max); + if (pos->castle_wq && pos->board[3]=='.' && pos->board[2]=='.' && pos->board[1]=='.' && !chess_square_attacked(pos,4,false) && !chess_square_attacked(pos,3,false) && !chess_square_attacked(pos,2,false)) add_move_if_legal(pos, 4, 2, 0, out, &n, max); + } else { + if (pos->castle_bk && pos->board[61]=='.' && pos->board[62]=='.' && !chess_square_attacked(pos,60,true) && !chess_square_attacked(pos,61,true) && !chess_square_attacked(pos,62,true)) add_move_if_legal(pos, 60, 62, 0, out, &n, max); + if (pos->castle_bq && pos->board[59]=='.' && pos->board[58]=='.' && pos->board[57]=='.' && !chess_square_attacked(pos,60,true) && !chess_square_attacked(pos,59,true) && !chess_square_attacked(pos,58,true)) add_move_if_legal(pos, 60, 58, 0, out, &n, max); + } + } break; + } + } + return n; +} + +bool chess_make_move(Position *pos, Move *m){ + m->is_castle=false; m->is_enpassant=false; + char p = pos->board[m->from]; char tgt = pos->board[m->to]; + if (p=='.') return false; + // handle special: en passant capture + if (tolower(p)=='p' && m->to == pos->ep_square && file_of(m->to)!=file_of(m->from) && tgt=='.'){ + m->is_enpassant = true; + int cap_sq = pos->white_to_move ? (m->to - 8) : (m->to + 8); + m->captured = pos->board[cap_sq]; pos->board[cap_sq]='.'; + } + + // move piece + pos->board[m->to] = p; + pos->board[m->from] = '.'; + + // promotion + if (tolower(p)=='p' && m->promo){ pos->board[m->to] = m->promo; } + + // castling rook move + if (tolower(p)=='k'){ + int from = m->from, to = m->to; + if (from==4 && to==6){ pos->board[5]='R'; pos->board[7]='.'; m->is_castle=true; } + else if (from==4 && to==2){ pos->board[3]='R'; pos->board[0]='.'; m->is_castle=true; } + else if (from==60 && to==62){ pos->board[61]='r'; pos->board[63]='.'; m->is_castle=true; } + else if (from==60 && to==58){ pos->board[59]='r'; pos->board[56]='.'; m->is_castle=true; } + } + + // update castling rights + if (m->from==0||m->to==0) pos->castle_wq=false; + if (m->from==7||m->to==7) pos->castle_wk=false; + if (m->from==56||m->to==56) pos->castle_bq=false; + if (m->from==63||m->to==63) pos->castle_bk=false; + if (tolower(p)=='k'){ if (is_white(p)) { pos->castle_wk=pos->castle_wq=false;} else {pos->castle_bk=pos->castle_bq=false;} } + + // update ep square + pos->ep_square = -1; + if (tolower(p)=='p'){ + int df = rank_of(m->to) - rank_of(m->from); + if (df==2 || df==-2){ pos->ep_square = (m->from + m->to)/2; } + } + + // halfmove clock + if (tolower(p)=='p' || tgt!='.') pos->halfmove_clock = 0; else pos->halfmove_clock++; + + // side to move + pos->white_to_move = !pos->white_to_move; if (pos->white_to_move) pos->fullmove_number++; + + return true; +} + +void chess_unmake_move(Position *pos, const Move *m){ + pos->white_to_move = !pos->white_to_move; if (!pos->white_to_move) pos->fullmove_number--; + // restore halfmove/flags + pos->ep_square = m->prev_ep; + pos->castle_wk = m->prev_wk; pos->castle_wq = m->prev_wq; pos->castle_bk = m->prev_bk; pos->castle_bq = m->prev_bq; + pos->halfmove_clock = m->prev_halfmove; + + char p = m->moved; + // undo promotions + if (tolower(p)=='p' && m->promo){ p = is_white(p)? 'P':'p'; } + + pos->board[m->from] = p; + pos->board[m->to] = m->captured? m->captured : '.'; + if (m->is_enpassant){ int cap_sq = pos->white_to_move ? (m->to - 8) : (m->to + 8); pos->board[m->to]='.'; pos->board[cap_sq]= m->captured; } + if (m->is_castle){ + if (m->from==4 && m->to==6){ pos->board[7]='R'; pos->board[5]='.'; } + else if (m->from==4 && m->to==2){ pos->board[0]='R'; pos->board[3]='.'; } + else if (m->from==60 && m->to==62){ pos->board[63]='r'; pos->board[61]='.'; } + else if (m->from==60 && m->to==58){ pos->board[56]='r'; pos->board[59]='.'; } + } +} + +void sq_to_coord(int sq, int *file, int *rank){ if (file) *file=file_of(sq); if (rank) *rank=rank_of(sq); } +int coord_to_sq(int file, int rank){ return rank*8 + file; } + +void move_to_uci(const Move *m, char buf[8]){ + int f1=file_of(m->from), r1=rank_of(m->from), f2=file_of(m->to), r2=rank_of(m->to); + buf[0]='a'+f1; buf[1]='1'+r1; buf[2]='a'+f2; buf[3]='1'+r2; int i=4; + if (m->promo){ buf[i++]=tolower(m->promo); } + buf[i]='\0'; +} + +bool parse_uci_move(const char *s, const Position *pos, Move *out){ + if (!s || strlen(s)<4) return false; + int f1=s[0]-'a', r1=s[1]-'1', f2=s[2]-'a', r2=s[3]-'1'; if (f1<0||f1>7||f2<0||f2>7||r1<0||r1>7||r2<0||r2>7) return false; + int from = r1*8+f1, to=r2*8+f2; char promo = s[4]? s[4]:0; if (promo) promo = pos->white_to_move? toupper((unsigned char)promo):tolower((unsigned char)promo); + Move list[MAX_MOVES]; size_t n = chess_generate_legal_moves(pos, list, MAX_MOVES); + for (size_t i=0;ipromo = promo? promo : list[i].promo; return true; } } + return false; +} + +bool chess_to_fen(const Position *pos, char *out, size_t outsz){ + char buf[256]; int idx=0; + for (int r=7;r>=0;r--){ + int empty=0; + for (int f=0;f<8;f++){ + char p = pos->board[r*8+f]; + if (p=='.') empty++; else { if (empty){ buf[idx++]= '0'+empty; empty=0; } buf[idx++]=p; } + } + if (empty) buf[idx++]= '0'+empty; + if (r) buf[idx++]='/'; + } + buf[idx++]=' '; + buf[idx++]= pos->white_to_move ? 'w':'b'; + buf[idx++]=' '; + int start=idx; + if (pos->castle_wk) buf[idx++]='K'; + if (pos->castle_wq) buf[idx++]='Q'; + if (pos->castle_bk) buf[idx++]='k'; + if (pos->castle_bq) buf[idx++]='q'; + if (idx==start) buf[idx++]='-'; + buf[idx++]=' '; + if (pos->ep_square==-1){ buf[idx++]='-'; } + else { int f=file_of(pos->ep_square), r=rank_of(pos->ep_square); buf[idx++]='a'+f; buf[idx++]='1'+r; } + idx += snprintf(buf+idx, sizeof(buf)-idx, " %d %d", pos->halfmove_clock, pos->fullmove_number); + buf[idx]='\0'; + snprintf(out, outsz, "%s", buf); + return true; +} diff --git a/C/opening_learner/chess.h b/C/opening_learner/chess.h new file mode 100644 index 0000000..9c2c9f6 --- /dev/null +++ b/C/opening_learner/chess.h @@ -0,0 +1,56 @@ +#ifndef CHESS_H +#define CHESS_H + +#include +#include + +// Board is 64 chars, a1=0, b1=1, ..., h8=63 +// Pieces: 'P','N','B','R','Q','K' for white, lowercase for black, '.' empty + +typedef struct { + char board[64]; + bool white_to_move; + bool castle_wk, castle_wq, castle_bk, castle_bq; + int ep_square; // -1 if none + int halfmove_clock; + int fullmove_number; +} Position; + +typedef struct { + int from, to; + char promo; // 0 or 'q','r','b','n' (lowercase for black) + char captured; // piece captured or 0 + char moved; // piece moved + bool is_castle; + bool is_enpassant; + int prev_ep; + bool prev_wk, prev_wq, prev_bk, prev_bq; + int prev_halfmove; +} Move; + +void chess_init_start(Position *pos); +void chess_copy(Position *dst, const Position *src); + +// Move gen and make/unmake +size_t chess_generate_legal_moves(const Position *pos, Move *out, size_t max); +bool chess_make_move(Position *pos, Move *m); +void chess_unmake_move(Position *pos, const Move *m); + +// Utility +bool chess_is_in_check(const Position *pos, bool white); +bool chess_square_attacked(const Position *pos, int sq, bool by_white); + +// Conversions +void sq_to_coord(int sq, int *file, int *rank); +int coord_to_sq(int file, int rank); + +// UCI strings like e2e4, with optional promotion char +void move_to_uci(const Move *m, char buf[8]); +bool parse_uci_move(const char *s, const Position *pos, Move *out); + +// FEN +bool chess_to_fen(const Position *pos, char *out, size_t outsz); + +#define MAX_MOVES 256 + +#endif diff --git a/C/opening_learner/chess.o b/C/opening_learner/chess.o new file mode 100644 index 0000000000000000000000000000000000000000..ee51fb838b1b6ceba9e8e0b51b37f8c6a0eda08c GIT binary patch literal 13784 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 literal 0 HcmV?d00001 diff --git a/C/opening_learner/engine.c b/C/opening_learner/engine.c new file mode 100644 index 0000000..c4ce1b0 --- /dev/null +++ b/C/opening_learner/engine.c @@ -0,0 +1,141 @@ +#include "engine.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static bool spawn_process(const char *path, Engine *e){ + int inpipe[2], outpipe[2]; + if (pipe(inpipe)<0 || pipe(outpipe)<0) return false; + pid_t pid = fork(); + if (pid < 0) return false; + if (pid == 0){ + dup2(inpipe[0], STDIN_FILENO); // child stdin from inpipe[0] + dup2(outpipe[1], STDOUT_FILENO); // child stdout to outpipe[1] + dup2(outpipe[1], STDERR_FILENO); + close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); + execlp(path, path, (char*)NULL); + _exit(127); + } + // parent + close(inpipe[0]); close(outpipe[1]); + e->pid = pid; e->in_fd = inpipe[1]; e->out_fd = outpipe[0]; e->ready=false; + // make out_fd non-blocking for reads with polling + int flags = fcntl(e->out_fd, F_GETFL, 0); fcntl(e->out_fd, F_SETFL, flags | O_NONBLOCK); + return true; +} + +static bool try_start(Engine *e, const char *name){ + if (!spawn_process(name, e)) return false; + // send UCI init + engine_cmd(e, "uci\n"); + char buf[4096]; int attempts=50; // ~5s total + while (attempts--){ + usleep(100000); + ssize_t n = read(e->out_fd, buf, sizeof(buf)-1); + if (n>0){ + buf[n]='\0'; + if (strstr(buf, "uciok")) { e->ready=true; break; } + } + } + if (!e->ready){ engine_stop(e); return false; } + engine_cmd(e, "isready\n"); + attempts=50; + while (attempts--){ + usleep(100000); + ssize_t n = read(e->out_fd, buf, sizeof(buf)-1); + if (n>0){ buf[n]='\0'; if (strstr(buf, "readyok")) break; } + } + return e->ready; +} + +bool engine_start(Engine *e){ + memset(e, 0, sizeof(*e)); e->pid=-1; e->in_fd=-1; e->out_fd=-1; e->ready=false; + if (try_start(e, "stockfish")) return true; + if (try_start(e, "asmfish")) return true; + return false; +} + +void engine_stop(Engine *e){ + if (e->in_fd!=-1){ write(e->in_fd, "quit\n", 5); close(e->in_fd); } + if (e->out_fd!=-1) close(e->out_fd); + if (e->pid>0){ int status; waitpid(e->pid, &status, 0); } + e->pid=-1; e->in_fd=e->out_fd=-1; e->ready=false; +} + +bool engine_cmd(Engine *e, const char *cmd){ + if (e->in_fd==-1) return false; + size_t len = strlen(cmd); + ssize_t n = write(e->in_fd, cmd, len); + if (n < 0) { + return false; + } + return n == (ssize_t)len; +} + +static void position_to_uci(const Position *pos, char *out, size_t outsz){ + // Send starting position and moves list by comparing with startpos; for simplicity, use FEN always. + char fen[256]; chess_to_fen(pos, fen, sizeof(fen)); + snprintf(out, outsz, "position fen %s\n", fen); +} + +size_t engine_get_top_moves(Engine *e, const Position *pos, EngineMove *out, size_t max){ + if (!e->ready) return 0; + char cmd[512]; position_to_uci(pos, cmd, sizeof(cmd)); engine_cmd(e, cmd); + // ask multiPV up to max (cap at 5 as requested) + size_t req = max; if (req>5) req=5; char go[128]; snprintf(go, sizeof(go), "setoption name MultiPV value %zu\n", req); engine_cmd(e, go); + engine_cmd(e, "go movetime 400\n"); + char buf[8192]; size_t count=0; int attempts=50; + while (attempts--){ + usleep(100000); + ssize_t n = read(e->out_fd, buf, sizeof(buf)-1); + if (n<=0) continue; + buf[n]='\0'; + char *line = strtok(buf, "\n"); + while (line){ + if (strncmp(line, "info ", 5)==0){ + // parse "info ... multipv X score cp Y ... pv " + char *mpv = strstr(line, " multipv "); char *score = strstr(line, " score "); char *pv = strstr(line, " pv "); + if (mpv && score && pv){ + int idx = atoi(mpv+9); if (idx>=1 && (size_t)idx<=req){ + int cp=0; if (strstr(score, "cp ")) cp = atoi(strstr(score, "cp ")+3); + char mv[8]={0}; + sscanf(pv+4, "%7s", mv); + size_t i = (size_t)idx-1; if (icount) count=i+1; } + } + } + } else if (strncmp(line, "bestmove ", 9)==0){ attempts=0; break; } + line = strtok(NULL, "\n"); + } + } + // simple sort by score descending (best to worst), keeping empties at end + for (size_t i=0;i out[i].score_cp){ EngineMove tmp=out[i]; out[i]=out[j]; out[j]=tmp; } + } + } + return count; +} + +bool engine_get_best_move(Engine *e, const Position *pos, char out_uci[8]){ + if (!e->ready) return false; + char cmd[512]; position_to_uci(pos, cmd, sizeof(cmd)); engine_cmd(e, cmd); + engine_cmd(e, "go movetime 300\n"); + char buf[4096]; int attempts=50; + while (attempts--){ + usleep(100000); + ssize_t n = read(e->out_fd, buf, sizeof(buf)-1); + if (n<=0) continue; + buf[n]='\0'; + char *line = strtok(buf, "\n"); + while (line){ + if (strncmp(line, "bestmove ", 9)==0){ sscanf(line+9, "%7s", out_uci); return true; } + line = strtok(NULL, "\n"); + } + } + return false; +} diff --git a/C/opening_learner/engine.h b/C/opening_learner/engine.h new file mode 100644 index 0000000..092918f --- /dev/null +++ b/C/opening_learner/engine.h @@ -0,0 +1,34 @@ +#ifndef ENGINE_H +#define ENGINE_H + +#include +#include + +#include "chess.h" + +typedef struct { + int score_cp; // centipawns relative to side to move + char uci[8]; +} EngineMove; + +typedef struct { + int pid; + int in_fd; // write to engine stdin + int out_fd; // read from engine stdout + bool ready; +} Engine; + +// Start engine: tries stockfish, then asmfish. Returns false if none. +bool engine_start(Engine *e); +void engine_stop(Engine *e); + +// Synchronous send command +bool engine_cmd(Engine *e, const char *cmd); + +// Ask for top N moves from a position (short fixed time). Returns count. +size_t engine_get_top_moves(Engine *e, const Position *pos, EngineMove *out, size_t max); + +// Ask for best move only. +bool engine_get_best_move(Engine *e, const Position *pos, char out_uci[8]); + +#endif diff --git a/C/opening_learner/engine.o b/C/opening_learner/engine.o new file mode 100644 index 0000000000000000000000000000000000000000..e400c89a000e4fead1eedb84e1f3145f63ad0002 GIT binary patch literal 7920 zcmbtYeQaCR6~9TG)=h(Lz60%+M{%L!!?V+NB+yNHiPOAn?m)D3Or@B_d2YNowrfAP zDU>F~NyWn$0s;vQHX*@Z8`1<~Y;+LZq-~gBQdTiwlxdST0aaSMNvs=di+9d__c|w! zJWboK?ECS%zjN-n_nvd_wMV)l8*6K7m`pY7TWr21QO1HR=Iiae+RnVp!!~P$lfHhf zxN`LxKsEC&zgsiU7Drr#O2xS1U_FAR^(gQOX2ttlqI4sn-fG-3UfI{El?=Ztbc`c2 zo$d-GZKe*3qn(XRD~A2_x)&RFuEEwNZvIbWec&|KA=Uc|tjgSqxU^p#h4o|k{SmPX zp|Vzdf^Q9M?j_dd4r==kV=g%RUKvV9kTKYOAdc)&k>&@Vy~KYuSEq7zrn?bXOq*GXPB_!eUGkHs&sQpAWn<~KX}&(1`9~qK z?ZhZ_f$J4cHWj0;!r6+^qM85DOhdSrR)iYR%_+@1Vp%m_9`Vl?@+gJuYP|OZu1s@6N$NJ{BHspIzBoAD;v;?W4v7r7Vmf` zI3=v9CHp=utGZpiTa8Y~CnBZS{AE~4u=F4XzGnU=Vtx@Z#gazM&nFpl!#0&JMSC>l zL&@#$D#l%E;atT~7QkBRWzGqkAH!rh^OtxKOpZc^;nogwmioLKk7#Dt@6*gf)rd&B z3g;`vT2AHS=4m_~+NCzNxKFq)cnN5nKVN3R+c0WE$LD;O|0ey75HTMCM!k5STPv>f zAFMQsnP9}cb9BswO95xi20vBWW6nT-J~j6ete07#UGq)3r}IdUd06c7Dese0o5ueF zdc04T$EJ*iP2&~s_%c}T385@h;Z$oR@7P9gIC^%1Qr@qcN8nT%+l}4J=BF;Z!N{L! zQ;m_fxAKJ(w}EwVG{8Bd zmF{15umLO-@vg-_(c0kT^FHvw7nKT3`jKN$iq(>b2Oeh^Yo*~jDhX@UQyOt;r7iwe za9i+*R#3aZOSEFd-=vkoL+6DTPP%~+HgDJuF1O!>&K~i%TASB4r+GX6aAFIL&H-<8 zJ6$V`&uVZsT?AK7r0D0zSj2qZ?*;(wqb~0&hQG0E>`Xq`6*_|fiX9|C*J0%CKaBEW z^RFa8Cm=3+%=a*&FbsK!ax-{T!c~U0KFs^XRCupiOv2#~F%*=y9Ym1%(R}<=&whIV zz1{*VF?Uh*ye^yqC+Y@&qtOHRn04O>v#gbZ5c(~I5ZWnM8;=ESTLg0&!)V3aPr=<5 z`n|@xjK`@{!^z6UK;)QKx^G#;{1=V96F-+bvNNAB8dyG-;8}W*C30E4w|`Fq;{^aY zBOM!zCvpRHh8=UsFQs11*7&(h_W0A8ZY+1$h^I75L)$2^3h&nlfS-%$RCK z8&3mFmska>x%YNfGwiEb(YVw-0b~X6m7=eJrRwn&YQr9-c6Wovr+VD#a<&fOcI<<+ z9(S?B<9ir5O$X{!k5X`Td&)}PWKDxdQ9Vr{-@&CUy~A8e$aCF^29JB=^6z-uEC_v1 ziT)leq5nWF=qS{MJ;7ZKTp5_qu^h(go~=;olc{#)d})ey0uB1wLxS2L+zD;X4Js$A<3~`29BAYS*)eY&gc=MZ_O?ECTi@ zeDN%{rzZhe41d}If6f7a*#Upm0iSfh4?Ey*IpDJn_Yj2YQI(VWqnHCQ0h8N{pwv$dG5!)!ff-@xo9%vv#P!z{$vp%AZg zeuxW%xI~Dn30X`o9^&#LuAn6-EP%1o8MZr{Fmx76rgJ*m-J38niGCJEvtonM*uY>k z-kV6WOd_MRcse`C;<1#GWcgfD*E9UqhATS@AJ(7Gv@m^Gk0moKst+fO`B89&)}*7c zp?(xd>M4or(2XeA5yg8s$6^C|E(h%>-1aP&%48EMBaRXVe;i~}u^~_&P2|$CmbG9d z;7QWX#d=e5su^#5u9~rHtZjg^+`6mJ!BYv-^#aGc8qaQtqEAW;0n+QU( zp#%P$1O5`h{lw1K2)>%&hX_vn`5l2{fp1*7ZcD^DjX1ugBz~>HaXs;^Bk?wZ(|tTn z@aqWwB+-v=E2-ym!p9hwxDN+}WFPlxg41y~2psLlznP?-TM3`)?;|*!*G_^{{RfEt z3yA)r1OG*WD}+ByaO%&;iGB~^|B3MN%_!$}hVbE^$|`=A@K+JM9uEabxITDaOFb6| z9K|UAa>Bi+@Z;~QFzyPxncCb;Rq-y)vOIIjTV z?-V$$1O7IU`u7k%U57^qPW8M%^ju2xyh!+8C-@ZMUqi<^?9POw6b1mUh z|GCA1-{HXDO8C@&?k0TdZyCa;`@GDbTR3ttj+Iwxde1|_{ z!{s|1e-B`idgOb1rMNdG&cuGOKyJ^F(c1?lJj-}FAZuAY*&8tQVR-5G_Zqz{(3i`x zKsJpHq=y3Vl%-90sm9VnLwX7%QfWgEz&|QC@<*v???~kXnQS_vXN^4|iu(0|Xgmvl z-B@P--?;)BaH=9)s`}bRtswSAc1T>d@ePQ+Xvf>d5B*sok9P`=Bh}#>0@F3{MO<8C z2=O#6i4qW{*p@1aK8@7>RXzl=sMGpi0V*QN@$q*pO>%r$wnOJ)^7r850MaU}sOEB7 zQvO!x!FsUDuqqD-d5i^`q?|1Ae*{^lydMjBiecT(9RLhn8~vQ7XNA1oE@{8yjzR@> z+T~}3{5`e-?eh15>|*}o6MqQFxJLiMca@Zv{ +#include + +const SDL_Color COLOR_LIGHT = { 238, 238, 210, 255 }; // light square (not pure white) +const SDL_Color COLOR_DARK = { 118, 150, 86, 255 }; // dark square (not pure black) +const SDL_Color COLOR_GRID = { 20, 20, 20, 255 }; // thick outline +const SDL_Color COLOR_SEL = { 200, 50, 50, 200 }; // selection highlight +const SDL_Color COLOR_TEXT = { 10, 10, 10, 255 }; + +static void set_color(SDL_Renderer *r, SDL_Color c) { + SDL_SetRenderDrawColor(r, c.r, c.g, c.b, c.a); +} + +bool gui_init(Gui *g, int w, int h, const char *title) { + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "SDL_Init error: %s\n", SDL_GetError()); + return false; + } + g->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); + if (!g->window) { + fprintf(stderr, "SDL_CreateWindow error: %s\n", SDL_GetError()); + return false; + } + g->renderer = SDL_CreateRenderer(g->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!g->renderer) { + fprintf(stderr, "SDL_CreateRenderer error: %s\n", SDL_GetError()); + return false; + } + g->win_w = w; g->win_h = h; g->flipped = false; + return true; +} + +void gui_destroy(Gui *g) { + if (g->renderer) SDL_DestroyRenderer(g->renderer); + if (g->window) SDL_DestroyWindow(g->window); + SDL_Quit(); +} + +void gui_set_flipped(Gui *g, bool flipped) { g->flipped = flipped; } + +static void draw_rect(SDL_Renderer *r, int x, int y, int w, int h, SDL_Color c) { + set_color(r, c); + SDL_Rect rc = { x, y, w, h }; + SDL_RenderFillRect(r, &rc); +} + +static void draw_outline(SDL_Renderer *r, int x, int y, int w, int h, int thickness, SDL_Color c) { + set_color(r, c); + for (int i=0;i= 'A' && p <= 'Z') ? (SDL_Color){250, 250, 250, 255} : (SDL_Color){30, 30, 30, 255}; + SDL_Color glyph = (p >= 'A' && p <= 'Z') ? (SDL_Color){30, 30, 30, 255} : (SDL_Color){240, 240, 240, 255}; + // Base disk + draw_rect(r, x+size*0.15, y+size*0.15, (int)(size*0.7), (int)(size*0.7), fill); + // Glyph: draw a simple letter-like mark + set_color(r, glyph); + // vertical bar + SDL_Rect bar1 = { x + size/2 - size/16, y + size/3, size/8, size/3 }; + SDL_RenderFillRect(r, &bar1); + // top bar + SDL_Rect bar2 = { x + size/3, y + size/3 - size/10, size/3, size/10 }; + SDL_RenderFillRect(r, &bar2); +} + +void gui_draw(Gui *g, const char board[64], const GuiSelection *sel, const char *status_line) { + SDL_GetWindowSize(g->window, &g->win_w, &g->win_h); + + set_color(g->renderer, (SDL_Color){ 35, 35, 35, 255 }); + SDL_RenderClear(g->renderer); + + int size = (g->win_w < g->win_h ? g->win_w : g->win_h) - 40; // margins + if (size < 200) size = 200; + int cell = size / 8; + size = cell * 8; + int ox = (g->win_w - size)/2; + int oy = (g->win_h - size)/2; + + // Board outline (thick) + draw_outline(g->renderer, ox-6, oy-6, size+12, size+12, 6, COLOR_GRID); + + // Squares + for (int r=0;r<8;r++) { + for (int f=0;f<8;f++) { + int idx = g->flipped ? (63 - (r*8+f)) : (r*8+f); + SDL_Color c = ((r+f)&1) ? COLOR_DARK : COLOR_LIGHT; + draw_rect(g->renderer, ox + f*cell, oy + r*cell, cell, cell, c); + + char p = board[idx]; + if (p != '.' && p != '\0') { + draw_piece_letter(g->renderer, ox + f*cell, oy + r*cell, cell, p); + } + } + } + + // Selection overlay + if (sel && sel->clicked && sel->from_sq >= 0) { + int s = sel->from_sq; + int rr = g->flipped ? 7 - (s/8) : (s/8); + int ff = g->flipped ? 7 - (s%8) : (s%8); + draw_outline(g->renderer, ox + ff*cell+2, oy + rr*cell+2, cell-4, cell-4, 3, COLOR_SEL); + } + + // Status strip + draw_outline(g->renderer, 10, g->win_h - 40, g->win_w - 20, 30, 2, COLOR_GRID); + // Without TTF, we can't render text; draw a minimal indicator bar to signal state. + // If status_line indicates success/failure, alter color. + SDL_Color bar = { 80, 120, 200, 255 }; + if (status_line && status_line[0]) { + if (SDL_strstr(status_line, "Correct")) bar = (SDL_Color){80, 200, 120, 255}; + else if (SDL_strstr(status_line, "Wrong")) bar = (SDL_Color){200, 80, 80, 255}; + } + draw_rect(g->renderer, 12, g->win_h - 38, g->win_w - 24, 26, bar); + + SDL_RenderPresent(g->renderer); +} + +int gui_coord_to_sq(Gui *g, int x, int y) { + int w, h; SDL_GetWindowSize(g->window, &w, &h); + int size = (w < h ? w : h) - 40; if (size < 200) size = 200; + int cell = size / 8; size = cell * 8; + int ox = (w - size)/2; int oy = (h - size)/2; + if (x < ox || y < oy || x >= ox+size || y >= oy+size) return -1; + int f = (x - ox) / cell; + int r = (y - oy) / cell; + int sq = r*8 + f; + if (g->flipped) sq = 63 - sq; + return sq; +} + +bool gui_poll_move(Gui *g, GuiSelection *sel, bool *quit_requested, int *key_out) { + SDL_Event e; + bool updated = false; + if (key_out) *key_out = 0; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { if (quit_requested) *quit_requested = true; } + else if (e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + updated = true; + } else if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT) { + int sq = gui_coord_to_sq(g, e.button.x, e.button.y); + if (sq >= 0) { + if (!sel->clicked) { sel->from_sq = sq; sel->to_sq = -1; sel->clicked = true; } + else { sel->to_sq = sq; updated = true; } + } + } else if (e.type == SDL_KEYDOWN) { + if (e.key.keysym.sym == SDLK_ESCAPE) { sel->clicked = false; sel->from_sq = sel->to_sq = -1; updated = true; } + if (key_out) *key_out = e.key.keysym.sym; + } + } + return updated; +} diff --git a/C/opening_learner/gui.h b/C/opening_learner/gui.h new file mode 100644 index 0000000..c409657 --- /dev/null +++ b/C/opening_learner/gui.h @@ -0,0 +1,36 @@ +#ifndef GUI_H +#define GUI_H + +#include +#include + +typedef struct { + SDL_Window *window; + SDL_Renderer *renderer; + int win_w, win_h; + bool flipped; // true if black at bottom +} Gui; + +typedef struct { + int from_sq; // 0..63 or -1 + int to_sq; // 0..63 or -1 + char promo; // 'q','r','b','n' or 0 + bool clicked; +} GuiSelection; + +bool gui_init(Gui *g, int w, int h, const char *title); +void gui_destroy(Gui *g); +void gui_set_flipped(Gui *g, bool flipped); +void gui_draw(Gui *g, const char board[64], const GuiSelection *sel, const char *status_line); +// Returns true if something changed. If a key was pressed, key_out receives SDL_Keycode else 0. +bool gui_poll_move(Gui *g, GuiSelection *sel, bool *quit_requested, int *key_out); +int gui_coord_to_sq(Gui *g, int x, int y); + +// colors +extern const SDL_Color COLOR_LIGHT; +extern const SDL_Color COLOR_DARK; +extern const SDL_Color COLOR_GRID; +extern const SDL_Color COLOR_SEL; +extern const SDL_Color COLOR_TEXT; + +#endif diff --git a/C/opening_learner/gui.o b/C/opening_learner/gui.o new file mode 100644 index 0000000000000000000000000000000000000000..46d7ba4f8b4806a55fc2c53f25dfeb7029ee9405 GIT binary patch literal 7152 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 b9NbWb{TWTX?Jqxm9R+KUwBKr7dRG4h9Oi*( literal 0 HcmV?d00001 diff --git a/C/opening_learner/main.c b/C/opening_learner/main.c new file mode 100644 index 0000000..993f89e --- /dev/null +++ b/C/opening_learner/main.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include + +#include "gui.h" +#include "chess.h" +#include "engine.h" +#include "mistakes.h" + +typedef struct { + Position pos; + Engine engine; + Gui gui; + MistakeList mistakes; + bool replay_mode; // allow revisiting mistakes + size_t replay_index; +} App; + +static void append_uci(char *line, size_t sz, const char *mv){ + if (line[0]) strncat(line, " ", sz-1); + strncat(line, mv, sz-1); +} + +static void position_push_move_text(Position *pos, const Move *m, char *line, size_t lsz){ + (void)pos; + char u[8]; move_to_uci(m, u); + append_uci(line, lsz, u); +} + +static void collect_all_legal_uci(const Position *pos, char list[][8], size_t *n, size_t max){ + Move mv[MAX_MOVES]; size_t k = chess_generate_legal_moves(pos, mv, MAX_MOVES); + *n = 0; + for (size_t i=0;i0)? (rand()%wsum) : 0; + size_t pick=0; for (size_t i=0;i0 && parse_uci_move(props[0].uci, &app.pos, &m)) pick=0; else { snprintf(status, sizeof(status), "No engine move"); quit=true; continue; } + } + chess_make_move(&app.pos, &m); + position_push_move_text(&app.pos, &m, line_uci, sizeof(line_uci)); + awaiting_player = true; + // 6. Ask engine for optimal response from player + engine_get_best_move(&app.engine, &app.pos, expected_player_move); + snprintf(status, sizeof(status), "Your turn"); + continue; + } + + // Player move input + int key=0; + bool updated = gui_poll_move(&app.gui, &sel, &quit, &key); + if (quit) break; + if (key=='m' || key=='M'){ + // enter simple replay: load a mistake line and restart from it to the position where best move is required + if (app.mistakes.count>0){ + if (app.replay_index >= app.mistakes.count) app.replay_index = 0; + Mistake *mk = &app.mistakes.items[app.replay_index++]; + // reset and play the line moves to reach the mistake position + chess_init_start(&app.pos); + char tmp[512]; snprintf(tmp, sizeof(tmp), "%s", mk->line); + char *tok = strtok(tmp, " "); + while (tok){ Move m; if (parse_uci_move(tok, &app.pos, &m)) chess_make_move(&app.pos, &m); tok = strtok(NULL, " "); } + snprintf(status, sizeof(status), "Practice: best is %s", mk->best_move); + strncpy(expected_player_move, mk->best_move, sizeof(expected_player_move)); + awaiting_player = true; + gui_set_flipped(&app.gui, !app.pos.white_to_move); // if it's black to move, flip so black bottom + } + } + if (updated && sel.clicked && sel.to_sq>=0){ + Move list[MAX_MOVES]; size_t n = chess_generate_legal_moves(&app.pos, list, MAX_MOVES); + bool moved=false; Move chosen={0}; + for (size_t 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 literal 0 HcmV?d00001 diff --git a/C/opening_learner/mistakes.c b/C/opening_learner/mistakes.c new file mode 100644 index 0000000..89f76b0 --- /dev/null +++ b/C/opening_learner/mistakes.c @@ -0,0 +1,65 @@ +#include "mistakes.h" +#include +#include +#include + +void mistakes_init(MistakeList *ml) { + ml->items = NULL; ml->count = 0; ml->cap = 0; +} + +void mistakes_free(MistakeList *ml) { + free(ml->items); ml->items = NULL; ml->count = ml->cap = 0; +} + +static void ensure_cap(MistakeList *ml, size_t need) { + if (need <= ml->cap) return; + size_t ncap = ml->cap ? ml->cap*2 : 16; + while (ncap < need) ncap *= 2; + Mistake *ni = realloc(ml->items, ncap * sizeof(Mistake)); + if (!ni) return; // OOM silently ignored + ml->items = ni; ml->cap = ncap; +} + +void mistakes_add(MistakeList *ml, const char *fen, const char *best_move, const char *line) { + ensure_cap(ml, ml->count+1); + Mistake *m = &ml->items[ml->count++]; + snprintf(m->fen, sizeof(m->fen), "%s", fen); + snprintf(m->best_move, sizeof(m->best_move), "%s", best_move); + snprintf(m->line, sizeof(m->line), "%s", line); +} + +bool mistakes_save(const MistakeList *ml, const char *path) { + FILE *f = fopen(path, "w"); + if (!f) return false; + for (size_t i=0;icount;i++) { + const Mistake *m = &ml->items[i]; + fprintf(f, "FEN:%s\nBEST:%s\nLINE:%s\n.\n", m->fen, m->best_move, m->line); + } + fclose(f); return true; +} + +bool mistakes_load(MistakeList *ml, const char *path) { + FILE *f = fopen(path, "r"); + if (!f) return false; + char buf[1024]; char fen[128] = ""; char best[16] = ""; char line[512] = ""; + while (fgets(buf, sizeof(buf), f)) { + if (strncmp(buf, "FEN:", 4) == 0) { + // copy up to 127 chars, strip newline + size_t l = strnlen(buf+4, sizeof(fen)-1); + memcpy(fen, buf+4, l); fen[l]='\0'; + if (l && fen[l-1]=='\n') fen[l-1]='\0'; + } else if (strncmp(buf, "BEST:", 5) == 0) { + size_t l = strnlen(buf+5, sizeof(best)-1); + memcpy(best, buf+5, l); best[l]='\0'; + if (l && best[l-1]=='\n') best[l-1]='\0'; + } else if (strncmp(buf, "LINE:", 5) == 0) { + size_t l = strnlen(buf+5, sizeof(line)-1); + memcpy(line, buf+5, l); line[l]='\0'; + if (l && line[l-1]=='\n') line[l-1]='\0'; + } else if (buf[0]=='.') { + mistakes_add(ml, fen, best, line); + fen[0]=best[0]=line[0]='\0'; + } + } + fclose(f); return true; +} diff --git a/C/opening_learner/mistakes.h b/C/opening_learner/mistakes.h new file mode 100644 index 0000000..7f77ce5 --- /dev/null +++ b/C/opening_learner/mistakes.h @@ -0,0 +1,28 @@ +#ifndef MISTAKES_H +#define MISTAKES_H + +#include +#include + +// A lightweight mistake store in memory + file persistence. + +typedef struct { + char fen[128]; + char best_move[8]; + // PGN-like ply list in UCI for context + char line[512]; +} Mistake; + +typedef struct { + Mistake *items; + size_t count; + size_t cap; +} MistakeList; + +void mistakes_init(MistakeList *ml); +void mistakes_free(MistakeList *ml); +void mistakes_add(MistakeList *ml, const char *fen, const char *best_move, const char *line); +bool mistakes_save(const MistakeList *ml, const char *path); +bool mistakes_load(MistakeList *ml, const char *path); + +#endif diff --git a/C/opening_learner/mistakes.o b/C/opening_learner/mistakes.o new file mode 100644 index 0000000000000000000000000000000000000000..e8e7eba50469eb0de6dcce06d81eae2f0a3dafe7 GIT binary patch literal 4016 zcmbtXe{54l9KUuOs{`IEh-L|x^^amk)R%S)9SpVYR(3UIfMcKnma?^M(si?a;BXK& z+X2t6I1~RdA#sVCkQft9NW_54;@E^l6QahDpi%t8IDP~P28<%}`@MI!9?#Vn{U&$! zz0ZBVKkj|+-fIs8!)xp|8xdk7Pmsb)poDnl6?7j{eMBMiNnE`)Mn5Y@L|xG27;mA* zA2j#m&j^@nCFWX#W6QW&Mf{sW#!Y`~$oSL0F=Tw*N{ugCXl|W@=9-)|zrjXVo>Ma) zfeFnoA#`x;gcEhO@jPAmRk|WROl=oHOZ`SWZydH063Xo-EEZ7JY!N+29tJX~1Z zvdzEMzs>*r6hK6+Y6QQz^2~lMazS?`$dm)rtxZLOK~ijuuoc2^LYf*HR=KL1np>B-yeqUC z&FiZ1)U5E-d6#4EC~#DW3tAa8;K8M}jUfilf{BgK@8R=B0)P2wV(WF<7SAhp3L``aJWM0x0UZ5`Gh^SPI6z$uEsPk*P3!`S^N2mDROU;9ifgE}QK>#y)~I+g zbL=N8lxn}?3MiGp2`G-I<^sJA+7Q}-ur~#)%ry>vm~Ssjt<6t4Q-E<1Qb`kG@w)umH8FV!8v}VE>qs5{4&2}lf6Qz0}T8EXC@c$ z_RoUPv6Nh&o{d#9^OOs#=4S(|akngB(=x)o4${*79Jg95=6IFFMb!mup%hotO5?bg zvw8vc7&L)_IwM#=^yA?Ot0CwiXtVJ*6v3YZocZAgj@1&djw1YI5qwV(d_Umme~kNQ z^#&|gg#UUG{LLcxND-WLb*A;`u2@=YBU-q@gPj+<8tj(OEJr$%oqAywPsL(|S+u>K zq+-!TqPvZxlRc@Hg9+YQ6nqEZoxcrlUKCLm#PQ4v{5a?+c#ZIE3VZ@V z5ai>T7Wj7vf>`-Kn(*KFzjNHmf1KA7af~7SQ;s91Ncc{Pk9S4z^AaCFFakd+;dq_} z{;lK(-qSPquM%I5o4^D?FrS6cgrAcVULoN>N%*}Ien-Ob9W4A%{z5?iGJm^-%lu;! zF7wAFT;`XeV-OtV=dfJD@pqrdr(Ajta@}3PLBVbCt+075>kGJecdWPI9zGwo;ym8x z7F^tSqNVqC>Cqi9>M3*V5Lzmhh-!MQS0`F~RF4vEM>K9P^Gi^JzTle@K^6x_j-z86fC2`+l}iFa-1IzgqZYM;tHIXLrlrTprOzIYVQSG z^jQN<77^cyi}+&nff4HB{0JZK^Zdnc7J&;pG2-`3=z@de*?+nJXMv0V_T)2<_UE|0 zH81h}k%RIgG>mVxXFu2gue2m*wNC&CL;0Y|QpbO|^l}iV_`VnR;yW;FY~WS$1L@?6 o;a$glTJ6!Nwe|p!YEJwRCz4pULl(f=Z1x}U{QD$ZRIT=Z0S7hfV*mgE literal 0 HcmV?d00001 diff --git a/C/opening_learner/mistakes.txt b/C/opening_learner/mistakes.txt new file mode 100644 index 0000000..3512742 --- /dev/null +++ b/C/opening_learner/mistakes.txt @@ -0,0 +1,12 @@ +FEN:rnbqkbnr/pppp1ppp/4p3/8/8/N4N2/PPPPPPPP/R1BQKB1R b KQkq - 1 2 +BEST:f8a3 +LINE:g1f3 e7e6 b1a3 +. +FEN:rnbqkbnr/pppppppp/8/8/8/7P/PPPPPPP1/RNBQKBNR b KQkq - 0 1 +BEST:e7e5 +LINE:h2h3 +. +FEN:rnbqkbnr/pppp1ppp/8/4p3/8/4P3/PPPPKPPP/RNBQ1BNR b kq - 1 2 +BEST:d7d5 +LINE:e2e3 e7e5 e1e2 +. diff --git a/C/opening_learner/opening_learner b/C/opening_learner/opening_learner new file mode 100755 index 0000000000000000000000000000000000000000..578843f2aea18d8b0d7726c366bc8381ba9708be GIT binary patch literal 40168 zcmeHw4|r5XmhbJPAq|Awpi!fu^l0OPgE8TclL$y|(w*GK4g`pRiZ-Fs4H-hZ>2yO- z2f}nmbGPj=Gt0QnKHtdB>O7z0uI`M35H%(N0yydh91(TLK^^rr5k*u8_^03R)UDgO zY3bS7?|a|-zW04x=&m}aPF0;cb?Q{rsk-Sc4p&u{$t37cws4a`Q1Nvdrkp3N`8kgu zC>KhFJbYg%Tp^4GY-Ttuy<7{Bsy+GHBSQ#;*>@^@vr>cg(3p?a7vS`1VMnfp*F$4Il1$Vc-0TOZyH z!eU-urv59Zj>4mdyn5EoV&F;Fsu$cMWVH!B#EYJ85=;yp0~|eB9M;o`wg4aM4$5!+ z-*VNb?ZoRIx^oHwhm|4`~FPe_> z1YtHlR32-$Rxsn1@MR}s`j7(j-RCyYU+`VDPzBt%@LMv_n^EJr=oe?8-;#lz*ted8 zpKmji^UDnQSO$D3L%mBg;CE&yr#S=uvkds<8RY5AKz}3y{hJx+FV4XKvJCjUGT>jy zP`;FbzA^*7Hv|3T4EQV1UMoK7Pg4f^AA|5*`c|BQpMTFl|8E)S-_3ykJVQBqGvJ@k zfIpR?{GVl@|CbE-J2J@Up$z30WWfJ41Ac9WdUG<+zng)+B?F(+KySq-{kbwj`Oysc zYLtIfdLe*+mx2EGNIRGQRAk`ux(xUiGn9WQ1O7^sGeNji==rI}tUlifZf9MMyTKdu zu5N4zd4qK|l}-L;Z(aSWCa>UjuU_YGcDID;gCVzDBRwbK61&T7_qK$B{!Pmon;ZNa z1;Q-$HltvBuzurWuP4M%OT3|BY^A@+A2d)_HhJqcxH9Oi4|xqm)%u&792>mNS}CXq z|5?sO;YJOoWzh=vYH7}9o~B`IgWeXs1kUUT@@j9$5e#yU?B1sOO{@l>HAa>+-sLrL zTh-Xq#Mkfmp>+p}h^ z+gIP%B=~|}ui$OL>kTkAGzz^4D!55#2nVJMAZ%j%cv~SNw;L0^hj;V3`o?Ch z4fP>}=)j=h<4qfGQj>opZ&FLJzPW)K+gk7TH8$5bk(7L%CVz`p*jV2f3N$tdEsd*@ zAguGQBfs*J=2t)9ir`_dKAb)7nm7;}T}S0iUCh zuz5C@Z)qOLfL@lTRfIpYrsQ6QqOV& zJ}-q8gp~%oXuz*A;PVamfB`?jfNwS6jq_BS0nfL-ELVpCPx}e|=``Rk(vb)sGT<*Z z;I|v_ml*Jm8}Nk&{L=>fr3U;?1O74tzQ=&S+<@P2z|&e#e+CTrD|IBoBL@5=1OBK1 zZ#Cdm1O9sk{0Rg8Dg%DVfWO*+7x*|O{kIwL76X2=0iSQcUt_=*8t{K-z*`OYDF*x$ z1AeLjUu?i%YrvNp@ZUG!%MEy1d+CpCz;lztl4=cjZo(LTxdDH@UI@aK27Ivrzs7){ zZomf&_!$O#s{udDfNwM4Z!q9H4EP@y@SO&HsR93x0bgdoZ#Up?G~mxi&r9IE1kOv~ zyadim;JgIROW?c&&P(9D1kOv~|2GMIE?#+7j(lv9W21WgaC@}(gtC$Ya%7KXH)~3A z)=NMqC%=quaZ)*A1h1jA#L*<4$$bQq>nAb5;4Xs6<&)@P@NWqwS5M+;20uYCxvmo1 z8T>1P$>o#iWbi)`Os<|p8-ssFFm10B0S5m)!Q}EutYq+y2_{!hqL#sT5lk+gL^*?l z1e0qgQOw{U5=<_ggq6Xo2qsrfBA>yx5lk+eguvj%1d9Zp_!fxC^9jx;_$Y%b2%bRj z0E2HNm|Qf89tKY*m|Qc7rx|=L!Q_%jY-jLQ1d}T!(aGS;2qqUyqK&~95KOL@M1a9# z2_~0IVkLvK2qsrcqL#sDn*o!HB~i}clLV7%B~i@aV+4~cFkxlzdjykHVP)_tg2}~^$Y=0v1Q!u3FnBS+*Asl=Yu5h+lPe=}l))7Qmk>O_ z;2Q}hmqwz8!P5yQS4QG#2471sxmFX~0ZYqDlG0M?7OC!*CGysf3sF&n9Gf_q8Yn9d z+blqoyzPoVX(RWlEB=hF1EK0b*C_kcZg9AZbj787>QX{BSyqmGF8{`*oK}AaFRdJ( zWph#DX|-XlAav6xeUVgMeGD19hA>LI2k<4wqc$4Ca(uiA^s=%~R_r#5>c%jN_K49k z3`e4=BN{ESR9y_6?6BLG5y49s-(W$_gC3~~@FvIZu~{NVO-jGASEbQc(zDBoic1d3 zXhIvhr{8W{D@49Dx#HVw0=h?5PW$%RZLNav`EOv1o~fY{-`G`-OtP}gMmzzBHC|*QpvFu8opG9IC@aV zF0zQ!-7E8`;qRy|=;kh_1Ij5``Lk2mqvm7O5M$9#(TCLig>w91+e&m+$#K#cin){q zTX~K0g;VJlC)q)dA_Zu>OZiBS>@}$`Lle8mn(7HCTdh8uOghUx2~BWD_Dqx`yDjq8 zZ>Z{UuPcV)Txdn1OF354{ueQN162}#-u5)op=ea^9ux&s z)Ys|r6^WCgsD-Nh(ou4J@KPN{Jtj2FQd9P+7<~X4w8kl)P`kFE{`Q{m+pI}tD{O_~ z9+G@{$?^H|g6E)FzRv6^a-_#Bm-U6eEIEWqA9#-%(=&Lju~I|*pf*i?2?d&+W&6Zv z05!(Ol^xvtk=E7dkL@VH8K2WHD{rY+LfXpSXixYWCOf+9!Rel7QX`tPuoX0>rXi6e zosZt95&T<>En_EL06J2o~HjslpyC(i4bkS~0#lGm1q%~B` zd?orZX?jI}^yAeC1(~`>Y`+4wB(WS{djHy$FBAU=yIFFaB-s9A@LtlBcz~H4G?J`} zN#3=Zfr&)pK4iFGi7^lkg%h^|=h&XsJd$P>Y>#5NplaOln6GR38eA4!9cM>tLp|!sN<+lv!z6iLwmpY4m|J ze!r~)#3em){G#`&AX&PrCC8sZ%rlFw$VC{aershLknRzP2D2Lq*Epo+R-5kn) z3|^_rqXfBT-AUB%4WR@WXRP3^p&_&#m0|R$Phf6}_Jj_TnrOIBRUg&^gL@px8EVTE zVyz9-V9W-_N_x5pVl7lZJ_S>NoP^u7oOl);$OOu4nfCLqqmrTx|H{5-bjsZQdAM<1KnLqb{wF9gAmq%B|R?X zUG(1FNML=Z&iaznNmqhXu=eV&z!mID`w&>v>Q;0x zu~de|X@8VW(t?^xdaC>xQ@f<}D*aOAGn1w$5#$3ETX|{SVpm>Cd`wCi8}%Xjn+uc7 zc_8r);n9gS{$alfS)LdNZK+W{O03~#CZ7b;MiSjoNDbF!F1>Tq2}N4-QzBB`D2>@V zK5BMV`h!8b;sQI6SjUDihORyksMlN3>*{hUUF-GjRNGF>k0jUSK&sbJ6TLhk)!p4>Z>Et~q~gWrYH)L;qxJw{x?}e+zbI$K zP@?h@t3k{8Y?~*9$8{x1C?=8z_Kt-zmL3*anPXEopQP@dW9za~r5LXkpHh&+$g!m? zOuUNp;ku|QX{l3rRjQMeFK$s@Um`1Cy~wg+N2+cGF=lJ8w9AIlBt^9=??NBD(1h!| z=}UbC9LP#GGZ6m(nl6}cp&QJSAygN!)Jh8py4J3E^4Wx?tS#uTEEH&m%VwuL(PBH~ zv74x4bDt!^#%I|j5?WtLj~W9Pu-U`pn!MG2(CF_;r@x)i55CIfrLNWJ>NGkbRqhOp z?$>GZ*5o-P$F_j)WJsiuVNo}Lm=qhswUp4K)aVIdNThco$zbowJ2n>XHo(#{r-BWJ zQ#qX4W6+dET~;o~H|EoNjgGcu#bpa%=b=sFwBml8u<>Qkwt4DdRN@3n(#|BXyRODp z7kzcpm)gox)h(Z4LldhmkYda7HB!pdT>zr80p*~$V84t-p(TFnkzM-<_Xn}&52$x} z@0slQv{!%;d}4C~n1ZRtd}82(qxQHrcdDu!^ofTXY*Ts@lT0t|qS6kYRbImye~r@D zdu)TFhNybzQ#7vZo$w3jhk{+ddE$vD$ckF+$_Lm~ zSU~}+iYg^$@7sIgwIWsBDmn*x&zdD=K zYM;-i^xM&Pm)v{ys;P&k_DOMb2^zTqLMeXR5N&oW)QB}E6;!oI zY+|vx1l@Ba)#~1WOkEG+Yu8eI)x`X~6Kgz}6+Ts;Cp|*GDFE1jwJVP@aVSr+X44vm zB%=Nf%_qY03UPLM?~Nil!?r}L<1 zKRu*;rkq6BgAcX`r>PzCN%7h`Tfs^3)|U|b(@9CZb`OAy_8^Oz6w6V0!7 zEU8>bqFJF0S(N>DG@a)0-XXKybTk{C1sPBhT3?GOb%e6NcW5-?w4zfVLM4@P`(&up zX~qE(sCLlTQDG!Mph$~=T7f9(mRe7|L6@)xl1zJOy&GBt*eZ3S?SeG*9jR?gAy!iM z&rL@jH;L^fn1s4NK~fie?K*_7#5CZT{pu!JOI;@jsW7CbFLt!NFw$hR=9u=<>d_)a z`tUg`i|zl8r5CAiYfbwpF*-o`+cu98#OPj%#zteI(g9;DD7$;V%4WQX?PIdg*GF^g z$X37_in%l;JJPh-*Igtj|k8w;4t z(E449x$~L1U{;CNWL?LJk*Xso9w6_hc;&}Zr3p`sSl|ufua5*fvYbJ z(i({}ayoJ}l0~7;hWo}qPcAn#4thCqk1ZgC@@O;#+DhRhh#{%F7GGVh_+qvZW;DMh zwwe#t_;Qu(Dw-c(^OkA>$C@yBrMbujA$_;b%aEX+p*rEmcpLz>X=b)f3vVIQjBVOZ zV28J9iI*Wi%zr<-k*0)w>MN8N^XJtXd#taC$HWRuvZvoZJrLQQ1ARyg@@0I0b|c8E zZqqV~Q5y)EQgl%RchOg35^(COV;HraQk_%z`j#ci=a~558JUMEXc~8ZL0Og8Wh|WG zxG}3sz!(H(j?J`blgA0=q_|)pa%@W3OQP$b@0D!m8}erxJI zc&6-~ElbN1TNcO7)60%*zS;Cr*|E(IMV-3;7pIe0#vYk^0+B8nc5(B5SY9}93I);8 zM|Q)K+S-OM#f+Oz!rGm($2-xKB?M_nW8i(=F)xE z@BwB=hAt0Jkov2}2rnR$;M_Nwc!TF8^n|21(48Ntn_0=>Uk|UrNC`_*S|PSSfShQh ztf<3EffbU#{1tALC-s|cro=6XF}njv<&GA&^^Dn@kbvt1oDW+y3vseMWG7roQoRT! ziR{T0RD!r;>6qxD@I*-&E$yo^1BFtn#+VXGX1LnG0+xGKX65z7abVO^)wpUL=AGnC z(#hgV!?80 zcgZ1qyKH6&zK>j?8|r`^yhQfYC$8%CN#bF%P5u3ckTIFWZfK6s_OgYoZ6D!SCaeiv zlmuZt0hfom4N1?DL6>2*^9LvB>yOBwNsK-V{EOS~H3{l<;FOiym1GW+Pt?o78Z5?U z_#0+$Pb0&ZT-M>h%teW%`BsLN{u4^`d~0bv*@Q zs_QM(Wii&Zl-Jb@U4v8xFHemx8rr!+cq{FQdtkJ%w1)?ax@LHouZ3Bp-eV)ZTGkSe()FYfWc@1IToUD%Kwfq?LDn|a@k&tU+8n;?RV_Per``;Ne^Ot z{~|4;&BM2;&BKwah&POE!4y~C zjGf#}TUT-t@+FXuwD^zxQ8S4Gwq}kEh5m^O%>}uWdG*50gTIBm6q=szprrLk(!k{a zsZZT0%mQHOh@b`-@~!{84+b3Nh(GU%_JzjQv?sBagGuQ`VPVjgB-LL*pju60vhfH|IdthAI3#$~}NXcWgrhmqhuSPO<>rct;Z;R0(S`I|MLc>3y7|jIIE>wr9 zr7w4cy_A&!mvR6TjA0D?ccQW27iGnpC%R&g+Ff77f?vCoiZAT3a-1<#d@U=bO28sV zddUTtE0?_)9B?wAFV|J}hj4_Q7{WBzsS|%Rri&JN81F-9itJJ4GFU$G$8Tv<*G~>1 zgtX4UK}W{pbFJ4x7vex8ThH08$;cVsVy0C~DbyAkQHo+oOZt#iz5HDg2sw}A_I2Mt z3lA4VGsy(nm1$d{a$C)$>@N7A14LRjD2o+;O4QLrcs&ql{sJ1Gsli!z0fwC`W@8H| zm-4910tD7q4sl1wmW!jSaIvH06b(>HFbvR{=o@m`18Y}K7zSu3#-&qvgGLlBKiCje zmtO&n3B*)Jyxz~!(3D@IDQeeIQt&1icZ{K8n(fvAgAqh7&^~oGOHSE-K7f8tIju-~ zVLBu+ctOfuM~*{@s-bV%=y`zz3_EMl<{QqI`fKE*A#@N!h!`Vj6EkEM2v+x!D3#ry z312?=8>kxyegp#O{S&FA(9d+Go=AD&3|;VNrqu8^-#x~qoJFQ@;YC%K0iF0g^~nE3 zYb8=TpRrh?OG6h!j(#9Fg)^RjpE8ivo;et^Jfe zUX`Pqkd%{l(b?ZSG>M#Xul1@|++ymN;B;IP`7mc{KX!jLdZqY^lH~XHN4}aTKG53muWvGOji0L@8wKADJg%HPXDJ}l;4A&=`1SmQx2YfQ*<7b%KjMYr!zl{ z&hWpw;^PlPE1dCL^PQeC@Y=A_t5Et__?xsMOIeX+heKmoQBv7Jcrvd#^q-C>Oy+KM zZsgdawzDSj-WKq}RR#76CyQ@}AA`_pQHx#qlN`Uzii2~_ZpFshO$Q;~jq{cL*oaL< zNA^mm-;_jWZ)C`{WhOZ)z&RF#M)Vc3=K#bfww<|I6JPk^wlixHEw`%SaU?gn?6vT} z&_V#49a1^Q;{)Ka?Q8|;+RiQ#xAj>D#ORk827^b2$Lq26)P(!&&<*Bq9ekeISLn?+32dd`jy>?COVl09#? z&6!jywg)NC%oHXN-j=uq5HA!2>xke&pzO+##5jb;qu%niZ%vmTDCvp3YmM~YJUCVx ztV89Yi%WBJ!k=kpUWb+b#P4vhh@n-|vwOt+hWi}{QWlzg<(|<#Wx+TcP)y5_+P;As zjo!R>c{ZDILhn?*z`BndZ1ZrlLR6pn6Q*Rb`xRN~modS`=fojBAu^{UAVx2Qj#PC_ zo9*^{*jzdx!Z#-aQG@PvTR;5&*q3$JTxzF6G2G z=3u~Lq+(s>Jl*?k_S6%Ro~+2xuf+DBA%p1*hOs)ywEy6cQYq_Q(u$PC8eX3I4{xK% z;ttFnLd7Zj8j~r6b5ftOL`qBtNs7PzN=Zmuz%sRgHQMX5^?v#}eFS~g{uBr(1F1>$ zO$?LB*?D679}(%VtP~P|KqOY_!t|A(WgZ*nB|Sc+vNAyzleBRU1#HK~nlFH08x(HW zB4@M0S3^RfEBh<67x!nMqhAv>$P+m;N7IL!!Y(4O?ysC1?5~`6F8V8o{%lTo9Bbr1 z0wR4X4cWneSSA%u#DQddMb6$RwttK<%X;a3MEWadqu|nW6}*QfBm3779Mf2eBtF3x zUE10XF^$*`Jp_a^cBwr3VcXM)2Ct~0 z6gFBm*>i~8+~r8A&W8)K6FFcZen2}CE2Y#@iWLUqTA(i>%u2!(12dJN072z!GlRt_ zu391g)TpfuA=v6bfMImKpum3)cq?GsZI}XZCO~q8vqKxer7j##;BuASR;b!=UxbF{ zRW$B)Y_qiy9WBgUa3~eJpxybH2V`Xk#`9F<-J_A-wSyw;BMw=}ghswC4^1w$gioZ3 zw5aQGv`o2C1ai|+^Q!z>61al#pWVcO2)LxjI_$A2H3lCGz z!vE$dIW%(1N`e=6Kmrh|WyJEB+71^T1a&=D(+D}F`G-%;%i zw!-;w^BQ8Yw8ry&oI=bMXbH2yh1!c@K*5Ep&d2DHu*Oi*mB$fE`B# z&>|8z3O-$9`j~Z}dJTq&bgOh5HrH4V*_HR~%7?ThdP{lZ^q)ofEHGFQO+CifLuGG- z9&zC|L9vt8MDbA{foo^kZn3=)+Cl#PLU=t3!Suf&!N8_?-wTj0V-@?Y!D93uaaM9b zISFp0I>r}xbeWE8eEkFi@zy&d-^>&5tIUDV`~d%)!Z+6Q31M?Jyfti|Y})cUQ@wGkfs`DP}bjE zav@Z}y8c|dV;89BEc4pt;f)}S9oEt?AmM+MVoT-7*`yd%F}jq4y=Nx%Ca$2}0+#Lj zryfyyX>ZYsRlC^UPvbA{FqQR%auFuW`o#9%0>{jaHbgKiNVI4gb!=R_T&AwWm9gcw zF@xg7mQvF^8NHp@itIGLkOM=#j&91HlITS31FSkpsa)~vuD=>hA@lBxJFJ@BkYZuM zZu&OzEruK&Jt!wNJD_cZFpF<#RZg&=9V@2|F>X5RGno3{MuJMA}ZY5d#U ze^Yw-&?e37NYaDs0q<|h?X2;M+T&1Y_9tQ&dr&A(FA>^Mu18L^|!`^QCb3K zNJ0IDG2vr$iBwy1po@}N$+{NnD*OblQPJGPDoH#KdRi~1^p@tnjaD);4H@RX^{KgU zuCH@=-n)usRNc6j97xT37)YskuLyO+!vF2l-ge`(cLLsAR~eiS&AJg|bKkH&zKH%y z3}J{4>mwPFESi%r4WF_vFkvb>0NL4b9iUi@Hlwqc)lZFiwirQY*lBfJM{gGjX z`6PLj+A${=CN`nG)cT+Co46NJ)$~V$Zy=u7x|ro-NIqTS{tpIhYPuBLMKq3?`hyUt zVagmSQqmVPPU_pZ@~CwuT9hCs>fcz;(-0P`XY1coPYcy^yR<@UPzy4r>VaTuNwBpr zABjuwrQ1P{Z$;}9)%c?IW83CRZLrM6U|9ey1;&oSGJ|YS+veFA7E`q7jfh^MMavMq z0MRb6Ds<1mS8BZc51d=4#>*#QOQV?$8g+{??NBNW=jXVeXH46HG#WIN!irDt`7myo z-FB-sWE2pm`kSUrz)9&jv@f|6Jkl9ODNUC&Z4$qUJ;3VgTQ<(PTqa}6NL#0y~IAkidG?7K+ zv~8yR7R@4IKn?dz635}GzWE4a0>%!D9zp%km_V19kw79-Uoj^99tm1=wTp*THfAhH zJT)KxiEhN*e!JE&?VtxYH-Ve~1<{^ES(=T-!+*|c=2F#*jcKb>X*W^Y4aT&akw$DW zMS;=~MH*XGVCzn|#??fS58ER)Z{R(kS$Lz21iVs?Usr^kjHY z?LNbCymMg&IlZreswJ_zhQzGAMV*7)B=msZ%+O52e%v{ub#&SB@FBW>Rf@i19k!CR zua;>=VmFiweEhbM`JJw(&Cow14pmw%i}ufmH{9{V9Y8t0fNBP#D~LPj5Lg0-UlJq4 z2h`B1yvemhQ%Lseiv%{7-$oiIhhGB)IH4PKOd+vNMA6`DW#WgBkNW$4+@7$r7FqcQ zS5@iCiUp3m)#Xy;3*3TUUG9kfC2WrPYfZ7re8nM)J1Pr09M}+6Uew`$SFiHo4o4+| zOFA6(SY@H&a3S~FvFiQ<%+2>YTsM(9oq>=?_GEYAax)x9O{1`NtsH~7*4`(3mY6&2 z7YS{5=L(@o1fjn8{GoV-r61@bamSJ|qN7)Sr~?D1?e1|xC^wcJ88U}%jttES`QYi% zZoJajF48SR=pWTPP_^zg5}!4%@l6&?J}f~UmmZfQA6TWx-r5AsW+{6Tt29T3Mu$ox zLs{W}?RN-^`yJUuga0xz{ie{A$k2`9c)z1G*zcHKG#E*x_d9ZsWmFNnjtxHg9rHjk zwVAPND;MuPR_<@ zQe-G+<26Wfo1}Qs7J*CPF+f(-5-_||E)9vhKo?&}| zQWruG4}@-pWh=*+hheZ|kQQ{fY!})giVN%=D{K}q`aapI?%iZ?iA3B1EX0BcXy_fh z3+)(u1ywkf=wPlbcGd_RLZubbI|gY*suOU7kUZmbBJF_oQ2s&^`GsP)9rcRcr~>69 zXJ!2PBjhf%$D;$6Afz`)un!u%0QVjA_d^bgQM!LLyEHs#pIvSae?TvYke{i*3O^H_ zu-Oa9<5Z1<)C1wsRB*u*N5?`7b9^VrR|Qe3U!g;^CQ@_ILalAAnOdt^6BC8tSiKYS z7%6}07AM}yiR>$9FNDIz;GIxR(oluri$oS6WaageL&ys854qyYhFr=3?NP5~N@VCy z*>jTl+i<*?J5w&(7n+R|M^J`8#6d7Q;^2=1wot={K6ZKfBK^e>N=3&^^I&f$CSVON z2OO_3)s!6!T@IHd-p%Yc7hJFOCGH~gka{0aYTHz9!me3<1b4Bx7aIXYmSizj-FKXHx}3oB&fmECK=xF;H;CS|IF3#QL)RVI&`5 z1>{~Q#n#KXO&xDF+2bp-z?-!TuW{fUG8vktd{*Q6i*5<>m?elW$cgmT<5<)ADhxoR zx1^#Y*M^+gvS*(ZbGjTcskWOg-*pt2ot`~7eY*~a6Hk&f&XX^5m6d#wvbk7p&LN#{Rvz(bSmFyw+&_Gl_hd)A%<2pUUXT6&K(dVbYDx3 zPo!5)cIH!Dv>(9%Ukf{*d>gUgklgq=y}ss*{YX$t`@lPmswilku^NjL*9V=-9a@2n z2N>}xWGN@r(lF#fU;=v$89sW zQ`zgpgp_ax>tL9@9&9KSNzAsl==X;aL$Gm z3GS!6@|`d8Gfl%(idREFGtNIF?qlr4nvof6jlJLHv4K31+w_NRk0ZV0ZPh}$Lh`-= zJ%D>WyUI~H>ulwSj2jHw0g@x%D#!6UDQNJDH^Ig9RZd^E^tF<{0=jFT!cLwse|UV# z%CT+%a}ISAwTIPvA5?Pd>?+5C8$-*s&aQAQsngC2x6XDs7dXV|X_SK(+NkQH@C$6B zq3bQs2kctu{s(cWuE#DCwtMKzO*2Wg={Ku2`_RmOn-R z#S`F9uzA(`wX2$gdVSc4vj>090MCc>X0T`XJVFx9*s)`i`=?LeUr*FUxI45!!rNsE8`b~2g!W-|FT@}`$*gy|j;4hd;*FA$G(xrn@P zbY@B?rvFtsIUV5l6L6rG#65>3$rC46N$k-D9QP$nmuJtW-z2;WaLMeS{|HwiY(N-5*h+X6?V%8-kb5Y^(X|DFD@QmH;Yx(1 z6ym-0R)i-IK7){+0xb|scP$o7t@)t8^YtbLFn9>OkP7Y_8hqEwBOjkYkR_) z-z1YO(KaD3zbdbAzBq29rA?T3+3ag)*sdZv%D)1iTFAJ95OsOh#ypGQLNtU=$G;_$ zPf=%AOdd9x4~P)ZZ{rpD7>gtJ0d=!lvgYD+nA6M_3BDI9AH>i$W#IjbrmHRpV}kj9L4U{yfs1JpJA*^jHLa zS-fFvULlwP3l-xalS&p9WYFzFo+HTfQ_@G)2hfpBeb8BT51Nni(zK~^94jjyWlY1k zjihAB%F3Xdwyg4V7FIEi^koIgYQq8VB5DgB!UceP88~{)j-F}wM1dOut^gs%3HrR6 z`n;O@Jo{V7k=hM~CncdW=(l=R*c7}s%AN*%?+32n70upgGRQcl?0L0WZ-H&7Q<@B_ z$7Mf?>dv7YY^+&PPciJ%{R7EloaDoFqdMf?y< zZOXpTly}5*bKX(YGK3X*1E#DEh&X{*iclg%JtEaW+@e$52{ylL5~Oii<>$&;Whg6q zE>%Z`rAqGuLI$D^A*=3vj#$D%)c-sp3pfSia5)gfGL^v)4&-4{9BrZgoz;in+aQY%)^&N7Vnjp0KZJD!y#Zwyugu25;Fo4?(@Y20g4W+Wt{h~{ z4gVhg=1GcQXNq5Fab(o|yq>J=|A6*RsWMo3W_OUC@%S<7wf-fUB!20^br!bRmgM*i>2OW>I1m4}dbCg%OSxqW?r`X|#Id18F_(yR;vg6hUY{X*>~@zL)Z z(?dADzZl~D*MD0A)*>y7{=0!BUwZDNe#4`~^jnAY@XH=8LVrk{9{u+U z@rOd$!+M^tH-EwAR9dA;2E}_s{kH$NQW=(p`Gk z*g%L&JR=@nEY4;%G|wgsaYdqWPY=|WJ(uvXoaO>NI-dQ-cfdN|M_8&La5hu*wNQS{+|c>@5iTCe1lcPRPk^r4}Cnurk6eU z^6)_({+fq7c-YUw!#w(o@DvY6 zU(M_1;Wa$GfrnK*T*^Zq4>#~o%Pj~`|5S@rS61F=odQcb)mk#E2*1K=onAbBR&i;G zz9`c(q=hEHX6#ne&}CZ@twD6 zczw7VQd!ldZDm0tz8pDCB!y`9AHJhLH~&grcV4LO6S*$JPOC+PFCo@IW9 zax(qydus-IB?JCph6n$5Xf>Wj`|0`L96vxWF5uzANq8nhIR`lXO5U!c&;xqt_mD|G zJ(31b*8<@$9N)>yFX#BrGL(Nd13nM+u=2V5+1eRpUC!{lP(xtNP|o$doK7-0c-US6 z;Y}IJS;*;Y>lwMg*3F3Ck%7KB1HLT-zJlo|F&0p7~<8iQOgJRz$vHGk=8mjZvTargZU z_}Nqr+9d?E5&*Jieg^tGGT`YKh|X2-rVRLP3=jF!M207#1;X|W^v`hm+8=0y%r}N; zAMp9YrNTg(-FS%0vzQzBYk2wlGL-*T2K@UO@TYmbJ!$o3XRvz(T;JNR))-;FQbaH1 z_@mseGQT76S7j)tnA30P)jcV;MOJ*O|eRjZZxa}bRHf0dCD z9$!xMS=i-SM<$XUor}+>GT{Aeyo@Y^puPF7uGT_JMken|S^zowa6NPp4jm~FeG@^cw1V4S=-#WdQHgf z_xW%#hl4~MPWl7xhQ?rv5cK=kYO%FAeAQz04GnHOXmmIF+)duq^-V(Rpw=U}?YAzF zYMhmV+l`HY+abHF7E3h_w`^Z5xT{?YD6IH2|h>uM^SaK2erzpBYAxV^27p%K&&zZ-IO`3|%Wn*1#z#lU(V ze7GB%-H3bE3T_{0skKesVVP(WgR*$mf^4muj@nbT&lDQMf$1rbXz65KJ0rIT>o+c@ zU%Aj~LT}-4TW_CdU4zil90)cxhlZO^`8DEoIP=%~S%W?Pz$QJbrnveEKI@%yhEo!O zo@(7!Ke9st^}!Y|jS{VrKx1H}a8d;{`Rg0lx&4Snwgl^&8%AJ!(9V_-7`L1Hfe$lH zL#r1%?LtfA>gM`k5e~PRI*OC~G74t37Z)GuLtbsvarJP!SFiIobB(6a)X?I0uK|lt z#&zCxMtv@sY3QL!9MA^^w{sz~Hb4!+Ezr&34yw{i*eTW7lddjyk*ceLh4Ok+#V}af6=2mfu!-XD(u{x)zB3@CiW-6C7-sR=FT+V8_ zj){et4{xj2(=^fmYAl7Dhes-mcxye`koqPRG@{i$e{iH2G;JC|Gdu@0`8R%N4$xCq zrOgfDmL{)vBxlfN-0B%owoufvXLH8u!Euv6Y(KwyMx>Tz?Ys9{qx3e`dk43@IN8*ITXo)Lf>X+fw1W#F+uQ;150 zP8Wr|xYty~3~y1;&n$P5ca7T@#H|?jng-<6AX~9 z8a?2_kCO3K#H!PZ#%K|+Fj%*awCexHb2$pZk_5gi?Wx83haNpFXVCChYQ4fyw45{M z@8tRQ&^DZv#jsr%@zA!=gpXc7odMIMhx$2yhSg~GIECadz`Yp1o_~Pn*F(L2EQi^X z&i@i%>1;sHub&_2;dY)8%Q?oUo?kylplv)=spr?vBlJ)|e*g)4sIA7w1Au(}dVbx% zuZOLiSm$4-(?i;>(%FFy>*oi0$hWiXe1z)O`RBz7TM(f>(evx)7kYSv6YBNr`StcI zJbwkJ)6YZnP(MGR{FmU9p8p?!Bfdz!?6HmvNk5HGy8iqGiN^d^t46Jd>@JU?0Nrn< zhrdY6ukTCs@aA$uW<=9K;Su1B_3M5$J=D*^^itCKe~jnX<*)l6^e~-&t6mTf|1~YY z?l0Cux+7+M((8XNEq@y~D|)EX;5IyZ|LLf`h#1HJntQYqJ+$hCJWQ|uHQ@8`;lnm{ zKG^dkEkk;JI;@9(1{u6Isr>r+;r_od{~+>`?&RZpRhi8yt%&(t6syu&BTL0KS=B9{KT~-ElG|>G{*gFVWIt3{$$VC9&@#TQy)gKe(lNU;;im p|GNDd1DwvgmP7k4j%(qSY5J?ztJ6}M=gPlen?`YQT81=G_&+6sz3Kn} literal 0 HcmV?d00001 diff --git a/C/opening_learner/run.sh b/C/opening_learner/run.sh new file mode 100755 index 0000000..e88c54c --- /dev/null +++ b/C/opening_learner/run.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" + +# Install SDL2 dev if sdl2-config is missing; otherwise build and run. + +if ! command -v sdl2-config >/dev/null 2>&1; then + echo "sdl2-config not found. Attempting to install SDL2 dev..." + if [ -f /etc/os-release ]; then + . /etc/os-release + case "$ID" in + ubuntu|debian|linuxmint|neon|pop) + sudo apt-get update + sudo apt-get install -y libsdl2-dev + ;; + arch|manjaro|endeavouros) + sudo pacman -Syu --noconfirm sdl2 + ;; + fedora) + sudo dnf install -y SDL2-devel + ;; + opensuse*|sles) + sudo zypper install -y libSDL2-devel + ;; + void) + sudo xbps-install -Sy SDL2-devel + ;; + alpine) + sudo apk add sdl2-dev + ;; + *) + echo "Unsupported distro ($ID). Please install SDL2 dev manually and rerun." >&2 + exit 3 + ;; + esac + else + echo "/etc/os-release not found; cannot auto-detect distro. Install SDL2 dev manually." >&2 + exit 3 + fi +fi + +./check_build.sh +./opening_learner