Skip to content

Instantly share code, notes, and snippets.

@serrasqueiro
Last active December 14, 2025 18:01
Show Gist options
  • Select an option

  • Save serrasqueiro/7b086c8d62888934bb3ef6b9b0cc9a2e to your computer and use it in GitHub Desktop.

Select an option

Save serrasqueiro/7b086c8d62888934bb3ef6b9b0cc9a2e to your computer and use it in GitHub Desktop.
Chess man!
[Event "Saint Louis Blitz 2017"]
[Site "Saint Louis USA"]
[Date "2017.08.18"]
[Round "12.3"]
[White "Caruana, Fabiano"]
[Black "Kasparov, Garry"]
[Result "0-1"]
[WhiteElo "2807"]
[BlackElo "2812"]
[ECO "B23"]
1. e4 c5 2. Nc3 d6 3. Nge2 Nf6 4. g3 g6 5. Bg2 Nc6 6. d3 Bg7 7. O-O O-O 8. a3 Rb8 9. Rb1 b6 10. b4 Bb7 11. h3 Nd7 12. Be3 Re8 13. Qd2 Ba8 14. f4 Rc8 15. g4 e6 16. f5 Nd4 17. Bxd4 cxd4 18. Nb5 exf5 19. gxf5 Ne5 20. Nbxd4 d5 21. Qf4 dxe4 22. dxe4 Qc7 23. f6 Bf8 24. Rb3 Nc4 25. Rc3 a6 26. Qxc7 Rxc7 27. Nf4 b5 28. Nd5 Rcc8 29. Rd3 Bd6 30. Nb3 Bxd5 31. exd5 Re5 32. Nc5 a5 33. Rfd1 a4 34. Rf1 Rce8 35. Rdf3 h5 36. Nd3 Re2 37. R3f2 R2e3 38. Rf3 Re2 39. R1f2 Kh7 40. Bf1 R2e4 41. Nc5 Re1 42. Kg2 Ne3+ 43. Kh1 Nxd5 44. Rd3 R8e5 45. Nd7 Rg5 46. Rff3 Bc7 47. Nc5 h4 48. Rd4 Bg3 49. Nd3 Ra1 50. Re4 Rxa3 51. Re7 Kg8 52. Re8+ Kh7 53. Rf8 Ra1 54. Rxf7+ Kg8 55. Rg7+ Kf8 56. Ra7 Rf5 57. Rxf5 gxf5 58. Kg2 Ne3+ 59. Kf3 Nxf1 60. Nc5 Re1 61. Kg2 Ne3+ 62. Kf3 Nc4 63. Ra6 Nd2+ 64. Kg2 Re2+ 65. Kh1 Rh2+ 0-1

Kasparov vs Caruana -- Saint Louis Blitz 2017

This repository contains a Python script and assets to visualize the final position of the game
Caruana vs Kasparov, Saint Louis Blitz 2017 (Round 12.3, ECO B23).

The tournament itself

Tournament is referenced here:

  • (here) 365chess - Saint Lous Blitz 20217

Files

  • dump_svg_last.py -> Python script that reads the PGN and generates a PNG of the last move.
  • Caruana_vs_Kasparov_2017.pgn -> PGN file with the full game.
  • kasparov_last_move.png -> Output image showing the final board position with Kasparov's last move highlighted.

Usage

1. Install dependencies

pip install chess cairosvg

2. Generate images

python dump_svg_all.py

3. Generate index.html:

  • python to_html.py

Other sweets!

Probable Continuation (illustrative)

Although Caruana resigned after 65... Rh2+, a likely continuation could have been:

Move White (Caruana) Black (Kasparov) Comment
66 Kg1 Rh1+ Black rook drives king further into the corner
67 Kg2 Ne3+ Knight joins the attack with tempo
68 Kf2 Rf1+ Rook check, forcing king back
69 Ke2 Rf2+ Rook invades, tightening the net
70 Kxf2 Nxf5 Knight recaptures, maintaining material edge
71 gxf5 Kg7 Black king centralizes, ready to support pawns
72 Kg3 Kf6 King marches forward
73 Kg4 h5+ Pawn thrust, opening mating net
74 Kxh5 Kxf5 King captures but Black king dominates
75 h4 Rh8# Final rook mate on h8

Result: 0–1 (Kasparov checkmates)

