Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions components/image_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ def __init__(self):
class RestartButton(ImageButton):
def __init__(self):
super().__init__(resource_path("assets/icons/restart_button_icon.png"), 30, 30)

# Cache both enabled and disabled images
self.enabled_image = self.image
disabled_img = pygame.image.load(resource_path("assets/icons/restart_button_icon_disabled.png")).convert_alpha()
self.disabled_image = pygame.transform.smoothscale(disabled_img, (30, 30))

def disable(self):
self.image = pygame.image.load(resource_path("assets/icons/restart_button_icon_disabled.png")).convert_alpha()
self.image = pygame.transform.smoothscale(self.image, (30, 30))
self.image = self.disabled_image
self.disabled = True

def enable(self):
self.image = pygame.image.load(resource_path("assets/icons/restart_button_icon.png")).convert_alpha()
self.image = pygame.transform.smoothscale(self.image, (30, 30))
self.image = self.enabled_image
self.disabled = False
14 changes: 11 additions & 3 deletions components/popup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def __init__(self, screen, message, button_type="ok", size=(300, 200), callbacks
self.callbacks = callbacks if callbacks else {}
self.buttons = self.create_buttons()
self.visible = False

# Cache the overlay surface to avoid creating it every frame
self.overlay = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA)
self.overlay.fill((0, 0, 0, 180)) # Black with alpha for transparency



Expand Down Expand Up @@ -54,10 +58,14 @@ def draw(self):
if not self.visible:
return

overlay = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180)) # Black with alpha for transparency
# Check if screen size has changed and recreate overlay if needed
current_size = self.screen.get_size()
if self.overlay.get_size() != current_size:
self.overlay = pygame.Surface(current_size, pygame.SRCALPHA)
self.overlay.fill((0, 0, 0, 180))

self.screen.blit(overlay, (0, 0))
# Use the cached overlay surface
self.screen.blit(self.overlay, (0, 0))
self.screen.blit(self.text, self.text_rect.move(self.popup_rect.topleft))

for text, button, _ in self.buttons:
Expand Down
60 changes: 40 additions & 20 deletions utils/board_pieces_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
from components.popup import Popup
from constants.fonts import CHECK_MATETEXT_MAIN

# Global cache for circle surfaces used in movement indicators
_circle_surface_cache = {}


