-
-
Notifications
You must be signed in to change notification settings - Fork 556
Open
Description
Could you consider adding the following functionality?
Optional interactive visualization feature (using pygame) that allows users to replay and browse through a chess.Board’s move_stack in a graphical board interface — stepping forward/backward with arrow keys, and jumping to the start or end of the game.
Example code:
"""
Chess move-stack visualizer using pygame.
Usage:
- Place piece PNGs in a folder called 'chess_pieces' next to this script. Filenames must be:
wP.png wN.png wB.png wR.png wQ.png wK.png
bP.png bN.png bB.png bR.png bQ.png bK.png
- Run: run_replay_from_board(board)
Controls:
- Right Arrow: advance one move
- Left Arrow: go back one move
- Ctrl + Right Arrow: jump to end
- Ctrl + Left Arrow: jump to start
- Space: toggle auto-play (1s interval)
- Esc: quit
"""
import sys
import os
import time
import pygame
import chess
import chess.pgn
# --- Configuration ---
SQUARE_SIZE = 80
BOARD_MARGIN = 20
FPS = 60
AUTOPLAY_INTERVAL = 1.0
PIECE_SHEET = "chess_pieces"
PIECE_FILENAMES = {
"P": "wp.png", "N": "wn.png", "B": "wb.png", "R": "wr.png", "Q": "wq.png", "K": "wk.png",
"p": "bp.png", "n": "bn.png", "b": "bb.png", "r": "br.png", "q": "bq.png", "k": "bk.png",
}
LIGHT_SQ = (240, 217, 181)
DARK_SQ = (181, 136, 99)
def load_piece_images(square_size):
"""Load and scale piece images from folder."""
images = {}
for sym, fname in PIECE_FILENAMES.items():
path = os.path.join(PIECE_SHEET, fname)
if not os.path.exists(path):
raise FileNotFoundError(f"Missing piece image: {path}")
img = pygame.image.load(path).convert_alpha()
img = pygame.transform.smoothscale(img, (square_size, square_size))
images[sym] = img
return images
def draw_board(board, images, square_size):
"""Draw the current board position and return a pygame.Surface."""
surf = pygame.Surface((8 * square_size, 8 * square_size))
for rank in range(8):
for file in range(8):
x = file * square_size
y = (7 - rank) * square_size
color = LIGHT_SQ if (file + rank) % 2 == 0 else DARK_SQ
pygame.draw.rect(surf, color, (x, y, square_size, square_size))
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece:
sym = piece.symbol()
img = images[sym]
file = chess.square_file(square)
rank = chess.square_rank(square)
x = file * square_size
y = (7 - rank) * square_size
surf.blit(img, (x, y))
return surf
def run_replay_from_board(src_board):
"""Visualize a chess.Board with a move_stack in pygame."""
moves = list(src_board.move_stack)
# Determine initial position (standard if not available)
initial_fen = getattr(src_board, "starting_fen", chess.STARTING_FEN)
board = chess.Board(fen=initial_fen)
current_index = 0
# Setup pygame
pygame.init()
pygame.key.set_repeat(175)
window_size = (8 * SQUARE_SIZE + 2 * BOARD_MARGIN, 8 * SQUARE_SIZE + 2 * BOARD_MARGIN + 30)
screen = pygame.display.set_mode(window_size)
pygame.display.set_caption("Chess Replay")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 24)
images = load_piece_images(SQUARE_SIZE)
board_surface = draw_board(board, images, SQUARE_SIZE)
autoplay = False
last_auto = time.time()
def set_index(target):
nonlocal current_index, board_surface
target = max(0, min(target, len(moves)))
if target > current_index:
for i in range(current_index, target):
board.push(moves[i])
elif target < current_index:
for _ in range(current_index - target):
board.pop()
current_index = target
board_surface = draw_board(board, images, SQUARE_SIZE)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
mods = pygame.key.get_mods()
ctrl = mods & pygame.KMOD_CTRL
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_SPACE:
autoplay = not autoplay
elif event.key == pygame.K_RIGHT:
if ctrl:
set_index(len(moves))
else:
set_index(current_index + 1)
elif event.key == pygame.K_LEFT:
if ctrl:
set_index(0)
else:
set_index(current_index - 1)
if autoplay and (time.time() - last_auto >= AUTOPLAY_INTERVAL):
if current_index < len(moves):
set_index(current_index + 1)
last_auto = time.time()
# Draw
screen.fill((40, 40, 40))
screen.blit(board_surface, (BOARD_MARGIN, BOARD_MARGIN))
txt = font.render(f"Move {current_index}/{len(moves)}", True, (255, 255, 255))
screen.blit(txt, (BOARD_MARGIN, 8 * SQUARE_SIZE + BOARD_MARGIN + 5))
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
Metadata
Metadata
Assignees
Labels
No labels