Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_pycache_/
*.py[cod]
5 changes: 5 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/python-chess.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 72 additions & 1 deletion data/classes/Board.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
from data.classes.pieces.Pawn import Pawn

class Board:
def __init__(self, width, height):
def __init__(self, width, height, is_flipped=False):
self.width = width
self.height = height
self.square_width = width // 8
self.square_height = height // 8
self.selected_piece = None
self.turn = 'white'
self.is_flipped = is_flipped

self.config = [
['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'],
Expand All @@ -31,6 +32,8 @@ def __init__(self, width, height):
self.squares = self.generate_squares()

self.setup_board()
if self.is_flipped:
self.apply_view(True)

def generate_squares(self):
output = []
Expand Down Expand Up @@ -100,6 +103,8 @@ def setup_board(self):
def handle_click(self, mx, my):
x = mx // self.square_width
y = my // self.square_height
if x < 0 or x > 7 or y < 0 or y > 7:
return
clicked_square = self.get_square_from_pos((x, y))

if self.selected_piece is None:
Expand All @@ -114,6 +119,30 @@ def handle_click(self, mx, my):
if clicked_square.occupying_piece.color == self.turn:
self.selected_piece = clicked_square.occupying_piece

def handle_click_flipped(self, mx, my):
x = 7 - (mx // self.square_width)
y = 7 - (my // self.square_height)
if x < 0 or x > 7 or y < 0 or y > 7:
return
clicked_square = self.get_square_from_pos((x, y))

if self.selected_piece is None:
if clicked_square.occupying_piece is not None:
if clicked_square.occupying_piece.color == self.turn:
self.selected_piece = clicked_square.occupying_piece

elif self.selected_piece.move(self, clicked_square):
self.turn = 'white' if self.turn == 'black' else 'black'

elif clicked_square.occupying_piece is not None:
if clicked_square.occupying_piece.color == self.turn:
self.selected_piece = clicked_square.occupying_piece

def apply_view(self, is_flipped):
self.is_flipped = is_flipped
for square in self.squares:
square.set_view(self.is_flipped)


def is_in_check(self, color, board_change=None): # board_change = [(x1, y1), (x2, y2)]
output = False
Expand Down Expand Up @@ -186,6 +215,48 @@ def get_piece_from_pos(self, pos):
return self.get_square_from_pos(pos).occupying_piece


def get_bitboards(self):
bitboards = {
'P': 0, 'N': 0, 'B': 0, 'R': 0, 'Q': 0, 'K': 0, # White pieces
'p': 0, 'n': 0, 'b': 0, 'r': 0, 'q': 0, 'k': 0 # Black pieces
}

for square in self.squares:
piece = square.occupying_piece
if piece is not None:
# Pygame y=0 is Rank 8, y=7 is Rank 1.
# Invert y so index 0 is A1 and index 63 is H8 (Little Edian Rank File mapping).
lerf_index = (7 - square.y) * 8 + square.x

# Safely get the piece type by its class name
piece_type = piece.__class__.__name__

# Map the class name to standard FIDE notation
symbol_map = {
'Pawn': 'P', 'Knight': 'N', 'Bishop': 'B',
'Rook': 'R', 'Queen': 'Q', 'King': 'K'
}
symbol = symbol_map.get(piece_type)

# Convert to lowercase if the piece is black
if piece.color == 'black':
symbol = symbol.lower()

# Stamp this piece onto its specific bitboard using Bitwise OR
# Example: 0001, shift by the lerf index 3 to 1000 and OR 0100 to get 1100
bitboards[symbol] |= (1 << lerf_index)

# Generate the summary bitboards for quick AI lookups
bitboards['white_pieces'] = (bitboards['P'] | bitboards['N'] | bitboards['B'] |
bitboards['R'] | bitboards['Q'] | bitboards['K'])

bitboards['black_pieces'] = (bitboards['p'] | bitboards['n'] | bitboards['b'] |
bitboards['r'] | bitboards['q'] | bitboards['k'])

bitboards['occupied_squares'] = bitboards['white_pieces'] | bitboards['black_pieces']

return bitboards

def draw(self, display):
if self.selected_piece is not None:
self.get_square_from_pos(self.selected_piece.pos).highlight = True
Expand Down
28 changes: 28 additions & 0 deletions data/classes/Button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import pygame

class Button:
def __init__(self, x, y, width, height, text, color=(100, 100, 100), hover_color=(150, 150, 150), text_color=(255, 255, 255)):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.color = color
self.hover_color = hover_color
self.text_color = text_color
self.font = pygame.font.SysFont(None, 36)
self.is_hovered = False

def draw(self, surface):
mouse_pos = pygame.mouse.get_pos()
self.is_hovered = self.rect.collidepoint(mouse_pos)

current_color = self.hover_color if self.is_hovered else self.color
pygame.draw.rect(surface, current_color, self.rect)

text_surf = self.font.render(self.text, True, self.text_color)
text_rect = text_surf.get_rect(center=self.rect.center)
surface.blit(text_surf, text_rect)

def is_clicked(self, event):
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if self.rect.collidepoint(event.pos):
return True
return False
14 changes: 14 additions & 0 deletions data/classes/Square.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ def __init__(self, x, y, width, height):
self.height
)

def set_view(self, is_flipped):
draw_x = 7 - self.x if is_flipped else self.x
draw_y = 7 - self.y if is_flipped else self.y

self.abs_x = draw_x * self.width
self.abs_y = draw_y * self.height
self.abs_pos = (self.abs_x, self.abs_y)
self.rect = pygame.Rect(
self.abs_x,
self.abs_y,
self.width,
self.height
)


def get_coord(self):
columns = 'abcdefgh'
Expand Down
25 changes: 25 additions & 0 deletions data/classes/StateManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class StateManager:
def __init__(self):
self.states = {}
self.current_state = None

def setup(self, states, initial_state):
self.states = states
self.change_state(initial_state)

def change_state(self, state_name):
if state_name in self.states:
self.current_state = self.states[state_name]
self.current_state.on_enter()

def handle_events(self, events):
if self.current_state:
self.current_state.handle_events(events)

def update(self):
if self.current_state:
self.current_state.update()

def draw(self, surface):
if self.current_state:
self.current_state.draw(surface)
7 changes: 7 additions & 0 deletions data/classes/chess_bot/Bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Bot:
def __init__(self, depth=1, color='white'):
pass

def get_best_move():
pass

21 changes: 21 additions & 0 deletions data/classes/chess_bot/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Colors
WHITE = 'white'
BLACK = 'black'

W_PAWN = 'P'
W_KNIGHT = 'N'
W_BISHOP = 'B'
W_ROOK = 'R'
W_QUEEN = 'Q'
W_KING = 'K'

B_PAWN = 'p'
B_KNIGHT = 'n'
B_BISHOP = 'b'
B_ROOK = 'r'
B_QUEEN = 'q'
B_KING = 'k'

W_PIECES = 'white_pieces'
B_PIECES = 'black_pieces'
OCCUPIED = 'occupied_squares'
2 changes: 2 additions & 0 deletions data/classes/chess_bot/evaluate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def evaluate():
pass
27 changes: 27 additions & 0 deletions data/classes/chess_bot/move_gens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from .constants import *
from .precompute import KNIGHT_MOVES, KING_MOVES

# Returns the indices of the set bits in a bitboard
# Example: 0b1010 -> yields 1 and 3 (0-indexed from the right)
def get_set_bits(bitboard):
while bitboard:
lsb = bitboard & -bitboard
yield lsb.bit_length() - 1
bitboard &= bitboard - 1

def get_knights_moves(bitboards, color):
moves = []
if color == WHITE:
knights = bitboards[W_KNIGHT]
friendly_pieces = bitboards[W_PIECES]
else:
knights = bitboards[B_KNIGHT]
friendly_pieces = bitboards[B_PIECES]

for knight in get_set_bits(knights):
raw_moves = KNIGHT_MOVES[knight]
legal_moves = raw_moves & ~friendly_pieces

for target in get_set_bits(legal_moves):
moves.append((knight, target))
return moves
27 changes: 27 additions & 0 deletions data/classes/chess_bot/precompute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
NOT_A_FILE = 0xFEFEFEFEFEFEFEFE # A-file bits are 0, everywhere else is 1
NOT_AB_FILE = 0xFCFCFCFCFCFCFCFC # A and B files are 0
NOT_H_FILE = 0x7F7F7F7F7F7F7F7F # H-file bits are 0
NOT_GH_FILE = 0x3F3F3F3F3F3F3F3F # G and H files are 0

def precomputed_knight_moves():
moves = []
for square in range(64):
knight = 1 << square
board = 0
board |= (knight & NOT_A_FILE) << 15 # Move 2 up, 1 left
board |= (knight & NOT_H_FILE) << 17 # Move 2 up, 1 right
board |= (knight & NOT_AB_FILE) << 6 # Move 1 up, 2 left
board |= (knight & NOT_GH_FILE) << 10 # Move 1 up, 2 right
board |= (knight & NOT_A_FILE) >> 17 # Move 2 down, 1 left
board |= (knight & NOT_H_FILE) >> 15 # Move 2 down, 1 right
board |= (knight & NOT_AB_FILE) >> 10 # Move 1 down, 2 left
board |= (knight & NOT_GH_FILE) >> 6 # Move 1 down, 2 right
moves.append(board)
return moves

def precomputed_king_moves():
moves = []
return moves

KNIGHT_MOVES = precomputed_knight_moves()
KING_MOVES = precomputed_king_moves()
2 changes: 2 additions & 0 deletions data/classes/chess_bot/search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def negamax():
pass
1 change: 1 addition & 0 deletions data/classes/pieces/Bishop.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def __init__(self, pos, color, board):
self.notation = 'B'



def get_possible_moves(self, board):
output = []

Expand Down
21 changes: 21 additions & 0 deletions data/states/MenuState.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from data.states.State import State
from data.classes.Button import Button

class MenuState(State):
def __init__(self, manager):
super().__init__(manager)
# Centered horizontally for a 600x600 window
self.btn_pvp = Button(200, 200, 200, 60, "PvP")
self.btn_pve = Button(200, 300, 200, 60, "PvE")

def handle_events(self, events):
for event in events:
if self.btn_pvp.is_clicked(event):
self.manager.change_state('pvp')
elif self.btn_pve.is_clicked(event):
self.manager.change_state('pve')

def draw(self, surface):
surface.fill((230, 230, 230)) # Light gray background
self.btn_pvp.draw(surface)
self.btn_pve.draw(surface)
Loading