def get_possible_positions(piece, color, board, x, y, king_moved, rook1_moved, rook2_moved):
# Adjust positions based on player perspective
Expand All @@ -31,17 +34,31 @@ def get_possible_positions(piece, color, board, x, y, king_moved, rook1_moved, r
moves = []

# If the king is under check, filter moves to only include those that prevent check
# Optimize by storing original piece and using in-place modifications
valid_moves = []
opponent_color = "white" if color == "black" else "black"
original_piece = board[y-1][x-1]

piece_code = f"{color[0]}{piece.piece_type.name[0]}"
if piece.piece_type == PieceType.KNIGHT:
piece_code = f"{color[0]}N"
piece_code = piece_code.upper()

for move in moves:
new_board = [row[:] for row in board] # Create a copy of the board
new_board[y-1][x-1] = "" # Remove the piece from the original position
piece_code = f"{color[0]}{piece.piece_type.name[0]}"
if piece.piece_type == PieceType.KNIGHT:
piece_code = f"{color[0]}N"
new_board[move[1]-1][move[0]-1] = piece_code.upper() # Place the piece in the new position
if not is_check(new_board, "white" if color == "black" else "black")[0]:

# Store the target square's original piece
target_piece = board[move[1]-1][move[0]-1]

# Make the move temporarily on the actual board
board[y-1][x-1] = ""
board[move[1]-1][move[0]-1] = piece_code

# Check if this move prevents check
if not is_check(board, opponent_color)[0]:
valid_moves.append(move)

# Restore the board state
board[y-1][x-1] = original_piece
board[move[1]-1][move[0]-1] = target_piece

moves = valid_moves

Expand Down Expand Up @@ -122,11 +139,13 @@ def _draw_rectangle(self, x, y, color=(105, 176, 50)):
pygame.draw.rect(self.screen, color, (x, y, self.square_size, self.square_size), 4)

def _no_move_left(self):
if not self.is_under_check: return
if not self.is_under_check:
return False
for piece in self.pieces:
if piece[0].piece_color.name.lower() == self.turn:
moves = get_possible_positions(piece[0], piece[0].piece_color.name.lower(), self.layout, piece[1], piece[2], False, False, False)
if moves: return False
if moves:
return False

return True

Expand All @@ -138,15 +157,19 @@ def _draw_circle(self, x, y):
x_center = (x - 1) * self.square_size + self.square_size // 2
y_center = (y - 1) * self.square_size + self.square_size // 2 + self.board_top_bar_height

# Create a higher resolution surface (4 times the original size)
high_res_size = self.square_size * 4
high_res_surface = pygame.Surface((high_res_size, high_res_size), pygame.SRCALPHA)
# Check if we have a cached circle surface for this size
if self.square_size not in _circle_surface_cache:
# Create a higher resolution surface (4 times the original size)
high_res_size = self.square_size * 4
high_res_surface = pygame.Surface((high_res_size, high_res_size), pygame.SRCALPHA)

# Draw the circle on the high resolution surface
pygame.draw.circle(high_res_surface, (105, 176, 50), (high_res_size // 2, high_res_size // 2), high_res_size // 6)

# Draw the circle on the high resolution surface
pygame.draw.circle(high_res_surface, (105, 176, 50), (high_res_size // 2, high_res_size // 2), high_res_size // 6)
# Scale the high resolution surface down to the original size and cache it
_circle_surface_cache[self.square_size] = pygame.transform.smoothscale(high_res_surface, (self.square_size, self.square_size))

# Scale the high resolution surface down to the original size
scaled_surface = pygame.transform.smoothscale(high_res_surface, (self.square_size, self.square_size))
scaled_surface = _circle_surface_cache[self.square_size]

# Blit the scaled surface onto the main screen
self.screen.blit(scaled_surface, (x_center - self.square_size // 2, y_center - self.square_size // 2))
Expand Down Expand Up @@ -311,9 +334,6 @@ def display(self):

if self.is_check_mate: self._draw_checkmate_popup()
self.reset_popup.draw()

# Update the display once after all drawing operations
pygame.display.flip()

# Handle the event
if self.event:
Expand Down
123 changes: 51 additions & 72 deletions utils/movements/king.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,56 +82,36 @@ def king_moves(board, color, x, y, king_moved, rook1_moved, rook2_moved):
# Castling logic
if not king_moved:
if color == 'white':
# Check if king is currently under attack (common check for both castling types)
king_not_attacked = not is_square_attacked(board, 5, 8, 'black')

# Kingside castling
if not rook2_moved and board[7][5] == "" and board[7][6] == "" and \
not is_square_attacked(board, 5 + 1, 8, 'black') and \
not is_square_attacked(board, 6 + 1, 8, 'black') and \
not is_square_attacked(board, 4 + 1, 8, 'black'): # Check current square
if king_not_attacked and not rook2_moved and board[7][5] == "" and board[7][6] == "" and \
not is_square_attacked(board, 6, 8, 'black') and \
not is_square_attacked(board, 7, 8, 'black'):
moves.append((7, 8))
# Queenside castling
if not rook1_moved and board[7][1] == "" and board[7][2] == "" and board[7][3] == "" and \
not is_square_attacked(board, 3 + 1, 8, 'black') and \
not is_square_attacked(board, 4 + 1, 8, 'black') and \
not is_square_attacked(board, 4 + 1, 8, 'black'): # Check current square
if king_not_attacked and not rook1_moved and board[7][1] == "" and board[7][2] == "" and board[7][3] == "" and \
not is_square_attacked(board, 4, 8, 'black') and \
not is_square_attacked(board, 3, 8, 'black'):
moves.append((3, 8))
else:
# Check if king is currently under attack (common check for both castling types)
king_not_attacked = not is_square_attacked(board, 5, 1, 'white')

# Kingside castling
if not rook2_moved and board[0][5] == "" and board[0][6] == "" and \
not is_square_attacked(board, 5 + 1, 1, 'white') and \
not is_square_attacked(board, 6 + 1, 1, 'white') and \
not is_square_attacked(board, 4 + 1, 1, 'white'): # Check current square
if king_not_attacked and not rook2_moved and board[0][5] == "" and board[0][6] == "" and \
not is_square_attacked(board, 6, 1, 'white') and \
not is_square_attacked(board, 7, 1, 'white'):
moves.append((7, 1))
# Queenside castling
if not rook1_moved and board[0][1] == "" and board[0][2] == "" and board[0][3] == "" and \
not is_square_attacked(board, 3 + 1, 1, 'white') and \
not is_square_attacked(board, 4 + 1, 1, 'white') and \
not is_square_attacked(board, 4 + 1, 1, 'white'): # Check current square
if king_not_attacked and not rook1_moved and board[0][1] == "" and board[0][2] == "" and board[0][3] == "" and \
not is_square_attacked(board, 4, 1, 'white') and \
not is_square_attacked(board, 3, 1, 'white'):
moves.append((3, 1))

return moves



def flatten(nested_list):
"""
Flattens a nested list into a single list, safely handling deeply nested structures.

Args:
nested_list (list): The list to flatten.

Returns:
list: A single flat list with all elements.
"""
result = []
stack = [nested_list]
while stack:
current = stack.pop()
if isinstance(current, list):
stack.extend(reversed(current)) # Add elements in reverse order to process correctly
else:
result.append(current)
return result

def is_check(board, color):
"""
Checks if the given color is in check.
Expand All @@ -141,51 +121,50 @@ def is_check(board, color):
color (str): The color to check ('white' or 'black').

Returns:
bool: True if the color is in check, False otherwise.
tuple: (bool, tuple) - True if the color is in check and the king's position.
"""


king_pos = None
color = "white" if color == "black" else "black"

# Find king position
for y, row in enumerate(board):
for x, piece in enumerate(row):
if piece.lower() == f"{color[0]}k":

king_pos = (x + 1, y + 1)

# for row in board:
# for piece in row:
# elm = " "
# if piece != "":
# elm = piece
# print(elm, end=" ")
# print()
# print("\n","#"*50,"\n")
break
if king_pos:
break

opponent_color = 'white' if color == 'black' else 'black'
opponents_possible_moves = []
i = -1
for p in flatten(board):
i+=1
if p and p[0].lower() == opponent_color[0]:
piece_type = p[1].lower()

x = i%8 + 1
y = i//8 + 1

# Directly iterate over board without flattening
for y, row in enumerate(board):
for x, piece in enumerate(row):
if piece and piece[0].lower() == opponent_color[0]:
piece_type = piece[1].lower()
# Convert to 1-indexed coordinates
pos_x = x + 1
pos_y = y + 1

if piece_type == 'p':
opponents_possible_moves.extend(pawn_moves(board, opponent_color, x, y))
elif piece_type == 'r':
opponents_possible_moves.extend(rook_moves(board, opponent_color, x, y))
elif piece_type == 'n':
opponents_possible_moves.extend(knight_moves(board, opponent_color, x, y))
elif piece_type == 'b':
opponents_possible_moves.extend(bishop_moves(board, opponent_color, x, y))
elif piece_type == 'q':
opponents_possible_moves.extend(queen_moves(board, opponent_color, x, y))
elif piece_type == 'k':
opponents_possible_moves.extend(king_moves(board, opponent_color, x, y, False, False, False))
# Get moves for this piece
moves = []
if piece_type == 'p':
moves = pawn_moves(board, opponent_color, pos_x, pos_y)
elif piece_type == 'r':
moves = rook_moves(board, opponent_color, pos_x, pos_y)
elif piece_type == 'n':
moves = knight_moves(board, opponent_color, pos_x, pos_y)
elif piece_type == 'b':
moves = bishop_moves(board, opponent_color, pos_x, pos_y)
elif piece_type == 'q':
moves = queen_moves(board, opponent_color, pos_x, pos_y)
elif piece_type == 'k':
moves = king_moves(board, opponent_color, pos_x, pos_y, False, False, False)

# Early return if king is in the attack range of this piece
if king_pos in moves:
return True, king_pos

return (king_pos in opponents_possible_moves), king_pos
return False, king_pos

31 changes: 23 additions & 8 deletions utils/pieces.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@ class PieceColor(Enum):
BLACK = 0
WHITE = 1

# Global cache for the chess pieces image to avoid repeated loading
_piece_image_cache = None

# Global cache for pre-scaled piece surfaces
_scaled_piece_cache = {}

class Piece:
def __init__(self, screen: pygame.Surface, square_size: int, player: str, piece_type: PieceType, piece_color: PieceColor):
global _piece_image_cache

self.screen = screen
self.square_size = square_size
self.player = player
Expand All @@ -24,7 +32,11 @@ def __init__(self, screen: pygame.Surface, square_size: int, player: str, piece_

self.piece_width = 128 # Assuming each piece is 128x128 pixels
self.piece_height = 128
self.image = pygame.image.load(resource_path("assets/chess_pieces_edited.png")).convert_alpha()

# Load the image only once and cache it
if _piece_image_cache is None:
_piece_image_cache = pygame.image.load(resource_path("assets/chess_pieces_edited.png")).convert_alpha()
self.image = _piece_image_cache

def _extract_piece(self):
"""
Expand Down Expand Up @@ -63,13 +75,16 @@ def display(self, x, y, board_top_bar_height: int, absolute_coordinates: bool =
x = (x - 1) * self.square_size
y = (y - 1) * self.square_size + board_top_bar_height

piece = self._extract_piece()

# Calculate the new size for the piece
new_size = (self.square_size, self.square_size)

# Resize the piece using smoothscale for better quality
resized_piece = pygame.transform.smoothscale(piece, new_size)
# Create a cache key based on piece type, color, and size
cache_key = (self.piece_type.value, self.piece_color.value, self.square_size)

# Check if this piece at this size is already cached
if cache_key not in _scaled_piece_cache:
piece = self._extract_piece()
new_size = (self.square_size, self.square_size)
_scaled_piece_cache[cache_key] = pygame.transform.smoothscale(piece, new_size)

resized_piece = _scaled_piece_cache[cache_key]

# Display the resized piece on the screen
self.screen.blit(resized_piece, (x, y))