Skip to content

Commit

Permalink
chess bot
Browse files Browse the repository at this point in the history
  • Loading branch information
aidangollan committed Jan 13, 2025
1 parent e99bf21 commit 1b90f6e
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
venv/
__pycache__/
37 changes: 37 additions & 0 deletions board.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# board.py
import chess

class ChessBoard:
def __init__(self):
self.board = chess.Board()

def get_legal_moves(self):
"""Returns a list of legal moves in the current position."""
return list(self.board.legal_moves)

def make_move(self, move):
"""
Attempts to make a move on the board.
Returns True if successful, False if illegal.
"""
try:
if move in self.board.legal_moves:
self.board.push(move)
return True
return False
except:
return False

def is_game_over(self):
"""Returns True if the game is over."""
return self.board.is_game_over()

def get_board_state(self):
"""Returns the current board state."""
return self.board

def get_result(self):
"""Returns the game result if the game is over."""
if self.is_game_over():
return self.board.outcome()
return None
17 changes: 17 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import chess
from board import ChessBoard

class ChessBot:
def __init__(self):
pass

def get_move(self, board: ChessBoard):
"""
Given the current board state, returns the chosen move.
This is the main function students will implement.
"""
# For now, just return the first legal move
legal_moves = board.get_legal_moves()
if legal_moves:
return legal_moves[0]
return None
8 changes: 8 additions & 0 deletions current_board.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 101 additions & 0 deletions game.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# game.py
import chess
import chess.svg
from board import ChessBoard
from bot import ChessBot
from human import HumanPlayer
import pygame
import cairosvg
import io
from PIL import Image

IS_BOT = False # Set to False for human vs bot, True for bot vs bot

class ChessGame:
def __init__(self):
self.board = ChessBoard()

# Initialize players based on IS_BOT flag
if IS_BOT:
self.white_player = ChessBot()
self.black_player = ChessBot()
else:
self.white_player = HumanPlayer(chess.WHITE)
self.black_player = ChessBot()

# Initialize Pygame
pygame.init()
self.WINDOW_SIZE = 600
self.screen = pygame.display.set_mode((self.WINDOW_SIZE, self.WINDOW_SIZE))
pygame.display.set_caption("Chess Game")

def svg_to_pygame_surface(self, svg_string):
"""Convert SVG string to Pygame surface"""
png_data = cairosvg.svg2png(bytestring=svg_string.encode('utf-8'))
image = Image.open(io.BytesIO(png_data))
image = image.resize((self.WINDOW_SIZE, self.WINDOW_SIZE))
mode = image.mode
size = image.size
data = image.tobytes()
return pygame.image.fromstring(data, size, mode)

def display_board(self, last_move=None):
"""Display the current board state"""
# Create SVG with highlighted last move if exists
svg = chess.svg.board(
board=self.board.get_board_state(),
lastmove=last_move,
size=self.WINDOW_SIZE
)

# Convert SVG to Pygame surface and display
py_image = self.svg_to_pygame_surface(svg)
self.screen.blit(py_image, (0, 0))
pygame.display.flip()

def play_game(self):
"""Main game loop"""
last_move = None

while not self.board.is_game_over():
# Display current board
self.display_board(last_move)

# Determine current player
current_player = self.white_player if self.board.get_board_state().turn else self.black_player

# Get player's move
move = current_player.get_move(self.board)

if move is None:
print("Game ended by player")
break

# Make the move
if not self.board.make_move(move):
print(f"Illegal move attempted: {move}")
break

print(f"Move played: {move}")
last_move = move

# Add delay only for bot moves
if isinstance(current_player, ChessBot):
pygame.time.wait(1000) # 1 second delay

# Display final position
self.display_board(last_move)
result = self.board.get_result()
print(f"Game Over! Result: {result}")

# Keep window open until closed
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
break

pygame.quit()

if __name__ == "__main__":
game = ChessGame()
game.play_game()
112 changes: 112 additions & 0 deletions human.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import chess
import pygame

class HumanPlayer:
def __init__(self, color):
self.color = color
self.selected_square = None

def get_square_from_coords(self, x, y, flipped=False):
"""Convert screen coordinates to chess square."""
file_idx = x * 8 // 600
rank_idx = y * 8 // 600
if flipped:
file_idx = 7 - file_idx
rank_idx = 7 - rank_idx
else:
rank_idx = 7 - rank_idx
return chess.square(file_idx, rank_idx)

def is_promotion_move(self, board, from_square, to_square):
"""Check if the move would be a pawn promotion."""
piece = board.get_board_state().piece_at(from_square)
if piece and piece.piece_type == chess.PAWN:
rank = chess.square_rank(to_square)
return (self.color == chess.WHITE and rank == 7) or \
(self.color == chess.BLACK and rank == 0)
return False

def get_promotion_choice(self):
"""Get the promotion piece choice from the player."""
# Create a simple text-based menu for promotion choices
pygame.font.init()
font = pygame.font.Font(None, 36)
screen = pygame.display.get_surface()

pieces = {
'Q': (chess.QUEEN, "Queen"),
'R': (chess.ROOK, "Rook"),
'B': (chess.BISHOP, "Bishop"),
'K': (chess.KNIGHT, "Knight")
}
choices = []
y = 250

# Draw promotion options
for key, piece in pieces.items():
text = font.render(f"Press {key} for {piece[1]}", True, (255, 255, 255))
rect = text.get_rect(center=(300, y))
screen.blit(text, rect)
choices.append((piece, rect))
y += 50

pygame.display.flip()

# Wait for valid choice
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
return None
if event.type == pygame.KEYDOWN:
for key, piece_type in [
(pygame.K_q, chess.QUEEN),
(pygame.K_r, chess.ROOK),
(pygame.K_b, chess.BISHOP),
(pygame.K_k, chess.KNIGHT)
]:
if event.key == key:
return piece_type

def get_move(self, board):
"""Get move from human player through GUI interaction."""
pygame.event.clear()

while True:
event = pygame.event.wait()

if event.type == pygame.QUIT:
return None

if event.type == pygame.MOUSEBUTTONDOWN:
x, y = event.pos
square = self.get_square_from_coords(x, y, self.color == chess.BLACK)

if self.selected_square is None:
# First click - select piece
piece = board.get_board_state().piece_at(square)
if piece and piece.color == self.color:
self.selected_square = square
else:
# Second click - try to make move
from_square = self.selected_square
to_square = square

# Check if this is a promotion move
if self.is_promotion_move(board, from_square, to_square):
promotion_piece = self.get_promotion_choice()
if promotion_piece is None:
self.selected_square = None
continue
move = chess.Move(from_square, to_square, promotion=promotion_piece)
else:
move = chess.Move(from_square, to_square)

# Check if move is legal
if move in board.get_legal_moves():
self.selected_square = None
return move

# If illegal move, clear selection
self.selected_square = None

return None

0 comments on commit 1b90f6e

Please sign in to comment.