""" # dump_svg_all.py -- including variations.
Generate SVGs for every move in a PGN game, including variation continuation.
"""
import os
import chess
import chess.pgn
import chess.svg
SHOW_COORDINATES = True
def main():
pgn_path = "365chess_games.pgn" # Caruana_vs_Kasparov_2017.pgn is a soft-link to that
out_dir = "svgs_all_moves"
show_coords = SHOW_COORDINATES
exported = script(pgn_path, out_dir, show_coords)
print(f"Exported {exported} SVG files to {out_dir}")
def script(pgn_path, out_dir, show_coords=False):
""" Export coordinates into move_N.svg file(s). """
exported = dump_all_moves_to_svg(
pgn_path=pgn_path,
out_dir=out_dir,
base_name="move",
size=400,
coordinates=show_coords
)
return exported
def ensure_dir(path):
if os.path.isdir(path):
print("Re-using:", path)
return False
os.makedirs(path)
return True
def svg_for_move(board, last_move=None, size=400, coordinates=False):
return chess.svg.board(
board=board,
lastmove=last_move,
size=size,
coordinates=coordinates
)
def dump_all_moves_to_svg(pgn_path, out_dir, base_name="move", size=400, coordinates=False, verbose=0):
print("Read:", pgn_path)
ensure_dir(out_dir)
with open(pgn_path, "r", encoding="utf-8") as fdin:
game = chess.pgn.read_game(fdin)
if game is None:
raise ValueError("No game found in PGN file.")
board = game.board()
move_index = 0
# Initial position
svg_init = svg_for_move(board, last_move=None, size=size, coordinates=coordinates)
init_name = f"{base_name}_{move_index:03d}.svg"
with open(os.path.join(out_dir, init_name), "w", encoding="utf-8") as outf:
print("(Re-)creating:", out_dir)
outf.write(svg_init)
# Export mainline moves
for mv in game.mainline_moves():
if verbose > 0:
print("Move:", mv)
board.push(mv)
move_index += 1
svg_data = svg_for_move(board, last_move=mv, size=size, coordinates=coordinates)
fname = f"{base_name}_{move_index:03d}.svg"
with open(os.path.join(out_dir, fname), "w", encoding="utf-8") as outf:
outf.write(svg_data)
# Export variation continuation if present
terminal = game.end()
if terminal.variations:
print("Found variation continuation; exporting hypothetical line...")
var_node = terminal.variations[0]
while var_node is not None:
mv = var_node.move
if mv is None:
break
print("Var move:", mv)
board.push(mv)
move_index += 1
svg_data = svg_for_move(board, last_move=mv, size=size, coordinates=coordinates)
fname = f"{base_name}_{move_index:03d}.svg"
with open(os.path.join(out_dir, fname), "w", encoding="utf-8") as outf:
outf.write(svg_data)
var_node = var_node.variations[0] if var_node.variations else None
return move_index
if __name__ == "__main__":
main()
""" kasparov_last_move_png.py
Generate a PNG image of the final position from Caruana vs Kasparov (Saint Louis Blitz 2017)
"""
import chess
import chess.pgn
import chess.svg
import cairosvg
def main():
# Replace with your PGN file path
pgn_file = "Caruana_vs_Kasparov_2017.pgn"
output_png = "kasparov_last_move.png"
script(pgn_file, output_png)
def script(pgn_file, output_png):
generate_last_position_png(pgn_file, output_png)
def generate_last_position_png(pgn_file, output_png):
# Load the PGN file
with open(pgn_file, "r", encoding="utf-8") as f:
game = chess.pgn.read_game(f)
# Play through the game to the end
board = game.board()
for move in game.mainline_moves():
board.push(move)
# Get the last move
last_move = board.peek()
print("Last move played:", last_move)
# Create SVG highlighting the last move
svg_data = chess.svg.board(board, lastmove=last_move, size=400)
# Convert SVG to PNG
cairosvg.svg2png(bytestring=svg_data.encode("utf-8"), write_to=output_png)
print(f"Final position saved as {output_png}")
def main():
# Replace with your PGN file path
pgn_file = "Caruana_vs_Kasparov_2017.pgn"
output_png = "kasparov_last_move.png"
generate_last_position_png(pgn_file, output_png)
if __name__ == "__main__":
main()

Caruana vs Kasparov -- Saint Louis Blitz 2017

Video Reference

Game Record

  • Event: Saint Louis Blitz 2017
  • Round: 12.3
  • Players: Caruana (White) vs Kasparov (Black)
  • Opening: Closed Sicilian (ECO B23)
  • Result: 0-1 (Kasparov wins)
  • PGN record: 365Chess game page

