Move Generation: Castling - micro-Max by H.G. Muller
Moderator: Ras
-
Alexlaw1964
- Posts: 34
- Joined: Fri Jan 03, 2025 6:36 am
- Full name: alex lobov
Re: Move Generation: Castling - micro-Max by H.G. Muller
Something went wrong. The pawn moves on two squares have disappeared.
-
Alexlaw1964
- Posts: 34
- Joined: Fri Jan 03, 2025 6:36 am
- Full name: alex lobov
Re: Move Generation: Castling - micro-Max by H.G. Muller
Все работает, спасибо thomasahle.
Code: Select all
#include <stdio.h>
#define W while
#define WHITE 8
#define BLACK 16
//char *FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w QKqk";
char *FEN = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1 ";//48
//char *FEN = "rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3";//31
//char *FEN = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -";
//char *FEN = "8/2p5/3p3k/1P5r/1R3pP1/1K6/4P3/8 b - g3";
char o[]={-16,-15,-17,0,1,16,0,1,16,15,17,0,14,18,31,33,0, /* step-vector lists */
7,-1,11,6,8,3,6, /* 1st dir. in o[] per piece*/
6,3,5,7,4,5,3,6}; /* initial piece setup */
char n[]=".?+nkbrq?*?NKBRQ"; /* piece symbols on printout*/
char b[129],t,x,y,u,p,H;
int M=136,r;
int K,N,cnt;
int E,S=128,V=112; /*E=e.p. sqr.*/
int ReadFEN(char *FEN){
int row, file, i, col, nr, cc;
char c, *p, epSqr[2];
p = FEN;
for(i=0; i<128; i++) b[i&0x77]=0;
nr=0;
cc=0;
E=0;
for(row=7; row>=0; row--)
{ /* read one row of the FEN */
file = 0;
do{
c = *p++;
if(c>='1' && c<='8') { file += c - '0'; }
else
{
col = WHITE;
cc=8*(7-row)+file+nr;
if(c >= 'a') { c += 'A'-'a'; col = BLACK; }
switch(c)
{
//{1,2,3,4,5,6,7} = {P+,P-,N,K,B,R,Q}
case 'K':
b[cc]=4|col;
break;
case 'R':
b[cc]=6|col;
break;
case 'Q':
b[cc]=7|col;
break;
case 'B':
b[cc]=5|col;
break;
case 'P':
b[cc]=9<<(1&(col>>4));
break;
case 'N':
b[cc]=3|col;
break;
//default: return -15;
}
file++;
}
}while(file < 8);
if(file > 8) return (-10); /* bad format */
if(file == 8)
{ c = *p++;
nr+=8;
if(row > 0 && c != '/') return(-10); /* bad format */
if(row==0 && c != ' ') return (-10); /* bad format */
}
}
while(c = *p++)
{
if(c>='0' && c<='9') continue; /* ignore move counts */
if(c>='a' && c<='h') /* might be e.p. square */
{ if(*p == '3' || *p == '6')
{
epSqr[0]=c;
epSqr[1]=*p;
E=((8 - (epSqr[1] - '0')) * 16) + (epSqr[0] - 'a');
p++;
continue;
}
//else if(c != 'b') continue;
}
switch(c)
{
case 'K':
case 'Q':
case 'k':
case 'g':b[7*(c&34^32)/2] |= c&64;break;
case 'w': col = WHITE; break;
case 'b': col = BLACK; break;
case ' ':
case '-': break;
//default: return -10;
}
}
return col;
}
int main()
{
int j,k=8;//k=8; белые k=16; черные
K=8;W(K--)
{
b[K]=(b[K+112]=o[K+24]+8)+8;
b[K+16]=18;
b[K+96]=9; /* initial board setup*/
}
k=ReadFEN(FEN);
W(1)
{
N=-1;W(++N<121)
printf(" %c",N&8&&(N+=7)?10:n[b[N]&15]); /* print board */
W((getchar())>10); /* read input line */
x=0;cnt=0;
do{
u=b[x]; /* scan board looking for */
if(u&k) /* own piece (inefficient!)*/
{r=p=u&7; /* p = piece type (set r>0) */
j=o[p+16]; /* first step vector f.piece*/
while(r=p>2&r<0?-r:-o[++j]) /* loop over directions o[] */
{
y=x;
do{
H=y+=r; /* y traverses ray */
if(y&M)break; /* board edge hit */
if(p<3&y==E)H=y^16; /* shift capt.sqr. H if e.p.*/
t=b[H];
if(t&k|p<3&!(r&7)!=!t)break; /* capt. own, bad pawn mode */
cnt++;
printf("%c%c%c%c\n",
'a' + (x % 16),
'0' + (8 - (x / 16)),
'a' + (y % 16),
'0' + (8 - (y / 16))
);
t += p<5
^ p<3 & 6*k+(y&V)==S
^ j<8 & y
& b[H=x^3^r>>1&7]==u+66
& !b[H^1] & !b[H^2];
}while(!t); /* if not capt. continue ray*/
}
}}W(x=x+9&~M);/* next sqr. of board, wrap */
printf("cnt=%d\n",cnt);
}
}
-
hgm
- Posts: 28540
- Joined: Fri Mar 10, 2006 10:06 am
- Location: Amsterdam
- Full name: H G Muller
Re: Move Generation: Castling - micro-Max by H.G. Muller
Well, the code by Thomas is not what there originally was in the micro-Max code, and he might have simplified it too much, or just made a typo in posting it here.
-
Alexlaw1964
- Posts: 34
- Joined: Fri Jan 03, 2025 6:36 am
- Full name: alex lobov
Re: Move Generation: Castling - micro-Max by H.G. Muller
The code written by Thomas works correctly.Alexlaw1964 wrote: ↑Sun Aug 23, 2026 5:10 am Все работает, спасибо thomasahle.Code: Select all
#include <stdio.h> #define W while #define WHITE 8 #define BLACK 16 //char *FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w QKqk"; char *FEN = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1 ";//48 //char *FEN = "rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3";//31 //char *FEN = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -"; //char *FEN = "8/2p5/3p3k/1P5r/1R3pP1/1K6/4P3/8 b - g3"; char o[]={-16,-15,-17,0,1,16,0,1,16,15,17,0,14,18,31,33,0, /* step-vector lists */ 7,-1,11,6,8,3,6, /* 1st dir. in o[] per piece*/ 6,3,5,7,4,5,3,6}; /* initial piece setup */ char n[]=".?+nkbrq?*?NKBRQ"; /* piece symbols on printout*/ char b[129],t,x,y,u,p,H; int M=136,r; int K,N,cnt; int E,S=128,V=112; /*E=e.p. sqr.*/ int ReadFEN(char *FEN){ int row, file, i, col, nr, cc; char c, *p, epSqr[2]; p = FEN; for(i=0; i<128; i++) b[i&0x77]=0; nr=0; cc=0; E=0; for(row=7; row>=0; row--) { /* read one row of the FEN */ file = 0; do{ c = *p++; if(c>='1' && c<='8') { file += c - '0'; } else { col = WHITE; cc=8*(7-row)+file+nr; if(c >= 'a') { c += 'A'-'a'; col = BLACK; } switch(c) { //{1,2,3,4,5,6,7} = {P+,P-,N,K,B,R,Q} case 'K': b[cc]=4|col; break; case 'R': b[cc]=6|col; break; case 'Q': b[cc]=7|col; break; case 'B': b[cc]=5|col; break; case 'P': b[cc]=9<<(1&(col>>4)); break; case 'N': b[cc]=3|col; break; //default: return -15; } file++; } }while(file < 8); if(file > 8) return (-10); /* bad format */ if(file == 8) { c = *p++; nr+=8; if(row > 0 && c != '/') return(-10); /* bad format */ if(row==0 && c != ' ') return (-10); /* bad format */ } } while(c = *p++) { if(c>='0' && c<='9') continue; /* ignore move counts */ if(c>='a' && c<='h') /* might be e.p. square */ { if(*p == '3' || *p == '6') { epSqr[0]=c; epSqr[1]=*p; E=((8 - (epSqr[1] - '0')) * 16) + (epSqr[0] - 'a'); p++; continue; } //else if(c != 'b') continue; } switch(c) { case 'K': case 'Q': case 'k': case 'g':b[7*(c&34^32)/2] |= c&64;break; case 'w': col = WHITE; break; case 'b': col = BLACK; break; case ' ': case '-': break; //default: return -10; } } return col; } int main() { int j,k=8;//k=8; белые k=16; черные K=8;W(K--) { b[K]=(b[K+112]=o[K+24]+8)+8; b[K+16]=18; b[K+96]=9; /* initial board setup*/ } k=ReadFEN(FEN); W(1) { N=-1;W(++N<121) printf(" %c",N&8&&(N+=7)?10:n[b[N]&15]); /* print board */ W((getchar())>10); /* read input line */ x=0;cnt=0; do{ u=b[x]; /* scan board looking for */ if(u&k) /* own piece (inefficient!)*/ {r=p=u&7; /* p = piece type (set r>0) */ j=o[p+16]; /* first step vector f.piece*/ while(r=p>2&r<0?-r:-o[++j]) /* loop over directions o[] */ { y=x; do{ H=y+=r; /* y traverses ray */ if(y&M)break; /* board edge hit */ if(p<3&y==E)H=y^16; /* shift capt.sqr. H if e.p.*/ t=b[H]; if(t&k|p<3&!(r&7)!=!t)break; /* capt. own, bad pawn mode */ cnt++; printf("%c%c%c%c\n", 'a' + (x % 16), '0' + (8 - (x / 16)), 'a' + (y % 16), '0' + (8 - (y / 16)) ); t += p<5 ^ p<3 & 6*k+(y&V)==S ^ j<8 & y & b[H=x^3^r>>1&7]==u+66 & !b[H^1] & !b[H^2]; }while(!t); /* if not capt. continue ray*/ } }}W(x=x+9&~M);/* next sqr. of board, wrap */ printf("cnt=%d\n",cnt); } }
What’s surprising is something else.
If I use this code, it doesn’t fit in the Attiny4313.
If I use my own version, it even fits in the Attiny2313.
-
Alexlaw1964
- Posts: 34
- Joined: Fri Jan 03, 2025 6:36 am
- Full name: alex lobov
Re: Move Generation: Castling - micro-Max by H.G. Muller
Reverse conversion from a position to FEN.
I’m a bit confused.
How does the color of “w” and “b” change when you enter “enter” and when you enter a move?
And also, please help with the castling again.
I’m a bit confused.
How does the color of “w” and “b” change when you enter “enter” and when you enter a move?
And also, please help with the castling again.
Code: Select all
//n[]=".?+nkbrq?*?NKBRQ";
//char fen[128];
//int M=136,S=128,I=8e3,C=799,Q,O,K,N,R,J,Z; /* M=0x88 */
void board_to_fen() {
char *ptr = fen;
char piece;
int emptyCount = 0;
N=-1;W(++N<121) {
piece = N&8&&(N+=7)?10:n[b[N]&15];
if (ptr >= fen + 86){*ptr = '\0';return;}
if (piece == '?')continue;
switch (piece) {
case 10: // Moving to the next horizontal
if (emptyCount > 0) {
*ptr++ = emptyCount + '0';
emptyCount = 0;
}
if (N < 112) {
*ptr++ = '/';
}
break;
case '.': // Empty field
emptyCount++;
break;
case '+': // Black pawn
if (emptyCount > 0) {
*ptr++ = emptyCount + '0';
emptyCount = 0;
}
*ptr++ = 'p';
break;
case '*': // White pawn
if (emptyCount > 0) {
*ptr++ = emptyCount + '0';
emptyCount = 0;
}
*ptr++ = 'P';
break;
default: // The other figures
if (emptyCount > 0) {
*ptr++ = emptyCount + '0';
emptyCount = 0;
}
*ptr++ = piece;
break;
}
}
if (k==8)ptr += sprintf(ptr, " w "); else ptr +=sprintf(ptr, " b ");
ptr += sprintf(ptr, "- ");
if (O!=128){
*ptr++ = 'a'+(O&7);
*ptr++ = '8'-(O>>4);
*ptr++ = ' ';}
else ptr +=sprintf(ptr, "- ");
// Adding the remaining FEN fields
ptr += sprintf(ptr, "0 1");
// End the line with a zero
*ptr = '\0';
printf("%s",fen);
}-
Alexlaw1964
- Posts: 34
- Joined: Fri Jan 03, 2025 6:36 am
- Full name: alex lobov
Re: Move Generation: Castling - micro-Max by H.G. Muller
Using AI, I added a book of openings.
https://brewpage.app/public/cyzEXQVI4b?p=BwDG
https://brewpage.app/public/cyzEXQVI4b?p=BwDG
-
Alexlaw1964
- Posts: 34
- Joined: Fri Jan 03, 2025 6:36 am
- Full name: alex lobov
Re: Move Generation: Castling - micro-Max by H.G. Muller
I have compiled the book. The first 5 moves in accordance with the book.
https://brewpage.app/public/5LXuLk7i5E
stockfish_windowsVSmicro_Max_STM32
[pgn][Event "Computer Schach Partie"]
[Site "LENOVO-PC-LAW"]
[Date "????.??.??"]
[Round "?"]
[White "stockfish 15.1"]
[Black "stm32"]
[Result "1-0"]
[WhiteElo "?"]
[BlackElo "?"]
[Variant "Standard"]
[TimeControl "-"]
[ECO "B50"]
[Opening "Sicilian Defense: Modern Variations"]
[Termination "Normal"]
[Annotator "lichess.org"]
1. e4 c5 2. Nf3 d6 { B50 Sicilian Defense: Modern Variations } 3. Nc3 e5 4. a4 Be7 5. Bc4 Nf6 6. Ng5 O-O 7. a5 Nc6 8. a6 bxa6 9. Nd5 Nxd5 10. Bxd5 Qb6 11. h4 Bxg5 12. hxg5 g6 13. b3 Be6 14. Bxc6 Qxc6 15. d3 f5 16. gxf6 Rxf6 17. Bh6 Rf7 18. Qd2 Rb8 19. Qe3 Ra8 20. Qd2 Rb8 21. Qc3 Rbb7 22. Ke2 Bg4+ 23. Ke3 Be6 24. Qa5 Rb6 25. f3 d5 26. Qd2 d4+ 27. Kf2 Qd6 28. Qg5 Re7 29. Ra5 Re8 30. Qf6 Rb7 31. Rha1 Rf7 32. Rxa6 Qxa6 33. Qxf7+ Bxf7 34. Rxa6 Re7 35. Bg5 Rd7 36. Bf6 Kf8 37. Rc6 Bxb3 38. cxb3 Rb7 39. Rxc5 Rxb3 40. Ra5 Rxd3 41. Ra2 Kf7 42. Bxe5 Ke6 43. Bf4 Rb3 44. Rxa7 Rb2+ 45. Kg1 h5 46. Ra4 Rb1+ 47. Kh2 Rd1 48. Ra2 Rb1 49. Ra7 Rd1 50. Ra8 Rb1 51. Ra5 Rd1 52. Rb5 Ra1 53. Rd5 Rd1 54. Rd6+ Kf7 55. Kg3 d3 56. Kf2 Kg7 57. Bg5 Kf7 58. e5 Kg7 59. e6 Kf8 60. Bf6 Rd2+ 61. Kg3 h4+ 62. Kf4 g5+ 63. Kf5 Kg8 64. Kg6 Rxg2 65. e7 Re2 66. Rd8# { White wins by checkmate. } 1-0[/pgn]
https://brewpage.app/public/5LXuLk7i5E
stockfish_windowsVSmicro_Max_STM32
[pgn][Event "Computer Schach Partie"]
[Site "LENOVO-PC-LAW"]
[Date "????.??.??"]
[Round "?"]
[White "stockfish 15.1"]
[Black "stm32"]
[Result "1-0"]
[WhiteElo "?"]
[BlackElo "?"]
[Variant "Standard"]
[TimeControl "-"]
[ECO "B50"]
[Opening "Sicilian Defense: Modern Variations"]
[Termination "Normal"]
[Annotator "lichess.org"]
1. e4 c5 2. Nf3 d6 { B50 Sicilian Defense: Modern Variations } 3. Nc3 e5 4. a4 Be7 5. Bc4 Nf6 6. Ng5 O-O 7. a5 Nc6 8. a6 bxa6 9. Nd5 Nxd5 10. Bxd5 Qb6 11. h4 Bxg5 12. hxg5 g6 13. b3 Be6 14. Bxc6 Qxc6 15. d3 f5 16. gxf6 Rxf6 17. Bh6 Rf7 18. Qd2 Rb8 19. Qe3 Ra8 20. Qd2 Rb8 21. Qc3 Rbb7 22. Ke2 Bg4+ 23. Ke3 Be6 24. Qa5 Rb6 25. f3 d5 26. Qd2 d4+ 27. Kf2 Qd6 28. Qg5 Re7 29. Ra5 Re8 30. Qf6 Rb7 31. Rha1 Rf7 32. Rxa6 Qxa6 33. Qxf7+ Bxf7 34. Rxa6 Re7 35. Bg5 Rd7 36. Bf6 Kf8 37. Rc6 Bxb3 38. cxb3 Rb7 39. Rxc5 Rxb3 40. Ra5 Rxd3 41. Ra2 Kf7 42. Bxe5 Ke6 43. Bf4 Rb3 44. Rxa7 Rb2+ 45. Kg1 h5 46. Ra4 Rb1+ 47. Kh2 Rd1 48. Ra2 Rb1 49. Ra7 Rd1 50. Ra8 Rb1 51. Ra5 Rd1 52. Rb5 Ra1 53. Rd5 Rd1 54. Rd6+ Kf7 55. Kg3 d3 56. Kf2 Kg7 57. Bg5 Kf7 58. e5 Kg7 59. e6 Kf8 60. Bf6 Rd2+ 61. Kg3 h4+ 62. Kf4 g5+ 63. Kf5 Kg8 64. Kg6 Rxg2 65. e7 Re2 66. Rd8# { White wins by checkmate. } 1-0[/pgn]
-
Alexlaw1964
- Posts: 34
- Joined: Fri Jan 03, 2025 6:36 am
- Full name: alex lobov
Re: Move Generation: Castling - micro-Max by H.G. Muller
chess_book_builder.py
Code: Select all
# chess_book_builder.py v3.9
# Генератор дебютной книги для Micro-Max на STM32
# v3.9: depth 14 (black) / 12 (white), multipv 5, таймер с прогнозом
import chess
import chess.engine
import random
import re
import os
import json
import glob
import sys
import time
STOCKFISH_PATH = r"C:\telnet_server\stockfish-windows-2022-x86-64-modern.exe"
DEPTH_BLACK = 14
DEPTH_WHITE = 12
BOOK_SIZE = 1500
SEED = 42
ZOBRIST_HEADER = "zobrist_table.h"
THRESHOLD_CP = 40
random.seed(SEED)
def find_zobrist_file():
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
search_dirs = [script_dir, os.getcwd()]
for d in search_dirs:
p = os.path.join(d, ZOBRIST_HEADER)
if os.path.exists(p):
return p
for sub in glob.glob(os.path.join(d, "**", ZOBRIST_HEADER), recursive=True):
if os.path.exists(sub):
return sub
return None
def find_stockfish():
if os.path.exists(STOCKFISH_PATH):
return STOCKFISH_PATH
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
search_dirs = [script_dir, os.getcwd(), os.path.dirname(script_dir)]
for d in search_dirs:
for pattern in ["stockfish*.exe", "Stockfish*.exe"]:
matches = glob.glob(os.path.join(d, pattern))
if matches:
return matches[0]
for sub in glob.glob(os.path.join(d, "**", pattern), recursive=True):
if os.path.exists(sub):
return sub
return None
def load_zobrist_from_header(filename):
if not os.path.exists(filename):
print(f"ОШИБКА: файл {filename} не найден!")
sys.exit(1)
with open(filename, "r") as f:
content = f.read()
numbers = re.findall(r'0x[0-9A-Fa-f]+ULL', content)
return [int(n.replace('ULL', ''), 16) for n in numbers]
MM_TO_STD = [0, 1, 1, 2, 6, 3, 4, 5]
def get_mm_type(piece):
if piece.piece_type == chess.PAWN:
return 2 if piece.color else 1
elif piece.piece_type == chess.KNIGHT:
return 3
elif piece.piece_type == chess.BISHOP:
return 5
elif piece.piece_type == chess.ROOK:
return 6
elif piece.piece_type == chess.QUEEN:
return 7
elif piece.piece_type == chess.KING:
return 4
return 0
def zobrist_key_stm32(board, ZT):
key = 0
for sq in chess.SQUARES:
piece = board.piece_at(sq)
if piece is None:
continue
mm_type = get_mm_type(piece)
std_type = MM_TO_STD[mm_type]
color = 0 if piece.color else 1
file_ = chess.square_file(sq)
rank_ = chess.square_rank(sq)
sq063 = rank_ * 8 + file_
z_index = color * 384 + (std_type - 1) * 64 + sq063
key ^= ZT[z_index]
if not board.turn:
key ^= ZT[768]
if board.castling_rights & chess.BB_H1:
key ^= ZT[769]
if board.castling_rights & chess.BB_A1:
key ^= ZT[770]
if board.castling_rights & chess.BB_H8:
key ^= ZT[771]
if board.castling_rights & chess.BB_A8:
key ^= ZT[772]
if board.ep_square is not None:
key ^= ZT[773 + chess.square_file(board.ep_square)]
return key & 0xFFFFFFFF
def move_to_polyglot(move):
file_from = chess.square_file(move.from_square)
rank_from = chess.square_rank(move.from_square)
file_to = chess.square_file(move.to_square)
rank_to = chess.square_rank(move.to_square)
sq063_from = rank_from * 8 + file_from
sq063_to = rank_to * 8 + file_to
return (sq063_from & 0x3F) | ((sq063_to & 0x3F) << 6)
def build_board(moves):
b = chess.Board()
for m in moves:
if isinstance(m, str):
b.push(chess.Move.from_uci(m))
else:
b.push(m)
return b
def best_black_move(board, engine):
"""Один лучший ход чёрных через engine.play()"""
result = engine.play(board, chess.engine.Limit(depth=DEPTH_BLACK))
return result.move
def filtered_white_moves(board, engine):
"""Ходы белых в пределах THRESHOLD_CP от лучшего, multipv=5"""
results = engine.analyse(board, chess.engine.Limit(depth=DEPTH_WHITE), multipv=5)
if not results:
return []
best_score = results[0]["score"].white().score()
if best_score is None:
best_score = 0
good = []
for r in results:
s = r["score"].white().score()
if s is None:
s = 0
if abs(s - best_score) <= THRESHOLD_CP:
good.append(r["pv"][0])
return good
def san_line(moves):
b = chess.Board()
parts = []
for i, m in enumerate(moves):
if isinstance(m, str):
m = chess.Move.from_uci(m)
if i % 2 == 0:
parts.append(f"{i // 2 + 1}.")
parts.append(b.san(m))
b.push(m)
return " ".join(parts)
def fmt_time(sec):
if sec < 60:
return f"{sec:.0f}с"
m = int(sec // 60)
s = sec % 60
return f"{m}мин {s:.0f}с"
def generate_book(engine, ZT):
book = []
lines = []
seen_keys = set()
t0 = time.time()
print("=" * 60)
print(f" chess_book_builder.py v3.9")
print(f" Размер: {BOOK_SIZE} позиций")
print(f" Глубина: чёрные={DEPTH_BLACK}, белые={DEPTH_WHITE}")
print(f" Фильтр белых: multipv=5, порог {THRESHOLD_CP}cp")
print(f" Логика: белые расширяют дерево, чёрные = 1 лучший ход")
print(f" Дебюты: e4, d4")
print("=" * 60)
print()
def add_position(board, move_list, ply_label):
if board.is_game_over():
return False
if len(book) >= BOOK_SIZE:
return False
key = zobrist_key_stm32(board, ZT)
if key in seen_keys:
return False
seen_keys.add(key)
# Один лучший ход чёрных
best_move = best_black_move(board, engine)
uci = best_move.uci()
san = board.san(best_move)
pg_move = move_to_polyglot(best_move)
book.append((key, uci, san, pg_move, 1))
full_line = move_list + [uci]
line_name = san_line(full_line) + f" [0x{key:08X}]"
lines.append((line_name, full_line, key))
if len(book) % 50 == 0:
elapsed = time.time() - t0
print(f" [{len(book):4d}/{BOOK_SIZE}] {ply_label} | {san} -> 0x{pg_move:04X} | {fmt_time(elapsed)}")
return True
# Очередь позиций для обработки: (board, move_list, ply)
# Позиция = после хода белых, очередь хода чёрных
from collections import deque
queue = deque()
# === ЭТАП 1: ply 1 — стартовые ходы белых ===
print("Этап 1: стартовые ходы белых (ply 1)...")
main_openings = ["e2e4", "d2d4"]
for m1_uci in main_openings:
b = chess.Board()
b.push(chess.Move.from_uci(m1_uci))
queue.append((b, [m1_uci], 1))
# Обрабатываем очередь: для каждой позиции — лучший ход чёрных в книгу,
# затем ходы белых для следующего этапа
stage = 1
max_ply = 11
while queue and len(book) < BOOK_SIZE:
stage_positions = len(queue)
stage_start = time.time()
next_queue = deque()
while queue and len(book) < BOOK_SIZE:
board, move_list, ply = queue.popleft()
added = add_position(board, move_list, f"ply {ply}")
if not added:
continue
# Делаем лучший ход чёрных
key = zobrist_key_stm32(board, ZT)
# Находим запись в книге
entry = None
for e in book:
if e[0] == key:
entry = e
break
if entry is None:
continue
black_uci = entry[1]
b2 = build_board(move_list + [black_uci])
# Получаем ходы белых для расширения
if ply < max_ply:
w_moves = filtered_white_moves(b2, engine)
for wm in w_moves:
wm_uci = wm.uci()
b3 = build_board(move_list + [black_uci, wm_uci])
next_queue.append((b3, move_list + [black_uci, wm_uci], ply + 2))
stage_time = time.time() - stage_start
total_time = time.time() - t0
done = len(book)
remaining = BOOK_SIZE - done
if stage_positions > 0 and done > 0:
sec_per_pos = total_time / done
forecast = sec_per_pos * remaining
print(f" После этапа {stage} (ply {2*stage-1}): {done} поз. | {fmt_time(stage_time)} | всего {fmt_time(total_time)} | прогноз: ~{fmt_time(forecast)} осталось")
else:
print(f" После этапа {stage} (ply {2*stage-1}): {done} поз. | {fmt_time(stage_time)}")
print()
queue = next_queue
stage += 1
engine.quit()
# Статистика
print("=" * 60)
print(f" Готово! Позиций: {len(book)}")
print(f" Время: {fmt_time(time.time() - t0)}")
print(f" Размер во Flash: ~{len(book) * 7} байт ({len(book) * 7 / 1024:.1f} КБ)")
ply_counts = {}
for ln in lines:
num_moves = len(ln[1])
ply = num_moves - 1
ply_counts[ply] = ply_counts.get(ply, 0) + 1
print(f"\n Статистика по этапам:")
for ply in sorted(ply_counts.keys()):
print(f" ply {ply}: {ply_counts[ply]} позиций")
print("=" * 60)
return book, lines
def write_debut_book_c(book, filename="debut_book.c"):
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
filepath = os.path.join(script_dir, filename)
book.sort(key=lambda x: x[0])
with open(filepath, "w") as f:
f.write('#include "polyglot.h"\n\n')
f.write("const polyglot_entry_t debut_book[] = {\n")
for entry in book:
key = entry[0]
pg = entry[3]
weight = entry[4]
f.write(f"\t{{0x{key:08X}U, 0x{pg:04X}U, {weight}}},\n")
f.write("};\n\n")
f.write(f"const int debut_book_size = {len(book)};\n")
print(f"\nФайл {filepath} записан ({len(book)} записей, отсортировано)")
def write_json_session(book, lines, filename="book_session.json"):
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
filepath = os.path.join(script_dir, filename)
book_entries = []
for entry in book:
book_entries.append([int(entry[0]), entry[1], entry[2], int(entry[3]), int(entry[4])])
json_lines = []
for ln in lines:
json_lines.append([ln[0], ln[1], int(ln[2])])
session = {
"version": "3.9",
"book": book_entries,
"lines": json_lines
}
with open(filepath, "w", encoding="utf-8") as f:
json.dump(session, f, ensure_ascii=False, indent=2)
if book_entries:
first_key = book_entries[0][0]
print(f"[DEBUG] Первый ключ в JSON: 0x{first_key:08X} (int={first_key})")
if lines and book_entries:
line_key = int(lines[0][2])
found = any(be[0] == line_key for be in book_entries)
if found:
print(f"[OK] Хэш из первой линии 0x{line_key:08X} найден в книге")
else:
print(f"[WARN] Хэш из первой линии 0x{line_key:08X} НЕ найден в книге")
print(f"Файл {filepath} записан ({len(book_entries)} записей, {len(json_lines)} линий)")
def main():
ZT_path = find_zobrist_file()
if ZT_path is None:
print("ОШИБКА: файл zobrist_table.h не найден!")
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
print(f" Искали в: {script_dir}")
print(f" Искали в: {os.getcwd()}")
sys.exit(1)
ZT = load_zobrist_from_header(ZT_path)
print(f"Загружено {len(ZT)} Zobrist-чисел из {ZT_path}")
sf_path = find_stockfish()
if sf_path is None:
print("ОШИБКА: Stockfish не найден!")
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
print(f" Искали в: {script_dir}")
print(f" Искали в: {os.getcwd()}")
print(f" Жёсткий путь: {STOCKFISH_PATH}")
sys.exit(1)
print(f"Stockfish: {sf_path}")
engine = chess.engine.SimpleEngine.popen_uci(sf_path)
print(f"Stockfish запущен\n")
book, lines = generate_book(engine, ZT)
write_debut_book_c(book)
write_json_session(book, lines)
if __name__ == "__main__":
main()
-
Alexlaw1964
- Posts: 34
- Joined: Fri Jan 03, 2025 6:36 am
- Full name: alex lobov
Re: Move Generation: Castling - micro-Max by H.G. Muller
chess_book_editor.py
Code: Select all
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
chess_book_editor.py — Ручной редактор дебютной книги для STM32
Версия: 4.9
GUI: tkinter, доска с шрифтом gc2004d, тёмная тема
Локализация: chess_book_editor_lang.py
"""
import os
import sys
import json
import re
import tkinter as tk
from tkinter import font as tkfont
from tkinter import filedialog, messagebox, ttk
import chess
import chess.pgn
try:
from chess_book_editor_lang import LANG, tr, set_lang, get_lang
except ImportError:
LANG = {}
def tr(key, default=None):
return default or key
def set_lang(l):
pass
def get_lang():
return 'ru'
# ====================== ZOBRIST ======================
ZOBRIST_NUMBERS = []
ZOBRIST_TABLE_H = ""
def load_zobrist_table(path):
global ZOBRIST_NUMBERS, ZOBRIST_TABLE_H
numbers = []
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
ZOBRIST_TABLE_H = content
hex_vals = re.findall(r'0x[0-9A-Fa-f]{8}', content)
for h in hex_vals:
numbers.append(int(h, 16))
if len(numbers) < 100:
print(f"[WARN] Only {len(numbers)} Zobrist numbers found")
except Exception as e:
print(f"[ERROR] Cannot load zobrist_table.h: {e}")
numbers = []
ZOBRIST_NUMBERS = numbers
return numbers
def find_zobrist_file():
# Search in current dir
cwd = os.getcwd()
for name in ["zobrist_table.h", "zobrist.h"]:
p = os.path.join(cwd, name)
if os.path.exists(p):
return p
# Search in subdirs of current dir
try:
for d in os.listdir(cwd):
full = os.path.join(cwd, d)
if os.path.isdir(full):
for name in ["zobrist_table.h", "zobrist.h"]:
p = os.path.join(full, name)
if os.path.exists(p):
return p
except:
pass
# Search in script dir
script_dir = os.path.dirname(os.path.abspath(__file__))
for name in ["zobrist_table.h", "zobrist.h"]:
p = os.path.join(script_dir, name)
if os.path.exists(p):
return p
# Search in subdirs of script dir
try:
for d in os.listdir(script_dir):
full = os.path.join(script_dir, d)
if os.path.isdir(full):
for name in ["zobrist_table.h", "zobrist.h"]:
p = os.path.join(full, name)
if os.path.exists(p):
return p
except:
pass
return None
def compute_zobrist(board):
if not ZOBRIST_NUMBERS:
return 0
key = 0
for sq in range(64):
piece = board.piece_at(sq)
if piece is not None:
piece_type = piece.piece_type
color = piece.color
piece_idx = (piece_type - 1) * 2 + (0 if color else 1)
zob_idx = piece_idx * 64 + sq
if zob_idx < len(ZOBRIST_NUMBERS):
key ^= ZOBRIST_NUMBERS[zob_idx]
if board.turn == chess.BLACK:
key ^= ZOBRIST_NUMBERS[-1] if len(ZOBRIST_NUMBERS) > 0 else 0
castling_idx = len(ZOBRIST_NUMBERS) - 5
if castling_idx + 3 < len(ZOBRIST_NUMBERS):
if board.has_kingside_castling_rights(chess.WHITE):
key ^= ZOBRIST_NUMBERS[castling_idx]
if board.has_queenside_castling_rights(chess.WHITE):
key ^= ZOBRIST_NUMBERS[castling_idx + 1]
if board.has_kingside_castling_rights(chess.BLACK):
key ^= ZOBRIST_NUMBERS[castling_idx + 2]
if board.has_queenside_castling_rights(chess.BLACK):
key ^= ZOBRIST_NUMBERS[castling_idx + 3]
return key & 0xFFFFFFFF
def move_to_polyglot(move, board):
from_sq = move.from_square
to_sq = move.to_square
ff = chess.square_file(from_sq)
fr = chess.square_rank(from_sq)
tf = chess.square_file(to_sq)
tr = chess.square_rank(to_sq)
pg = (fr << 9) | (ff << 6) | (tr << 3) | tf
if move.promotion:
promo_map = {chess.QUEEN: 0, chess.ROOK: 1, chess.BISHOP: 2, chess.KNIGHT: 3}
pg |= (promo_map.get(move.promotion, 0) << 12)
return pg & 0xFFFF
# ====================== HELPERS ======================
def _safe_key(val):
"""Convert any key format to int."""
if isinstance(val, int):
return val
if isinstance(val, float):
return int(val)
if isinstance(val, str):
s = val.strip()
if s.startswith("0x") or s.startswith("0X"):
return int(s, 16)
return int(s)
return 0
def _safe_int(val):
"""Convert any int-like value to int."""
if isinstance(val, int):
return val
if isinstance(val, float):
return int(val)
if isinstance(val, str):
s = val.strip()
if s.startswith("0x") or s.startswith("0X"):
return int(s, 16)
return int(s)
return 0
# ====================== ВИЗУАЛИЗАЦИЯ ДОСКИ (gc2004d) ======================
def board_to_visual_chars(board):
f = {0: 163, 1: 48, 2: 71, 3: 87, 4: 109, 5: 169, 6: 185}
piece_map = {'K': 1, 'Q': 2, 'R': 3, 'B': 4, 'N': 5, 'P': 6}
chars = []
n = 0
for rank in range(7, -1, -1):
for file_idx in range(8):
piece = board.piece_at(chess.square(file_idx, rank))
if piece is None:
chars.append(chr(f[0] + n))
elif piece.symbol().islower():
chars.append(chr(f[piece_map[piece.symbol().upper()]] + n + 2))
else:
chars.append(chr(f[piece_map[piece.symbol().upper()]] + n))
n = 1 - n
n = 1 - n
return chars
# ====================== GUI ======================
CELL_SIZE = 40
LIGHT_COLOR = "#f0d9b5"
DARK_COLOR = "#b58863"
BG_COLOR = "#2b2b2b"
PANEL_COLOR = "#3c3f41"
ACCENT_COLOR = "#4a9eff"
TEXT_COLOR = "#e0e0e0"
class ChessBookEditor:
def __init__(self, root):
self.root = root
root.title(tr('title', 'Chess Book Editor v4.9 - STM32'))
root.geometry("1200x720")
root.configure(bg=BG_COLOR)
self.board = chess.Board()
self.book = []
self.lines = []
self.current_line_moves = []
self.selected_square = None
self.last_move = None
self.auto_add = tk.BooleanVar(value=True)
self.search_var = tk.StringVar()
self.search_results = []
self.search_idx = 0
self.lines_search_var = tk.StringVar()
self.lines_search_results = []
self.lines_search_idx = 0
# PGN playback state
self.pgn_moves = []
self.pgn_index = 0
self.pgn_game = None
# Create font once
try:
self.piece_font = tkfont.Font(family="gc2004d", size=28)
except tk.TclError:
try:
self.piece_font = tkfont.Font(family="DejaVu Sans", size=28)
except tk.TclError:
self.piece_font = tkfont.Font(family="Arial", size=24)
# Widget references for language switching
self.widgets = {}
style = ttk.Style()
style.theme_use('clam')
style.configure('Dark.TFrame', background=BG_COLOR)
style.configure('Dark.TLabel', background=BG_COLOR, foreground=TEXT_COLOR, font=('Arial', 10))
style.configure("Treeview", background=PANEL_COLOR, foreground=TEXT_COLOR, fieldbackground=PANEL_COLOR)
style.configure("Treeview.Heading", background=PANEL_COLOR, foreground=TEXT_COLOR)
self._build_ui()
self._draw_board()
def _build_ui(self):
# Destroy existing widgets
for w in self.root.winfo_children():
w.destroy()
LEFT_WIDTH = 340
left = tk.Frame(self.root, bg=BG_COLOR, width=LEFT_WIDTH)
left.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
left.pack_propagate(False)
# Language selector
lang_frame = tk.Frame(left, bg=BG_COLOR)
lang_frame.pack(fill=tk.X, pady=(0, 5))
tk.Label(lang_frame, text=tr('language', 'Language:'), bg=BG_COLOR, fg=TEXT_COLOR,
font=('Arial', 9)).pack(side=tk.LEFT)
self.lang_var = tk.StringVar(value=get_lang())
lang_cb = tk.OptionMenu(lang_frame, self.lang_var, 'ru', 'en',
command=self._switch_lang)
lang_cb.config(bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT)
lang_cb.pack(side=tk.LEFT, padx=5)
board_frame = tk.Frame(left, bg=BG_COLOR)
board_frame.pack()
coords_left = tk.Canvas(board_frame, width=15, height=CELL_SIZE * 8,
bg=BG_COLOR, highlightthickness=0)
coords_left.pack(side=tk.LEFT)
for i in range(8):
y = i * CELL_SIZE + CELL_SIZE // 2
coords_left.create_text(7, y, text=str(8 - i), fill=TEXT_COLOR, font=('Arial', 9))
self.canvas = tk.Canvas(board_frame, width=CELL_SIZE * 8, height=CELL_SIZE * 8,
bg=LIGHT_COLOR, highlightthickness=0)
self.canvas.pack(side=tk.LEFT)
self.canvas.bind("<Button-1>", self._on_click)
coords_bottom = tk.Canvas(left, width=CELL_SIZE * 8, height=15,
bg=BG_COLOR, highlightthickness=0)
coords_bottom.pack()
for i in range(8):
x = i * CELL_SIZE + CELL_SIZE // 2
coords_bottom.create_text(x, 7, text=chr(ord('a') + i),
fill=TEXT_COLOR, font=('Arial', 9))
btn_frame = tk.Frame(left, bg=BG_COLOR)
btn_frame.pack(fill=tk.X, pady=5)
self.widgets['btn_new_line'] = tk.Button(btn_frame, text=tr('new_line', 'New line'),
command=self._new_line, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9, 'bold'), relief=tk.FLAT)
self.widgets['btn_new_line'].pack(fill=tk.X, pady=2)
self.widgets['btn_undo'] = tk.Button(btn_frame, text=tr('undo', 'Undo move'),
command=self._undo_move, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT)
self.widgets['btn_undo'].pack(fill=tk.X, pady=2)
self.widgets['btn_add'] = tk.Button(btn_frame, text=tr('add_to_book', '+ Add to book'),
command=self._manual_add, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT)
self.widgets['btn_add'].pack(fill=tk.X, pady=2)
self.widgets['chk_auto'] = tk.Checkbutton(btn_frame, text=tr('auto_add', 'Auto-add'),
variable=self.auto_add, bg=BG_COLOR, fg=TEXT_COLOR, selectcolor=PANEL_COLOR, font=('Arial', 9))
self.widgets['chk_auto'].pack(fill=tk.X, pady=2)
self.widgets['btn_pgn'] = tk.Button(btn_frame, text=tr('import_pgn', 'Import PGN'),
command=self._import_pgn, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT)
self.widgets['btn_pgn'].pack(fill=tk.X, pady=2)
self.widgets['btn_reset'] = tk.Button(btn_frame, text=tr('reset_board', 'Reset board'),
command=self._reset_board, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT)
self.widgets['btn_reset'].pack(fill=tk.X, pady=2)
# PGN navigation
pgn_nav = tk.Frame(left, bg=BG_COLOR)
pgn_nav.pack(fill=tk.X, pady=2)
self.widgets['btn_pgn_first'] = tk.Button(pgn_nav, text="<<",
command=self._pgn_first, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT, width=3)
self.widgets['btn_pgn_first'].pack(side=tk.LEFT, padx=1)
self.widgets['btn_pgn_prev'] = tk.Button(pgn_nav, text="<",
command=self._pgn_prev, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT, width=3)
self.widgets['btn_pgn_prev'].pack(side=tk.LEFT, padx=1)
self.widgets['btn_pgn_next'] = tk.Button(pgn_nav, text=">",
command=self._pgn_next, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT, width=3)
self.widgets['btn_pgn_next'].pack(side=tk.LEFT, padx=1)
self.widgets['btn_pgn_last'] = tk.Button(pgn_nav, text=">>",
command=self._pgn_last, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT, width=3)
self.widgets['btn_pgn_last'].pack(side=tk.LEFT, padx=1)
self.widgets['btn_pgn_import'] = tk.Button(pgn_nav, text=tr('pgn_import_all', '>>>'),
command=self._pgn_import_all, bg=ACCENT_COLOR, fg="white", font=('Arial', 9, 'bold'), relief=tk.FLAT, width=3)
self.widgets['btn_pgn_import'].pack(side=tk.LEFT, padx=1)
self.pgn_label = tk.Label(left, text="", bg=BG_COLOR, fg=TEXT_COLOR, font=('Arial', 8))
self.pgn_label.pack(fill=tk.X)
save_frame = tk.Frame(left, bg=BG_COLOR)
save_frame.pack(fill=tk.X, pady=5)
self.widgets['btn_save_c'] = tk.Button(save_frame, text=tr('save_c', 'Save debut_book.c'),
command=self._save_c, bg=ACCENT_COLOR, fg="white", font=('Arial', 9, 'bold'), relief=tk.FLAT)
self.widgets['btn_save_c'].pack(fill=tk.X, pady=2)
self.widgets['btn_save_json'] = tk.Button(save_frame, text=tr('save_json', 'Save session (JSON)'),
command=self._save_session, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT)
self.widgets['btn_save_json'].pack(fill=tk.X, pady=2)
self.widgets['btn_load_json'] = tk.Button(save_frame, text=tr('load_json', 'Load session (JSON)'),
command=self._load_session, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 9), relief=tk.FLAT)
self.widgets['btn_load_json'].pack(fill=tk.X, pady=2)
# Fixed-width info label
self.info_label = tk.Label(left, text="", bg=BG_COLOR, fg=TEXT_COLOR,
font=('Consolas', 9), justify=tk.LEFT,
anchor="nw", width=42, height=8)
self.info_label.pack(fill=tk.X, pady=5)
self._update_info()
# Right panel
right = tk.Frame(self.root, bg=BG_COLOR)
right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
search_frame = tk.Frame(right, bg=BG_COLOR)
search_frame.pack(fill=tk.X, pady=(0, 5))
self.widgets['lbl_search'] = tk.Label(search_frame, text=tr('search_key', 'Search by key:'),
bg=BG_COLOR, fg=TEXT_COLOR, font=('Arial', 9))
self.widgets['lbl_search'].pack(side=tk.LEFT)
self.search_entry = tk.Entry(search_frame, textvariable=self.search_var,
bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Consolas', 9),
relief=tk.FLAT, width=20)
self.search_entry.pack(side=tk.LEFT, padx=5)
self.search_entry.bind('<Return>', self._do_search)
self.widgets['btn_search'] = tk.Button(search_frame, text=tr('lines_search', 'Search'),
command=self._do_search, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 8), relief=tk.FLAT)
self.widgets['btn_search'].pack(side=tk.LEFT, padx=2)
self.search_status = tk.Label(search_frame, text="", bg=BG_COLOR, fg=TEXT_COLOR,
font=('Arial', 8))
self.search_status.pack(side=tk.LEFT, padx=5)
self.widgets['btn_search_prev'] = tk.Button(search_frame, text=tr('search_prev', '< Back'),
command=self._search_prev, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 8), relief=tk.FLAT)
self.widgets['btn_search_prev'].pack(side=tk.LEFT, padx=2)
self.widgets['btn_search_next'] = tk.Button(search_frame, text=tr('search_next', 'Fwd >'),
command=self._search_next, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 8), relief=tk.FLAT)
self.widgets['btn_search_next'].pack(side=tk.LEFT, padx=2)
# Book table
table_frame = tk.Frame(right, bg=BG_COLOR)
table_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 5))
self.widgets['lbl_book'] = tk.Label(table_frame, text=tr('book_entries', 'Book entries'),
bg=BG_COLOR, fg=ACCENT_COLOR, font=('Arial', 10, 'bold'))
self.widgets['lbl_book'].pack(anchor=tk.W)
self.tree = ttk.Treeview(table_frame, columns=("key", "move", "san", "pg"),
show="headings", height=12)
self.tree.heading("key", text=tr('col_key', 'Zobrist Key'))
self.tree.heading("move", text=tr('col_move', 'UCI'))
self.tree.heading("san", text=tr('col_san', 'SAN'))
self.tree.heading("pg", text=tr('col_pg', 'Polyglot'))
self.tree.column("key", width=120)
self.tree.column("move", width=70)
self.tree.column("san", width=70)
self.tree.column("pg", width=80)
self.tree.pack(fill=tk.BOTH, expand=True)
self.tree.bind("<Double-1>", self._on_tree_double)
self.tree.bind("<Delete>", self._on_tree_delete)
# Lines
lines_frame = tk.Frame(right, bg=BG_COLOR)
lines_frame.pack(fill=tk.BOTH, expand=True, pady=(5, 0))
self.widgets['lbl_lines'] = tk.Label(lines_frame, text=tr('lines', 'Lines'),
bg=BG_COLOR, fg=ACCENT_COLOR, font=('Arial', 10, 'bold'))
self.widgets['lbl_lines'].pack(anchor=tk.W)
# Lines search
lines_search_frame = tk.Frame(lines_frame, bg=BG_COLOR)
lines_search_frame.pack(fill=tk.X, pady=(2, 5))
self.widgets['lbl_lines_search'] = tk.Label(lines_search_frame, text=tr('lines_search_key', 'Search in lines:'),
bg=BG_COLOR, fg=TEXT_COLOR, font=('Arial', 9))
self.widgets['lbl_lines_search'].pack(side=tk.LEFT)
self.lines_search_entry = tk.Entry(lines_search_frame, textvariable=self.lines_search_var,
bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Consolas', 9),
relief=tk.FLAT, width=20)
self.lines_search_entry.pack(side=tk.LEFT, padx=5)
self.lines_search_entry.bind('<Return>', self._do_lines_search)
self.widgets['btn_lines_search'] = tk.Button(lines_search_frame, text=tr('lines_search', 'Search'),
command=self._do_lines_search, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 8), relief=tk.FLAT)
self.widgets['btn_lines_search'].pack(side=tk.LEFT, padx=2)
self.lines_search_status = tk.Label(lines_search_frame, text="", bg=BG_COLOR, fg=TEXT_COLOR,
font=('Arial', 8))
self.lines_search_status.pack(side=tk.LEFT, padx=5)
self.widgets['btn_lines_search_prev'] = tk.Button(lines_search_frame, text=tr('lines_search_prev', '< Back'),
command=self._lines_search_prev, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 8), relief=tk.FLAT)
self.widgets['btn_lines_search_prev'].pack(side=tk.LEFT, padx=2)
self.widgets['btn_lines_search_next'] = tk.Button(lines_search_frame, text=tr('lines_search_next', 'Fwd >'),
command=self._lines_search_next, bg=PANEL_COLOR, fg=TEXT_COLOR, font=('Arial', 8), relief=tk.FLAT)
self.widgets['btn_lines_search_next'].pack(side=tk.LEFT, padx=2)
self.lines_tree = ttk.Treeview(lines_frame, columns=("name", "moves"),
show="tree", height=6)
self.lines_tree.heading("#0", text=tr('lines', 'Lines'))
self.lines_tree.pack(fill=tk.BOTH, expand=True)
self.lines_tree.bind("<Double-1>", self._on_line_double)
self._update_tree()
self._update_lines_tree()
def _switch_lang(self, lang):
set_lang(lang)
self.root.title(tr('title', 'Chess Book Editor v4.9 - STM32'))
self._build_ui()
self._draw_board()
self._update_info()
def _draw_board(self, highlight_sq=None):
chars = board_to_visual_chars(self.board)
self.canvas.delete("all")
for r in range(8):
for c in range(8):
color = LIGHT_COLOR if (r + c) % 2 == 0 else DARK_COLOR
x1, y1 = c * CELL_SIZE, r * CELL_SIZE
x2, y2 = x1 + CELL_SIZE, y1 + CELL_SIZE
self.canvas.create_rectangle(x1, y1, x2, y2, fill=color, outline="")
if self.last_move:
for sq in [self.last_move.from_square, self.last_move.to_square]:
f = chess.square_file(sq)
r_sq = chess.square_rank(sq)
col = f
row = 7 - r_sq
x1, y1 = col * CELL_SIZE, row * CELL_SIZE
x2, y2 = x1 + CELL_SIZE, y1 + CELL_SIZE
self.canvas.create_rectangle(x1, y1, x2, y2, outline="#ffcc00", width=3)
if highlight_sq is not None:
f = chess.square_file(highlight_sq)
r_sq = chess.square_rank(highlight_sq)
col = f
row = 7 - r_sq
x1, y1 = col * CELL_SIZE, row * CELL_SIZE
x2, y2 = x1 + CELL_SIZE, y1 + CELL_SIZE
self.canvas.create_rectangle(x1, y1, x2, y2, outline="#00ff00", width=3)
for r in range(8):
for c in range(8):
ch = chars[r * 8 + c]
cx, cy = c * CELL_SIZE + CELL_SIZE // 2, r * CELL_SIZE + CELL_SIZE // 2
self.canvas.create_text(cx, cy, text=ch, font=self.piece_font, fill="#000000")
if highlight_sq is not None:
piece = self.board.piece_at(highlight_sq)
if piece:
for move in self.board.legal_moves:
if move.from_square == highlight_sq:
to_sq = move.to_square
f = chess.square_file(to_sq)
r_sq = chess.square_rank(to_sq)
col = f
row = 7 - r_sq
cx, cy = col * CELL_SIZE + CELL_SIZE // 2, row * CELL_SIZE + CELL_SIZE // 2
target_piece = self.board.piece_at(to_sq)
if target_piece:
self.canvas.create_oval(cx - 16, cy - 16, cx + 16, cy + 16,
outline="#00cc00", width=2)
else:
self.canvas.create_oval(cx - 5, cy - 5, cx + 5, cy + 5,
fill="#00cc00", outline="")
def _on_click(self, event):
col = event.x // CELL_SIZE
row = event.y // CELL_SIZE
if col < 0 or col > 7 or row < 0 or row > 7:
return
file_idx = col
rank = 7 - row
square = chess.square(file_idx, rank)
if self.selected_square is None:
piece = self.board.piece_at(square)
if piece is not None and piece.color == self.board.turn:
self.selected_square = square
self._draw_board(square)
else:
if square == self.selected_square:
self.selected_square = None
self._draw_board()
return
from_sq = self.selected_square
to_sq = square
move_candidate = chess.Move(from_sq, to_sq)
piece = self.board.piece_at(from_sq)
if piece and piece.piece_type == chess.PAWN:
if (piece.color == chess.WHITE and chess.square_rank(to_sq) == 7) or \
(piece.color == chess.BLACK and chess.square_rank(to_sq) == 0):
move_candidate = chess.Move(from_sq, to_sq, promotion=chess.QUEEN)
if move_candidate in self.board.legal_moves:
self._make_move(move_candidate)
self.selected_square = None
else:
piece = self.board.piece_at(square)
if piece is not None and piece.color == self.board.turn:
self.selected_square = square
self._draw_board(square)
else:
self.selected_square = None
self._draw_board()
def _make_move(self, move):
was_black_turn = (self.board.turn == chess.BLACK)
key = compute_zobrist(self.board)
pg = move_to_polyglot(move, self.board)
san = self.board.san(move)
uci = move.uci()
self.board.push(move)
self.last_move = move
self.current_line_moves.append(uci)
self._draw_board()
self._update_info()
if was_black_turn and self.auto_add.get():
self._add_to_book_precomputed(move, san, uci, key, pg)
def _add_to_book_precomputed(self, move, san, uci, key, pg):
for existing in self.book:
if existing[0] == key and existing[1] == uci:
return
self.book.append((key, uci, san, pg))
uci_short = uci[:4]
print(f" + Book: key=0x{key:08X} move={uci_short} ({san}) pg=0x{pg:04X}")
self._update_tree()
def _add_to_book(self, move, san, uci):
key = compute_zobrist(self.board)
pg = move_to_polyglot(move, self.board)
for existing in self.book:
if existing[0] == key and existing[1] == uci:
return
self.book.append((key, uci, san, pg))
uci_short = uci[:4]
print(f" + Book: key=0x{key:08X} move={uci_short} ({san}) pg=0x{pg:04X}")
self._update_tree()
def _manual_add(self):
if self.last_move is None:
messagebox.showinfo(tr('info', 'Info'), tr('make_move_first', 'Make a move first'),
parent=self.root)
return
board_copy = self.board.copy()
last_move = board_copy.pop()
if board_copy.turn == chess.WHITE:
messagebox.showinfo(tr('info', 'Info'), tr('only_black', 'Only black moves added to book'),
parent=self.root)
return
uci = last_move.uci()
san = board_copy.san(last_move)
key = compute_zobrist(board_copy)
pg = move_to_polyglot(last_move, board_copy)
for existing in self.book:
if existing[0] == key and existing[1] == uci:
return
self.book.append((key, uci, san, pg))
print(f" + Book: key=0x{key:08X} move={uci[:4]} ({san}) pg=0x{pg:04X}")
self._update_tree()
def _undo_move(self):
if len(self.current_line_moves) == 0:
return
self.current_line_moves.pop()
self.board = chess.Board()
for uci in self.current_line_moves:
self.board.push_uci(uci)
self.last_move = chess.Move.from_uci(self.current_line_moves[-1]) if self.current_line_moves else None
self.selected_square = None
self._draw_board()
self._update_info()
def _new_line(self):
if self.current_line_moves:
name = f"{tr('line', 'Line')} {len(self.lines) + 1}"
self.lines.append((name, list(self.current_line_moves)))
self._update_lines_tree()
self.current_line_moves = []
self.board = chess.Board()
self.last_move = None
self.selected_square = None
self._draw_board()
self._update_info()
def _reset_board(self):
self.current_line_moves = []
self.board = chess.Board()
self.last_move = None
self.selected_square = None
self._draw_board()
self._update_info()
def _on_line_double(self, event):
sel = self.lines_tree.selection()
if not sel:
return
moves_uci = self.lines_tree.set(sel[0], "moves")
if not moves_uci:
return
moves_list = moves_uci.split()
self.current_line_moves = list(moves_list)
self.board = chess.Board()
for uci in moves_list:
self.board.push_uci(uci)
self.last_move = chess.Move.from_uci(moves_list[-1]) if moves_list else None
self._draw_board()
self._update_info()
def _import_pgn(self):
path = filedialog.askopenfilename(
title=tr('select_pgn', 'Select PGN file'),
filetypes=[("PGN files", "*.pgn"), ("All files", "*.*")],
parent=self.root)
if not path:
return
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
game = chess.pgn.read_game(f)
if game is None:
messagebox.showerror(tr('error', 'Error'), tr('no_games', 'No games found'),
parent=self.root)
return
self.pgn_game = game
self.pgn_moves = list(game.mainline_moves())
self.pgn_index = 0
self.board = chess.Board()
self.last_move = None
self.current_line_moves = []
self._draw_board()
self._update_info()
self.pgn_label.config(text=f"PGN: {len(self.pgn_moves)} {tr('moves', 'moves')}")
print(f" + PGN loaded: {len(self.pgn_moves)} moves")
except Exception as e:
messagebox.showerror(tr('error', 'Error'), str(e), parent=self.root)
def _pgn_first(self):
if not self.pgn_moves:
return
self.pgn_index = 0
self.board = chess.Board()
self.last_move = None
self.current_line_moves = []
self._draw_board()
self._update_info()
self.pgn_label.config(text=f"PGN: 0 / {len(self.pgn_moves)}")
def _pgn_prev(self):
if not self.pgn_moves or self.pgn_index == 0:
return
self.pgn_index -= 1
self.board = chess.Board()
self.current_line_moves = []
for i in range(self.pgn_index):
mv = self.pgn_moves[i]
self.board.push(mv)
self.current_line_moves.append(mv.uci())
self.last_move = self.pgn_moves[self.pgn_index - 1] if self.pgn_index > 0 else None
self._draw_board()
self._update_info()
self.pgn_label.config(text=f"PGN: {self.pgn_index} / {len(self.pgn_moves)}")
def _pgn_next(self):
if not self.pgn_moves or self.pgn_index >= len(self.pgn_moves):
return
move = self.pgn_moves[self.pgn_index]
was_black = (self.board.turn == chess.BLACK)
key = compute_zobrist(self.board)
pg = move_to_polyglot(move, self.board)
san = self.board.san(move)
uci = move.uci()
self.board.push(move)
self.last_move = move
self.current_line_moves.append(uci)
self.pgn_index += 1
self._draw_board()
self._update_info()
self.pgn_label.config(text=f"PGN: {self.pgn_index} / {len(self.pgn_moves)}")
if was_black and self.auto_add.get():
self._add_to_book_precomputed(move, san, uci, key, pg)
def _pgn_last(self):
if not self.pgn_moves:
return
while self.pgn_index < len(self.pgn_moves):
move = self.pgn_moves[self.pgn_index]
was_black = (self.board.turn == chess.BLACK)
key = compute_zobrist(self.board)
pg = move_to_polyglot(move, self.board)
san = self.board.san(move)
uci = move.uci()
self.board.push(move)
self.current_line_moves.append(uci)
self.pgn_index += 1
if was_black and self.auto_add.get():
self._add_to_book_precomputed(move, san, uci, key, pg)
self.last_move = self.pgn_moves[-1] if self.pgn_moves else None
self._draw_board()
self._update_info()
self.pgn_label.config(text=f"PGN: {self.pgn_index} / {len(self.pgn_moves)}")
def _pgn_import_all(self):
if not self.pgn_game:
messagebox.showinfo(tr('info', 'Info'), tr('load_pgn_first', 'Load PGN first'),
parent=self.root)
return
count = 0
board = self.pgn_game.board()
for move in self.pgn_game.mainline_moves():
was_black = (board.turn == chess.BLACK)
if was_black:
key = compute_zobrist(board)
pg = move_to_polyglot(move, board)
uci = move.uci()
san = board.san(move)
dup = False
for existing in self.book:
if existing[0] == key and existing[1] == uci:
dup = True
break
if not dup:
self.book.append((key, uci, san, pg))
count += 1
board.push(move)
print(f" + PGN import: {count} entries added")
self._update_tree()
messagebox.showinfo(tr('pgn_import', 'PGN import'),
f"{count} {tr('entries_added', 'entries added')}",
parent=self.root)
def _save_c(self):
if not self.book:
messagebox.showinfo(tr('info', 'Info'), tr('book_empty', 'Book is empty'),
parent=self.root)
return
path = filedialog.asksaveasfilename(
title=tr('save_c', 'Save debut_book.c'),
defaultextension=".c", initialfile="debut_book.c",
filetypes=[("C files", "*.c"), ("All files", "*.*")],
parent=self.root)
if not path:
return
sorted_book = sorted(self.book, key=lambda x: x[0])
try:
with open(path, "w", encoding="utf-8") as f:
f.write("// Auto-generated by chess_book_editor.py v4.9\n")
f.write("// Total entries: %d\n" % len(sorted_book))
f.write("// Sorted by Zobrist key for binary search\n\n")
f.write("#include <stdint.h>\n\n")
f.write("typedef struct {\n")
f.write(" uint32_t key; // Zobrist key\n")
f.write(" uint16_t move; // Polyglot move\n")
f.write(" uint16_t weight;\n")
f.write("} book_entry_t;\n\n")
f.write("const book_entry_t debut_book[%d] = {\n" % len(sorted_book))
for key, uci, san, pg in sorted_book:
uci_short = uci[:4]
k = _safe_key(key)
p = _safe_int(pg)
f.write(" { 0x%08X, 0x%04X, 1 }, // %s (%s)\n" % (k, p, uci_short, san))
f.write("};\n\n")
f.write("const uint16_t debut_book_size = %d;\n" % len(sorted_book))
print(f" + Saved: {path} ({len(sorted_book)} entries, sorted)")
messagebox.showinfo(tr('save', 'Save'),
f"{len(sorted_book)} {tr('entries_saved_sorted', 'entries saved, sorted by key')}",
parent=self.root)
except Exception as e:
messagebox.showerror(tr('error', 'Error'), str(e), parent=self.root)
def _save_session(self):
path = filedialog.asksaveasfilename(
title=tr('save_session', 'Save session'),
defaultextension=".json", initialfile="book_session.json",
filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
parent=self.root)
if not path:
return
if self.current_line_moves:
self.lines.append((f"{tr('line', 'Line')} {len(self.lines) + 1}",
list(self.current_line_moves)))
self._update_lines_tree()
data = {
"version": "4.9",
"book": [[int(k), u, s, int(p)] for k, u, s, p in self.book],
"lines": [[name, moves] for name, moves in self.lines],
}
try:
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f" + Session saved: {path}")
messagebox.showinfo(tr('save', 'Save'),
f"{len(self.book)} {tr('entries', 'entries')}, {len(self.lines)} {tr('lines_saved', 'lines')}",
parent=self.root)
except Exception as e:
messagebox.showerror(tr('error', 'Error'), str(e), parent=self.root)
def _load_session(self):
path = filedialog.askopenfilename(
title=tr('load_session', 'Load session'),
filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
parent=self.root)
if not path:
return
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
self.book = [(_safe_key(e[0]), e[1], e[2], _safe_int(e[3])) for e in data.get("book", [])]
self.lines = [(l[0], l[1]) for l in data.get("lines", [])]
self.current_line_moves = []
self.board = chess.Board()
self.last_move = None
self._update_tree()
self._update_lines_tree()
self._draw_board()
self._update_info()
print(f" + Loaded: {len(self.book)} entries, {len(self.lines)} lines")
if self.book:
k0 = _safe_key(self.book[0][0])
p0 = _safe_int(self.book[0][3])
print(f" [DEBUG] First key: 0x{k0:08X} (type={type(self.book[0][0]).__name__})")
print(f" [DEBUG] First pg: 0x{p0:04X} (type={type(self.book[0][3]).__name__})")
children = self.tree.get_children()
print(f" [DEBUG] Tree items: {len(children)}")
messagebox.showinfo(tr('load', 'Load'),
f"{len(self.book)} {tr('entries', 'entries')}, {len(self.lines)} {tr('lines', 'lines')}",
parent=self.root)
except Exception as e:
messagebox.showerror(tr('error', 'Error'), str(e), parent=self.root)
def _update_tree(self):
for item in self.tree.get_children():
self.tree.delete(item)
for key, uci, san, pg in self.book:
k = _safe_key(key)
p = _safe_int(pg)
self.tree.insert("", tk.END, values=(f"0x{k:08X}", uci[:4], san, f"0x{p:04X}"))
def _update_lines_tree(self):
for item in self.lines_tree.get_children():
self.lines_tree.delete(item)
for name, moves in self.lines:
self.lines_tree.insert("", tk.END, text=name, values=("", " ".join(moves)))
def _on_tree_double(self, event):
sel = self.tree.selection()
if not sel:
return
values = self.tree.item(sel[0], "values")
messagebox.showinfo(tr('entry', 'Entry'),
f"Key: {values[0]}\nUCI: {values[1]}\nSAN: {values[2]}\nPG: {values[3]}",
parent=self.root)
def _on_tree_delete(self, event):
sel = self.tree.selection()
if not sel:
return
idx = self.tree.index(sel[0])
if idx < len(self.book):
del self.book[idx]
self._update_tree()
self._update_info()
def _do_search(self, event=None):
query = self.search_var.get().strip().lower()
if not query:
self.search_status.config(text="", fg=TEXT_COLOR)
return
if query.startswith("0x"):
query = query[2:]
print(f" [SEARCH] query='{query}', book size={len(self.book)}")
self.search_results = []
try:
for i, (key, uci, san, pg) in enumerate(self.book):
k = _safe_key(key)
key_hex = f"{k:08x}"
if query in key_hex:
self.search_results.append(i)
except Exception as e:
print(f" [SEARCH ERROR] {e}")
self.search_status.config(text=tr('not_found', 'not found'), fg="#ff6666")
return
print(f" [SEARCH] found {len(self.search_results)} matches")
if self.search_results:
self.search_idx = 0
self._goto_search_result()
self.search_status.config(text=f"1/{len(self.search_results)}", fg="#00ff00")
else:
self.search_status.config(text=tr('not_found', 'not found'), fg="#ff6666")
def _goto_search_result(self):
if not self.search_results:
return
idx = self.search_results[self.search_idx]
children = self.tree.get_children()
print(f" [SEARCH] goto idx={idx}, tree items={len(children)}")
if idx < len(children):
self.tree.selection_set(children[idx])
self.tree.see(children[idx])
def _search_next(self):
if not self.search_results:
self._do_search()
return
self.search_idx = (self.search_idx + 1) % len(self.search_results)
self._goto_search_result()
self.search_status.config(text=f"{self.search_idx + 1}/{len(self.search_results)}", fg="#00ff00")
def _search_prev(self):
if not self.search_results:
self._do_search()
return
self.search_idx = (self.search_idx - 1) % len(self.search_results)
self._goto_search_result()
self.search_status.config(text=f"{self.search_idx + 1}/{len(self.search_results)}", fg="#00ff00")
def _do_lines_search(self, event=None):
query = self.lines_search_var.get().strip().lower()
if not query:
self.lines_search_status.config(text="", fg=TEXT_COLOR)
return
if query.startswith("0x"):
query = query[2:]
print(f" [LINES SEARCH] query='{query}', lines count={len(self.lines)}")
self.lines_search_results = []
try:
for i, (name, moves) in enumerate(self.lines):
# Search in line name (hash is usually in the name)
name_lower = name.lower()
if query in name_lower:
self.lines_search_results.append(i)
continue
# Also compute key from moves if name doesn't contain it
if ZOBRIST_NUMBERS and moves:
b = chess.Board()
for uci in moves:
try:
b.push_uci(uci)
except:
break
key = compute_zobrist(b)
key_hex = f"{key:08x}"
if query in key_hex:
self.lines_search_results.append(i)
except Exception as e:
print(f" [LINES SEARCH ERROR] {e}")
self.lines_search_status.config(text=tr('not_found', 'not found'), fg="#ff6666")
return
print(f" [LINES SEARCH] found {len(self.lines_search_results)} matches")
if self.lines_search_results:
self.lines_search_idx = 0
self._goto_lines_search_result()
self.lines_search_status.config(text=f"1/{len(self.lines_search_results)}", fg="#00ff00")
else:
self.lines_search_status.config(text=tr('not_found', 'not found'), fg="#ff6666")
def _goto_lines_search_result(self):
if not self.lines_search_results:
return
idx = self.lines_search_results[self.lines_search_idx]
children = self.lines_tree.get_children()
print(f" [LINES SEARCH] goto idx={idx}, tree items={len(children)}")
if idx < len(children):
self.lines_tree.selection_set(children[idx])
self.lines_tree.see(children[idx])
def _lines_search_next(self):
if not self.lines_search_results:
self._do_lines_search()
return
self.lines_search_idx = (self.lines_search_idx + 1) % len(self.lines_search_results)
self._goto_lines_search_result()
self.lines_search_status.config(text=f"{self.lines_search_idx + 1}/{len(self.lines_search_results)}", fg="#00ff00")
def _lines_search_prev(self):
if not self.lines_search_results:
self._do_lines_search()
return
self.lines_search_idx = (self.lines_search_idx - 1) % len(self.lines_search_results)
self._goto_lines_search_result()
self.lines_search_status.config(text=f"{self.lines_search_idx + 1}/{len(self.lines_search_results)}", fg="#00ff00")
def _update_info(self):
key = compute_zobrist(self.board)
in_book = any(_safe_key(e[0]) == key for e in self.book)
info = f"Zobrist: 0x{key:08X}\n"
info += f"{tr('in_book', 'In book')}: {tr('yes', 'yes') if in_book else tr('no', 'no')}\n"
info += f"{tr('entries', 'Entries')}: {len(self.book)}\n"
info += f"{tr('line_moves', 'Moves in line')}: {len(self.current_line_moves)}\n"
info += f"FEN: {self.board.fen()}"
self.info_label.config(text=info)
# ====================== MAIN ======================
def main():
print("=== Chess Book Editor v4.9 ===")
zobrist_path = find_zobrist_file()
if zobrist_path is None:
print("[Zobrist] File not found. Select manually.")
root_temp = tk.Tk()
root_temp.withdraw()
zobrist_path = filedialog.askopenfilename(
title="Select zobrist_table.h",
filetypes=[("Header files", "*.h"), ("All files", "*.*")])
root_temp.destroy()
if not zobrist_path:
print("[ERROR] zobrist_table.h not selected. Exit.")
sys.exit(1)
numbers = load_zobrist_table(zobrist_path)
print(f"Loaded {len(numbers)} Zobrist numbers from {os.path.basename(zobrist_path)}")
root = tk.Tk()
app = ChessBookEditor(root)
root.mainloop()
if __name__ == "__main__":
main()
-
Alexlaw1964
- Posts: 34
- Joined: Fri Jan 03, 2025 6:36 am
- Full name: alex lobov
Re: Move Generation: Castling - micro-Max by H.G. Muller
chess_book_editor_lang.py
Code: Select all
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
chess_book_editor_lang.py — Локализация редактора дебютной книги
Версия: 4.9
"""
LANG = {
'ru': {
'title': 'Редактор дебютной книги v4.9 — STM32',
'language': 'Язык:',
'new_line': 'Новая линия',
'undo': 'Отменить ход',
'add_to_book': '+ В книгу',
'auto_add': 'Авто-добавление',
'import_pgn': 'Импорт PGN',
'reset_board': 'Сброс доски',
'pgn_import_all': '>>>',
'save_c': 'Сохранить debut_book.c',
'save_json': 'Сохранить сессию (JSON)',
'load_json': 'Загрузить сессию (JSON)',
'search_key': 'Поиск по ключу:',
'search_prev': '< Назад',
'search_next': 'Вперёд >',
'lines_search_key': 'Поиск в линиях:',
'lines_search': 'Поиск',
'lines_search_prev': '< Назад',
'lines_search_next': 'Вперёд >',
'book_entries': 'Записи книги',
'col_key': 'Zobrist-ключ',
'col_move': 'UCI',
'col_san': 'SAN',
'col_pg': 'Polyglot',
'lines': 'Линии',
'info': 'Инфо',
'make_move_first': 'Сначала сделайте ход',
'only_black': 'В книгу добавляются только ходы чёрных',
'line': 'Линия',
'select_pgn': 'Выберите PGN-файл',
'error': 'Ошибка',
'no_games': 'Игры не найдены',
'moves': 'ходов',
'load_pgn_first': 'Сначала загрузите PGN',
'entries_added': 'записей добавлено',
'book_empty': 'Книга пуста',
'entries_saved_sorted': 'записей сохранено, отсортировано по ключу',
'save': 'Сохранение',
'entries': 'записей',
'lines_saved': 'линий',
'load': 'Загрузка',
'entry': 'Запись',
'not_found': 'не найдено',
'in_book': 'В книге',
'yes': 'да',
'no': 'нет',
'line_moves': 'Ходов в линии',
},
'en': {
'title': 'Chess Book Editor v4.9 — STM32',
'language': 'Language:',
'new_line': 'New line',
'undo': 'Undo move',
'add_to_book': '+ Add to book',
'auto_add': 'Auto-add',
'import_pgn': 'Import PGN',
'reset_board': 'Reset board',
'pgn_import_all': '>>>',
'save_c': 'Save debut_book.c',
'save_json': 'Save session (JSON)',
'load_json': 'Load session (JSON)',
'search_key': 'Search by key:',
'search_prev': '< Back',
'search_next': 'Fwd >',
'lines_search_key': 'Search in lines:',
'lines_search': 'Search',
'lines_search_prev': '< Back',
'lines_search_next': 'Fwd >',
'book_entries': 'Book entries',
'col_key': 'Zobrist Key',
'col_move': 'UCI',
'col_san': 'SAN',
'col_pg': 'Polyglot',
'lines': 'Lines',
'info': 'Info',
'make_move_first': 'Make a move first',
'only_black': 'Only black moves added to book',
'line': 'Line',
'select_pgn': 'Select PGN file',
'error': 'Error',
'no_games': 'No games found',
'moves': 'moves',
'load_pgn_first': 'Load PGN first',
'entries_added': 'entries added',
'book_empty': 'Book is empty',
'entries_saved_sorted': 'entries saved, sorted by key',
'save': 'Save',
'entries': 'entries',
'lines_saved': 'lines',
'load': 'Load',
'entry': 'Entry',
'not_found': 'not found',
'in_book': 'In book',
'yes': 'yes',
'no': 'no',
'line_moves': 'Moves in line',
}
}
_current_lang = 'ru'
def set_lang(l):
global _current_lang
if l in LANG:
_current_lang = l
def get_lang():
return _current_lang
def tr(key, default=None):
d = LANG.get(_current_lang, LANG.get('ru', {}))
return d.get(key, default or key)