The way to do it is to try all the possible combinations of 2 moves and choose the first move of the combination that leads to the lowest score(I think that it may be improved by using some engine to prune part of the combination and try all the logical combinations of 3 moves).
Still did not work against Stockfish developement version with 10K nodes per second but was closer than earlier tries.
[pgn][Event "Knight-Odds 2-Ply White Search Book"]
[Site "?"]
[Date "????.??.??"]
[Round "?"]
[White "Brute-force 2-Ply (No Engine, No Nb1)"]
[Black "Stockfish 30_09 (10k nodes)"]
[Result "0-1"]
[BlackElo "2200"]
[WhiteElo "2000"]
[TimeControl "300+3"]
[SetUp "1"]
[FEN "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/R1BQKBNR w KQkq - 0 1"]
[Termination "normal"]
[PlyCount "192"]
[WhiteType "program"]
[BlackType "program"]
1. e4 c6 2. d4 d6 3. Bd2 g6 4. e5 Bh6 5. f4 Bg7 6. Qf3 Be6 7. O-O-O Nh6 8.
Ne2 Na6 9. Kb1 Qc7 10. Rg1 f6 11. exf6 exf6 12. f5 Bxf5 13. g4 Bc8 14. g5
Nf5 15. gxf6 Bxf6 16. Ng3 Be7 17. Re1 Rf8 18. Qc3 Bd7 19. Bxa6 bxa6 20. d5
O-O-O 21. dxc6 Be8 22. Qc4 Bh4 23. Rxe8 Rdxe8 24. Nxf5 Rxf5 25. Qxh4 Rc5
26. a4 Rxc6 27. Bc3 Rc4 28. Qf6 Qe7 29. Qf1 d5 30. Qd3 Qd6 31. b3 Rc6 32.
Qd2 Kb8 33. Rg5 Qe6 34. Re5 Qf7 35. Rxe8+ Qxe8 36. Kb2 Qe4 37. Qf2 Qf5 38.
Qd4 Rc8 39. Qe3 g5 40. a5 Kb7 41. Qe2 h5 42. Qg2 g4 43. Bd4 h4 44. c3 Rc6
45. Qe2 Re6 46. Qd2 h3 47. Ka3 Re8 48. Bc5 g3 49. hxg3 Qe5 50. Bg1 Re7 51.
g4 Rc7 52. g5 Rf7 53. Qd3 Rc7 54. Qd2 Kc8 55. Kb2 Qe4 56. Qc1 d4 57. c4
Qg2+ 58. Qc2 Qxc2+ 59. Kxc2 Rg7 60. Kd3 Rxg5 61. Bh2 Rxa5 62. Kxd4 Ra2 63.
Bg3 a5 64. Ke3 Kd7 65. Be5 Ra3 66. Kf2 Rxb3 67. Bd4 a4 68. Bh8 a5 69. c5
Kc6 70. Kg1 Ra3 71. Kh1 Rd3 72. Kh2 Re3 73. Kh1 Rg3 74. Be5 Rb3 75. Bd6 Rf3
76. Kh2 Re3 77. Kg1 Re2 78. Kh1 Re3 79. Bb8 Rd3 80. Bd6 Re3 81. Kg1 Rd3 82.
Be7 Rb3 83. Bh4 Rf3 84. Be7 a3 85. Bd6 Re3 86. Kf2 Re6 87. Kg3 a2 88. Kxh3
a4 89. Bb8 a1=Q 90. Kg2 a3 91. Bd6 Qd4 92. Bg3 a2 93. Bb8 a1=Q 94. Bg3 Qg4
95. Kh2 Re2+ 96. Bf2 Rxf2# 0-1
[/pgn]
Here is the python code for that task(comments are in hebrew because I use hebrew to discuss with gemini:
Code: Select all
import os
import chess
import chess.pgn
import chess.engine
STOCKFISH_PATH = "stockfish-30_09.exe"
PGN_OUTPUT = "knight_odds_book_2ply_white.pgn"
# עמדת התחלה ללא פרש ב-b1
START_FEN_KNIGHT_ODDS = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/R1BQKBNR w KQkq - 0 1"
BLACK_NODES = 10000 # 10K עמדות לשחור
def score_to_white_cp(score_obj):
"""ממיר תוצאת מנוע לסנטי-רגלים מנקודת המבט של לבן"""
pov = score_obj.pov(chess.WHITE)
if pov.is_mate():
m = pov.mate()
return (100000.0 - m * 10) if m > 0 else (-100000.0 - m * 10)
cp = pov.score()
return float(cp) if cp is not None else 0.0
def evaluate_position_white(engine, board):
"""הערכת סטוקפיש מנקודת המבט של לבן לפי 10k צמתים"""
info = engine.analyse(board, chess.engine.Limit(nodes=BLACK_NODES))
return score_to_white_cp(info["score"])
def get_black_reply(engine, board):
"""מחזיר את המסע של שחור לפי סטוקפיש (10k צמתים)"""
res = engine.play(board, chess.engine.Limit(nodes=BLACK_NODES))
return res.move
def main():
if not os.path.exists(STOCKFISH_PATH):
print(f"שגיאה: קובץ סטוקפיש לא נמצא בנתיב '{STOCKFISH_PATH}'!")
return
print("=== מתחיל בניית ספר פתיחות (Knight-Odds: ללא פרש ב-b1) ===")
print("שיטה: בחינת כל הצירופים של 2 מסעי לבן קדימה מול מענה סטוקפיש 10k לשחור\n")
engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
engine.configure({"Threads": 1, "Hash": 256})
book_moves = []
move_number = 1
try:
while True:
# שחזור העמדה עד למסע הנוכחי
board = chess.Board(START_FEN_KNIGHT_ODDS)
for m in book_moves:
board.push(m)
if board.is_game_over():
outcome = board.outcome()
print(f"\nהמשחק הסתיים: {outcome.result()} ({outcome.termination.name})")
break
legal_w1 = list(board.legal_moves)
if not legal_w1:
print("\nאין מסעים חוקיים ללבן.")
break
print(f"--- מסע {move_number} של לבן ---")
print(f"בוחן צירופי 2 מסעים קדימה (מתוך {len(legal_w1)} אפשרויות למסע הראשון)...")
best_w1_move = None
best_b1_move = None # נשמור את המענה המדויק של שחור שנבחר בלולאה!
best_score = -float("inf")
best_combination_info = ""
# לולאת המסע הראשון של לבן (W1)
for w1 in legal_w1:
w1_san = board.san(w1)
board.push(w1)
if board.is_game_over():
outcome = board.outcome()
score = 100000.0 if outcome.winner == chess.WHITE else (0.0 if outcome.winner is None else -100000.0)
if score > best_score:
best_score = score
best_w1_move = w1
best_b1_move = None
best_combination_info = f"{w1_san} (סיום משחק מיידי)"
board.pop()
continue
# מענה ראשון של שחור (B1)
b1 = get_black_reply(engine, board)
if not b1:
board.pop()
continue
b1_san = board.san(b1)
board.push(b1)
if board.is_game_over():
outcome = board.outcome()
score = 100000.0 if outcome.winner == chess.WHITE else (0.0 if outcome.winner is None else -100000.0)
if score > best_score:
best_score = score
best_w1_move = w1
best_b1_move = b1
best_combination_info = f"{w1_san} -> {b1_san} (סיום משחק)"
board.pop()
board.pop()
continue
# לולאת המסע השני של לבן (W2)
legal_w2 = list(board.legal_moves)
for w2 in legal_w2:
w2_san = board.san(w2)
board.push(w2)
if board.is_game_over():
outcome = board.outcome()
score = 100000.0 if outcome.winner == chess.WHITE else (0.0 if outcome.winner is None else -100000.0)
else:
# מענה שני של שחור (B2)
b2 = get_black_reply(engine, board)
if b2:
board.push(b2)
score = evaluate_position_white(engine, board)
board.pop()
else:
score = evaluate_position_white(engine, board)
board.pop() # ביטול w2
if score > best_score:
best_score = score
best_w1_move = w1
best_b1_move = b1 # מקבעים את b1 שהוביל לתוצאה הטובה ביותר
best_combination_info = f"{w1_san} -> {b1_san} -> {w2_san} (הערכה ללבן: {score:+.2f})"
board.pop() # ביטול b1
board.pop() # ביטול w1
# 1. קיבוע המסע הנבחר של לבן לספר
chosen_w1_san = board.san(best_w1_move)
board.push(best_w1_move)
book_moves.append(best_w1_move)
print(f"-> נבחר ללבן לספר: {chosen_w1_san}")
print(f" הצירוף המוביל שנמצא: {best_combination_info}")
if board.is_game_over():
outcome = board.outcome()
print(f"\nהמשחק הסתיים לאחר מסע לבן: {outcome.result()}")
break
# 2. שחור משחק בדיוק את המסע שחושב בלולאה (b1) – עקביות מלאה של 100%
if best_b1_move:
chosen_black_san = board.san(best_b1_move)
board.push(best_b1_move)
book_moves.append(best_b1_move)
print(f"-> שחור משחק לספר: {chosen_black_san}\n")
else:
# גיבוי למקרה קצה נדיר
black_move = get_black_reply(engine, board)
chosen_black_san = board.san(black_move)
board.push(black_move)
book_moves.append(black_move)
print(f"-> שחור משחק לספר (חישוב ישיר): {chosen_black_san}\n")
if board.is_game_over():
outcome = board.outcome()
print(f"\nהמשחק הסתיים לאחר מסע שחור: {outcome.result()}")
break
move_number += 1
finally:
engine.quit()
# שמירה ל-PGN
final_game = chess.pgn.Game()
final_game.headers["Event"] = "Knight-Odds 2-Ply White Search Book"
final_game.headers["White"] = "Brute-force 2-Ply (No Engine, No Nb1)"
final_game.headers["Black"] = "Stockfish 30_09 (10k nodes)"
final_game.headers["FEN"] = START_FEN_KNIGHT_ODDS
final_game.headers["SetUp"] = "1"
node = final_game
b_verify = chess.Board(START_FEN_KNIGHT_ODDS)
for m in book_moves:
node = node.add_variation(m)
b_verify.push(m)
if b_verify.is_game_over():
final_game.headers["Result"] = b_verify.result()
with open(PGN_OUTPUT, "w", encoding="utf-8") as f:
print(final_game, file=f, end="\n\n")
print(f"הספר נשמר בהצלחה ב-PGN: '{PGN_OUTPUT}'")
input("\nלחץ Enter לסיום...")
if __name__ == "__main__":
main()