Opening Classification

  • ECO Code B23 -> Sicilian Defense, Closed Variation / Grand Prix Attack
  • Defined by: 1. e4 c5 2. Nc3
  • Style: White builds slowly with f4, g3, Bg2; Black counters dynamically.
  • Reference: ChessOpenings.com entry for B23

Step-by-Step Timeline Method

Since PGN does not store times, reconstruct them from the video:

  1. Open the broadcast.
  2. Pause after each move to note both clocks.
  3. Record in a table: Move | Caruana's clock | Kasparov's clock | Notes.
  4. Highlight tempo shifts: Caruana burns time in buildup, Kasparov plays faster and keeps a time edge.

Example Timeline (first moves)

Move Caruana (White) Kasparov (Black) Notes
1. e4 e6 5:00 5:00 Opening French setup
2. d4 d5 4:58 4:59 Both still fast
3. Nc3 Nf6 4:55 4:58 Caruana thinking slightly longer
4. e5 Nfd7 4:50 4:57 Kasparov plays instantly
5. f4 c5 4:40 4:55 Caruana burns time, Kasparov strikes quickly

Why This Matters

  • Kasparov's edge -> He kept a time advantage by relying on intuition.
  • Caruana's struggle -> His precise style cost him seconds, and blitz magnifies that.
  • Tempo analogy -> The clocks are like BPM markers in a live set, Kasparov kept the rhythm driving forward, while Caruana slowed down in the buildup.

Sources

Refer to new/document-word in this repo.

MIT License
Copyright (c) 2025 Henrique Moreira
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
to_html.py
Collect all SVG files and generate a simple HTML viewer to step through moves.
"""
import os
def main():
svg_dir = "svgs_all_moves" # directory containing move_000.svg, move_001.svg, ...
do_script(svg_dir)
def do_script(svg_dir):
""" Main stuff! """
res = build_html(svg_dir, output_html="index.html")
output_html, html_lines, svg_files = res
assert html_lines, "html_lines"
print(f"HTML viewer written to {output_html}:", svg_files)
def build_html(svg_dir, output_html="index.html"):
# Collect all SVG files in order
svg_files = sorted([f for f in os.listdir(svg_dir) if f.endswith(".svg")])
# Convert Python list into proper JavaScript array
str_moves = ",".join(f'"{svg_dir}/{f}"' for f in svg_files)
moves_js = "[" + str_moves + "]"
# Build HTML content
html_lines = [
"<!DOCTYPE html>",
"<html>",
"<head>",
" <meta charset='UTF-8'>",
" <title>Kasparov vs Caruana – SVG Viewer</title>",
" <style>",
" body { font-family: sans-serif; text-align: center; }",
" img { border: 1px solid #ccc; margin: 10px; }",
" .controls { margin: 20px; }",
" </style>",
"</head>",
"<body>",
" <h1>Kasparov vs Caruana – Saint Louis Blitz 2017</h1>",
" <div class='controls'>",
" <button onclick='prevMove()'>Previous</button>",
" <button onclick='nextMove()'>Next</button>",
" <button onclick='toggleAutoPlay()'>Auto Play</button>",
" <p id='moveLabel'></p>",
" </div>",
" <div>",
" <img id='board' src='' alt='Chess board'>",
" </div>",
" <script>",
f" const moves = {moves_js};",
" let index = 0;",
" let autoPlayInterval = null;",
"",
" function showMove(i) {",
" document.getElementById('board').src = moves[i];",
" document.getElementById('moveLabel').innerText = 'Move ' + i;",
" }",
"",
" function prevMove() {",
" if (index > 0) { index--; showMove(index); }",
" }",
"",
" function nextMove() {",
" if (index < moves.length - 1) { index++; showMove(index); }",
" }",
"",
" function toggleAutoPlay() {",
" if (autoPlayInterval) {",
" clearInterval(autoPlayInterval);",
" autoPlayInterval = null;",
" } else {",
" autoPlayInterval = setInterval(() => {",
" if (index < moves.length - 1) {",
" index++;",
" showMove(index);",
" } else {",
" clearInterval(autoPlayInterval);",
" autoPlayInterval = null;",
" }",
" }, 2000); // advance every 2 seconds",
" }",
" }",
"",
" // Initialize",
" showMove(index);",
" </script>",
"</body>",
"</html>"
]
with open(output_html, "w", encoding="utf-8") as f:
f.write("\n".join(html_lines) + "\n")
return output_html, html_lines, svg_files
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment