Skip to content

Commit 6f2955e

Browse files
Bobainclaude
andcommitted
Add resizable window support and refactor project structure
- Add LayoutManager for dynamic layout calculations based on window size - Reorganize project into modular structure (rendering/, core/, engine/, input/) - Rename constants to follow style guide with explicit names (e.g., CHESS_BOARD_SIZE_PIXELS) - Window now supports free resizing with mouse, maintaining proportions - Board stays square and properly positioned during resize - Fonts scale proportionally with window size - Bump version to 0.1.3 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 38991a9 commit 6f2955e

19 files changed

+666
-453
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "python-chess-gui"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "A Python chess GUI application using pygame and python-chess with Stockfish support"
55
readme = "README.md"
66
requires-python = ">=3.12"

src/python_chess_gui/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
"""Python Chess GUI - A chess application with pygame interface."""
22

3-
__version__ = "0.1.0"
3+
__version__ = "0.1.3"
4+
5+
from python_chess_gui.main import main, ChessApplication
6+
from python_chess_gui.layout_manager import LayoutManager
7+
8+
__all__ = [
9+
"main",
10+
"ChessApplication",
11+
"LayoutManager",
12+
]

src/python_chess_gui/constants.py

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
11
"""Configuration constants for the chess GUI application."""
22

3-
# Window dimensions
4-
BOARD_SIZE = 640
5-
SQUARE_SIZE = BOARD_SIZE // 8
6-
EVAL_BAR_WIDTH = 40
7-
SIDEBAR_WIDTH = 160
8-
STATUS_BAR_HEIGHT = 50
9-
CONTROL_BAR_HEIGHT = 50
3+
# Window dimensions (in pixels)
4+
CHESS_BOARD_SIZE_PIXELS = 640
5+
BOARD_SQUARE_SIZE_PIXELS = CHESS_BOARD_SIZE_PIXELS // 8
6+
EVALUATION_BAR_WIDTH_PIXELS = 40
7+
SIDEBAR_WIDTH_PIXELS = 160
8+
STATUS_BAR_HEIGHT_PIXELS = 50
9+
CONTROL_BAR_HEIGHT_PIXELS = 50
1010

11-
WINDOW_WIDTH = BOARD_SIZE + SIDEBAR_WIDTH
12-
WINDOW_HEIGHT = STATUS_BAR_HEIGHT + BOARD_SIZE + CONTROL_BAR_HEIGHT
11+
MAIN_WINDOW_WIDTH_PIXELS = CHESS_BOARD_SIZE_PIXELS + SIDEBAR_WIDTH_PIXELS
12+
MAIN_WINDOW_HEIGHT_PIXELS = STATUS_BAR_HEIGHT_PIXELS + CHESS_BOARD_SIZE_PIXELS + CONTROL_BAR_HEIGHT_PIXELS
1313

14-
# Board position offset (from top-left of window)
15-
BOARD_OFFSET_X = 0
16-
BOARD_OFFSET_Y = STATUS_BAR_HEIGHT
14+
# Board position offset (from top-left of window, in pixels)
15+
BOARD_OFFSET_X_PIXELS = 0
16+
BOARD_OFFSET_Y_PIXELS = STATUS_BAR_HEIGHT_PIXELS
1717

18-
# Colors (RGB)
19-
COLOR_LIGHT_SQUARE = (240, 217, 181) # Tan
20-
COLOR_DARK_SQUARE = (181, 136, 99) # Brown
21-
COLOR_BACKGROUND = (49, 46, 43) # Dark gray
22-
COLOR_WHITE = (255, 255, 255)
23-
COLOR_BLACK = (0, 0, 0)
24-
COLOR_SELECTED = (186, 202, 68, 180) # Yellow-green highlight
25-
COLOR_LEGAL_MOVE = (130, 151, 105) # Green dot for legal moves
26-
COLOR_LAST_MOVE = (205, 210, 106, 128) # Yellow highlight for last move
27-
COLOR_CHECK = (235, 97, 80) # Red for check
28-
COLOR_HINT = (72, 150, 220, 200) # Blue highlight for hint move
29-
COLOR_BUTTON = (70, 70, 70) # Button background
30-
COLOR_BUTTON_SELECTED = (76, 154, 42) # Green for selected button
31-
COLOR_BUTTON_HOVER = (100, 100, 100) # Button hover
32-
COLOR_TEXT = (255, 255, 255) # White text
33-
COLOR_EVAL_WHITE = (255, 255, 255) # White side of eval bar
34-
COLOR_EVAL_BLACK = (0, 0, 0) # Black side of eval bar
35-
COLOR_MENU_BG = (39, 37, 34) # Menu background
18+
# Colors (RGB/RGBA tuples)
19+
LIGHT_SQUARE_COLOR_RGB = (240, 217, 181) # Tan
20+
DARK_SQUARE_COLOR_RGB = (181, 136, 99) # Brown
21+
WINDOW_BACKGROUND_COLOR_RGB = (49, 46, 43) # Dark gray
22+
WHITE_COLOR_RGB = (255, 255, 255)
23+
BLACK_COLOR_RGB = (0, 0, 0)
24+
SELECTED_SQUARE_HIGHLIGHT_RGBA = (186, 202, 68, 180) # Yellow-green highlight
25+
LEGAL_MOVE_INDICATOR_COLOR_RGB = (130, 151, 105) # Green dot for legal moves
26+
LAST_MOVE_HIGHLIGHT_RGBA = (205, 210, 106, 128) # Yellow highlight for last move
27+
CHECK_HIGHLIGHT_COLOR_RGB = (235, 97, 80) # Red for check
28+
HINT_MOVE_HIGHLIGHT_RGBA = (72, 150, 220, 200) # Blue highlight for hint move
29+
BUTTON_BACKGROUND_COLOR_RGB = (70, 70, 70) # Button background
30+
BUTTON_SELECTED_COLOR_RGB = (76, 154, 42) # Green for selected button
31+
BUTTON_HOVER_COLOR_RGB = (100, 100, 100) # Button hover
32+
TEXT_COLOR_RGB = (255, 255, 255) # White text
33+
EVAL_BAR_WHITE_COLOR_RGB = (255, 255, 255) # White side of eval bar
34+
EVAL_BAR_BLACK_COLOR_RGB = (0, 0, 0) # Black side of eval bar
35+
MENU_BACKGROUND_COLOR_RGB = (39, 37, 34) # Menu background
3636

3737
# Fonts
3838
FONT_NAME = None # Use default system font
@@ -60,8 +60,8 @@
6060
}
6161

6262
# Stockfish configuration
63-
STOCKFISH_DEPTH = 15 # Analysis depth
64-
STOCKFISH_MOVE_TIME = 1.0 # Seconds to think for AI moves
63+
STOCKFISH_ANALYSIS_DEPTH = 15 # Analysis depth
64+
STOCKFISH_MOVE_TIME_SECONDS = 1.0 # Seconds to think for AI moves
6565

6666
# AI difficulty presets (Elo ratings)
6767
# Note: Stockfish 17+ requires minimum Elo of 1320
@@ -76,11 +76,11 @@
7676
DEFAULT_DIFFICULTY = "Level 3"
7777

7878
# Evaluation bar scaling
79-
EVAL_MAX_CENTIPAWNS = 1000 # +/- 10 pawns = max bar
80-
EVAL_MATE_SCORE = 10000 # Score to use for mate positions
79+
EVALUATION_MAX_CENTIPAWNS = 1000 # +/- 10 pawns = max bar
80+
EVALUATION_MATE_SCORE = 10000 # Score to use for mate positions
8181

8282
# Frame rate
83-
FPS = 60
83+
FRAMES_PER_SECOND = 60
8484

8585
# Coordinate labels
8686
FILE_LABELS = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Core module for chess game logic and utilities."""
2+
3+
from python_chess_gui.core.game_state_manager import GameStateManager
4+
from python_chess_gui.core.coordinate_converter import (
5+
convert_screen_position_to_board_square,
6+
convert_board_square_to_screen_position,
7+
get_square_center,
8+
)
9+
10+
__all__ = [
11+
"GameStateManager",
12+
"convert_screen_position_to_board_square",
13+
"convert_board_square_to_screen_position",
14+
"get_square_center",
15+
]

src/python_chess_gui/coordinate_converter.py renamed to src/python_chess_gui/core/coordinate_converter.py

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,53 @@
22

33
import chess
44

5-
from python_chess_gui.constants import (
6-
BOARD_OFFSET_X,
7-
BOARD_OFFSET_Y,
8-
SQUARE_SIZE,
9-
)
5+
from python_chess_gui.layout_manager import LayoutManager
106

117

128
def convert_screen_position_to_board_square(
13-
screen_x: int, screen_y: int, player_is_white: bool
9+
screen_x: int, screen_y: int, player_is_white: bool, layout: LayoutManager
1410
) -> chess.Square | None:
1511
"""Convert screen coordinates to a chess square.
1612
1713
Args:
1814
screen_x: X coordinate on screen
1915
screen_y: Y coordinate on screen
2016
player_is_white: True if player is playing white (white at bottom)
17+
layout: Layout manager for dimension calculations
2118
2219
Returns:
2320
Chess square index (0-63) or None if outside board
2421
"""
25-
# Adjust for board offset
26-
board_x = screen_x - BOARD_OFFSET_X
27-
board_y = screen_y - BOARD_OFFSET_Y
22+
board_x = screen_x - layout.board_offset_x
23+
board_y = screen_y - layout.board_offset_y
2824

29-
# Check if within board bounds
30-
if board_x < 0 or board_x >= SQUARE_SIZE * 8:
25+
if board_x < 0 or board_x >= layout.square_size * 8:
3126
return None
32-
if board_y < 0 or board_y >= SQUARE_SIZE * 8:
27+
if board_y < 0 or board_y >= layout.square_size * 8:
3328
return None
3429

35-
# Convert to file and rank indices (0-7)
36-
file_idx = board_x // SQUARE_SIZE
37-
rank_idx = board_y // SQUARE_SIZE
30+
file_idx = board_x // layout.square_size
31+
rank_idx = board_y // layout.square_size
3832

39-
# If player is white, board is oriented with rank 8 at top (row 0)
40-
# If player is black, board is flipped
4133
if player_is_white:
42-
# White at bottom: rank 8 at top (rank_idx 0 = rank 7)
4334
file = file_idx
4435
rank = 7 - rank_idx
4536
else:
46-
# Black at bottom: rank 1 at top (rank_idx 0 = rank 0)
4737
file = 7 - file_idx
4838
rank = rank_idx
4939

5040
return chess.square(file, rank)
5141

5242

5343
def convert_board_square_to_screen_position(
54-
square: chess.Square, player_is_white: bool
44+
square: chess.Square, player_is_white: bool, layout: LayoutManager
5545
) -> tuple[int, int]:
5646
"""Convert a chess square to screen coordinates (top-left of square).
5747
5848
Args:
5949
square: Chess square index (0-63)
6050
player_is_white: True if player is playing white (white at bottom)
51+
layout: Layout manager for dimension calculations
6152
6253
Returns:
6354
Tuple of (x, y) screen coordinates for top-left corner of square
@@ -66,29 +57,30 @@ def convert_board_square_to_screen_position(
6657
rank = chess.square_rank(square)
6758

6859
if player_is_white:
69-
# White at bottom: file a on left, rank 8 at top
7060
screen_col = file
7161
screen_row = 7 - rank
7262
else:
73-
# Black at bottom: file h on left, rank 1 at top
7463
screen_col = 7 - file
7564
screen_row = rank
7665

77-
x = BOARD_OFFSET_X + screen_col * SQUARE_SIZE
78-
y = BOARD_OFFSET_Y + screen_row * SQUARE_SIZE
66+
x = layout.board_offset_x + screen_col * layout.square_size
67+
y = layout.board_offset_y + screen_row * layout.square_size
7968

8069
return (x, y)
8170

8271

83-
def get_square_center(square: chess.Square, player_is_white: bool) -> tuple[int, int]:
72+
def get_square_center(
73+
square: chess.Square, player_is_white: bool, layout: LayoutManager
74+
) -> tuple[int, int]:
8475
"""Get the center coordinates of a chess square on screen.
8576
8677
Args:
8778
square: Chess square index (0-63)
8879
player_is_white: True if player is playing white (white at bottom)
80+
layout: Layout manager for dimension calculations
8981
9082
Returns:
9183
Tuple of (x, y) screen coordinates for center of square
9284
"""
93-
x, y = convert_board_square_to_screen_position(square, player_is_white)
94-
return (x + SQUARE_SIZE // 2, y + SQUARE_SIZE // 2)
85+
x, y = convert_board_square_to_screen_position(square, player_is_white, layout)
86+
return (x + layout.square_size // 2, y + layout.square_size // 2)

src/python_chess_gui/game_state_manager.py renamed to src/python_chess_gui/core/game_state_manager.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ def select_square(self, square: chess.Square) -> list[chess.Move]:
3131
"""
3232
piece = self.board.piece_at(square)
3333

34-
# If there's a piece of the current turn's color, select it
3534
if piece is not None and piece.color == self.board.turn:
3635
self.selected_square = square
3736
return self.get_legal_moves_from_square(square)
@@ -59,16 +58,14 @@ def try_move(self, from_square: chess.Square, to_square: chess.Square) -> chess.
5958
Returns:
6059
The move if successful, None if illegal
6160
"""
62-
# Check for promotion
6361
piece = self.board.piece_at(from_square)
6462
promotion = None
6563

6664
if piece is not None and piece.piece_type == chess.PAWN:
67-
# Check if pawn is reaching the back rank
6865
if piece.color == chess.WHITE and chess.square_rank(to_square) == 7:
69-
promotion = chess.QUEEN # Auto-promote to queen
66+
promotion = chess.QUEEN
7067
elif piece.color == chess.BLACK and chess.square_rank(to_square) == 0:
71-
promotion = chess.QUEEN # Auto-promote to queen
68+
promotion = chess.QUEEN
7269

7370
move = chess.Move(from_square, to_square, promotion=promotion)
7471

@@ -79,7 +76,6 @@ def try_move(self, from_square: chess.Square, to_square: chess.Square) -> chess.
7976
self.selected_square = None
8077
return move
8178

82-
# Try without promotion (for non-pawn moves that might match)
8379
move_no_promo = chess.Move(from_square, to_square)
8480
if move_no_promo in self.board.legal_moves:
8581
self.board.push(move_no_promo)
@@ -129,10 +125,8 @@ def undo_move_pair(self) -> bool:
129125
True if moves were undone, False if not enough moves
130126
"""
131127
if len(self.move_history) < 2:
132-
# If only one move, undo just that one
133128
return self.undo_move()
134129

135-
# Undo two moves
136130
self.undo_move()
137131
self.undo_move()
138132
return True
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Engine module for Stockfish integration."""
2+
3+
from python_chess_gui.engine.stockfish_engine_controller import StockfishEngineController
4+
from python_chess_gui.engine.config_manager import (
5+
find_stockfish,
6+
get_stockfish_path,
7+
set_stockfish_path,
8+
load_config,
9+
save_config,
10+
)
11+
12+
__all__ = [
13+
"StockfishEngineController",
14+
"find_stockfish",
15+
"get_stockfish_path",
16+
"set_stockfish_path",
17+
"load_config",
18+
"save_config",
19+
]

src/python_chess_gui/config_manager.py renamed to src/python_chess_gui/engine/config_manager.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,25 +95,21 @@ def find_stockfish() -> str | None:
9595
Returns:
9696
Path to Stockfish executable, or None if not found
9797
"""
98-
# First, check saved config
9998
config_path = get_stockfish_path()
10099
if config_path:
101100
return config_path
102101

103-
# Second, check environment variable
104102
env_path = os.environ.get("STOCKFISH_PATH")
105103
if env_path and Path(env_path).exists():
106104
return env_path
107105

108-
# Third, try to find in PATH
109106
stockfish_in_path = shutil.which("stockfish")
110107
if stockfish_in_path:
111108
return stockfish_in_path
112109

113-
# Fourth, check platform-specific common locations
114110
system = platform.system()
115111

116-
if system == "Darwin": # macOS
112+
if system == "Darwin":
117113
common_paths = [
118114
"/opt/homebrew/bin/stockfish",
119115
"/usr/local/bin/stockfish",

0 commit comments

Comments
 (0)