11 Commits

Author SHA1 Message Date
e6fafc8081 implemented en passant
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-01-31 18:55:02 +01:00
1be71bf203 fixed giga spelling mistake (it's castling right, not castling write) 2025-01-31 18:27:44 +01:00
e862ab6b0b added comments to the make_move function 2025-01-31 18:24:52 +01:00
6c0819428e implemented checkmate
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-01-31 18:20:12 +01:00
496207861e implemented castling
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-01-31 16:35:41 +01:00
87e8e75c04 simplified moves, made them algebraic 2025-01-31 16:35:29 +01:00
13e3675665 fixed FEN reading for castling writes 2025-01-31 16:33:56 +01:00
2e27e7b703 implemented king moves (missing castles) 2025-01-31 15:22:49 +01:00
d7863e0d81 added capturing circle around possible capture for
legal moves
2025-01-31 14:34:54 +01:00
a3b7df4e4c minor fix again 2025-01-31 14:28:18 +01:00
806a4a7f65 minor fix 2025-01-31 14:09:35 +01:00
13 changed files with 299 additions and 61 deletions

View File

@ -40,11 +40,11 @@ class Controller:
def on_tile_selected(self, x: int, y: int) -> None: def on_tile_selected(self, x: int, y: int) -> None:
pos = Position(x, y) pos = Position(x, y)
print(f"Clicked on {pos.to_algebraic()}")
piece = self._board.piece_at(x, y) piece = self._board.piece_at(x, y)
if self._selected_piece is None or (piece is not None and piece != self._selected_piece): if self._selected_piece is None \
or (piece is not None and piece != self._selected_piece and piece.colour == self._selected_piece.colour):
self._show_legal_moves(pos) self._show_legal_moves(pos)
else: else:
legal_moves_positions = [move for move in self._legal_moves if move.pos == pos] legal_moves_positions = [move for move in self._legal_moves if move.pos == pos]
@ -55,3 +55,9 @@ class Controller:
else: else:
move = legal_moves_positions[0] move = legal_moves_positions[0]
self._make_move(move) self._make_move(move)
if self._board.is_checkmate_for(self._board._turn):
self._view.notify_checkmate(self._board._turn)
if self._board.is_stalemate_for(self._board._turn):
self._view.notify_stalemate(self._board._turn)

View File

@ -1,4 +1,4 @@
from logic.move import Move from logic.move import CastleSide, Move
from logic.pieces.bishop import Bishop from logic.pieces.bishop import Bishop
from logic.pieces.king import King from logic.pieces.king import King
from logic.pieces.knight import Knight from logic.pieces.knight import Knight
@ -15,8 +15,8 @@ class Board:
self._white: dict[Position, Piece] = {} self._white: dict[Position, Piece] = {}
self._black: dict[Position, Piece] = {} self._black: dict[Position, Piece] = {}
self._turn = None self._turn = None
self._white_castling_write = set() self._white_castling_rights = set()
self._black_castling_write = set() self._black_castling_rights = set()
self._en_passant_target = None self._en_passant_target = None
@staticmethod @staticmethod
@ -76,7 +76,7 @@ class Board:
ret._turn = Colour.BLACK ret._turn = Colour.BLACK
else: else:
raise ValueError(f"The FEN position is malformed, the active colour should be either 'w' or 'b', but is '{position[index]}'") raise ValueError(f"The FEN position is malformed, the active colour should be either 'w' or 'b', but is '{position[index]}'")
index += 1 index += 2
# -- Castling Rights # -- Castling Rights
@ -88,13 +88,13 @@ class Board:
sides = "kq" sides = "kq"
assert c in sides or c in sides.upper(), f"The FEN position is malformed, the castling rights should be either k or q (both either lower- or upper-case), instead is '{c}'" assert c in sides or c in sides.upper(), f"The FEN position is malformed, the castling rights should be either k or q (both either lower- or upper-case), instead is '{c}'"
if c == "K": if c == "K":
ret._white_castling_write.add(Board.KING_SIDE_CASTLE) ret._white_castling_rights.add(CastleSide.King)
if c == "Q": if c == "Q":
ret._white_castling_write.add(Board.QUEEN_SIDE_CASTLE) ret._white_castling_rights.add(CastleSide.Queen)
if c == "k": if c == "k":
ret._black_castling_write.add(Board.KING_SIDE_CASTLE) ret._black_castling_rights.add(CastleSide.King)
if c == "q": if c == "q":
ret._black_castling_write.add(Board.QUEEN_SIDE_CASTLE) ret._black_castling_rights.add(CastleSide.Queen)
# -- En passant target # -- En passant target
if position[index] != "-": if position[index] != "-":
@ -113,31 +113,117 @@ class Board:
return white_piece return white_piece
return black_piece return black_piece
def is_check_for(self, colour: Colour) -> bool:
""" Is it check for the defending colour passed as parameter """
defending_pieces, attacking_pieces = (self._white, self._black) if colour == Colour.WHITE else (self._black, self._white)
kings = [piece for piece in defending_pieces.values() if type(piece) == King]
assert len(kings) == 1, f"We have more than one king for {colour}, that is no buono..."
king = kings[0]
for piece in attacking_pieces.values():
possible_pos = []
if type(piece) == King:
# special case for the king, because it creates infinite recursion (since he looks if he's walking into a check)
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
x, y = piece.pos.x + dx, piece.pos.y + dy
if Position.is_within_bounds(x, y):
possible_pos.append(Position(x, y))
else:
possible_pos += [move.pos for move in piece.legal_moves(self, looking_for_check=True)]
if king.pos in possible_pos:
return True
return False
def is_checkmate_for(self, colour: Colour) -> bool:
""" Is it checkmate for the defending colour passed as parameter """
return self.is_check_for(colour) and self._no_legal_moves_for(colour)
def is_stalemate_for(self, colour: Colour) -> bool:
""" Is it stalemate for the defending colour passed as parameter """
return not self.is_check_for(colour) and self._no_legal_moves_for(colour)
def _no_legal_moves_for(self, colour: Colour) -> bool:
""" Return true if there are indeed no legal moves for the given colour (for checkmate or stalemate)"""
pieces = self._white if colour == Colour.WHITE else self._black
for piece in pieces.values():
if len(piece.legal_moves(self)) > 0:
return False
return True
def castling_rights_for(self, colour: Colour) -> set[CastleSide]:
return self._white_castling_rights if colour == Colour.WHITE else self._black_castling_rights
def make_move(self, move: Move) -> "Board": def make_move(self, move: Move) -> "Board":
dest_piece = self.piece_at(move.pos.x, move.pos.y) dest_piece = self.piece_at(move.pos.x, move.pos.y)
if dest_piece: if dest_piece:
assert dest_piece.colour != move.piece.colour, "A piece cannot cannot eat another piece of the same colour" assert dest_piece.colour != move.piece.colour, "A piece cannot cannot eat another piece of the same colour"
# -- Copy current state
ret = Board() ret = Board()
ret._white = self._white.copy() ret._white = self._white.copy()
ret._black = self._black.copy() ret._black = self._black.copy()
ret._turn = Colour.WHITE if self._turn == Colour.BLACK else Colour.BLACK ret._turn = Colour.WHITE if self._turn == Colour.BLACK else Colour.BLACK
ret._white_castling_write = self._white_castling_write.copy() ret._white_castling_rights = self._white_castling_rights.copy()
ret._black_castling_write = self._black_castling_write.copy() ret._black_castling_rights = self._black_castling_rights.copy()
ret._en_passant_target = self._en_passant_target
piece = move.piece piece = move.piece
if piece.colour == Colour.WHITE: # -- Actually make the move
del ret._white[piece.pos] pieces_moving, other_pieces = (ret._white, ret._black) if piece.colour == Colour.WHITE else (ret._black, ret._white)
ret._white[move.pos] = piece.move_to(move.pos)
if move.pos in ret._black: del pieces_moving[piece.pos]
del ret._black[move.pos] pieces_moving[move.pos] = piece.move_to(move.pos)
if move.pos in other_pieces:
del other_pieces[move.pos]
if move.en_passant:
pos_to_remove = Position(move.pos.x, move.pos.y + (1 if self._turn == Colour.BLACK else -1))
del other_pieces[pos_to_remove]
# -- Set en passant target if needed
if move.becomes_en_passant_target:
ret._en_passant_target = pieces_moving[move.pos]
else: else:
del ret._black[piece.pos] ret._en_passant_target = None
ret._black[move.pos] = piece.move_to(move.pos)
if move.pos in ret._white: # -- Handle castling (just move the rook over)
del ret._white[move.pos] if move.castle_side == CastleSide.King:
rook_pos = Position(7, piece.pos.y)
assert rook_pos in pieces_moving and type(pieces_moving[rook_pos]) == Rook, "Either rook is absent from the king side or you are trying to castle with something else than a rook..."
del pieces_moving[rook_pos]
new_rook_pos = Position(5, piece.pos.y)
pieces_moving[new_rook_pos] = Rook(new_rook_pos, piece.colour)
elif move.castle_side == CastleSide.Queen:
rook_pos = Position(0, piece.pos.y)
assert rook_pos in pieces_moving and type(pieces_moving[rook_pos]) == Rook, "Either rook is absent from the queen side or you are trying to castle with something else than a rook..."
del pieces_moving[rook_pos]
new_rook_pos = Position(3, piece.pos.y)
pieces_moving[new_rook_pos] = Rook(new_rook_pos, piece.colour)
# -- Check for castling rights
if piece.colour == Colour.WHITE:
if type(piece) == King:
ret._white_castling_rights = set()
if type(piece) == Rook:
if piece.pos.x == 0 and CastleSide.Queen in ret._white_castling_rights:
ret._white_castling_rights.remove(CastleSide.Queen)
elif piece.pos.x == 7 and CastleSide.King in ret._white_castling_rights:
ret._white_castling_rights.remove(CastleSide.King)
else:
if type(piece) == King:
ret._black_castling_rights = set()
if type(piece) == Rook:
if piece.pos.x == 0 and CastleSide.Queen in ret._black_castling_rights:
ret._black_castling_rights.remove(CastleSide.Queen)
elif piece.pos.x == 7 and CastleSide.King in ret._black_castling_rights:
ret._black_castling_rights.remove(CastleSide.King)
return ret return ret

View File

@ -2,9 +2,19 @@
from logic.position import Position from logic.position import Position
from enum import Enum from enum import Enum
class CastleSide(Enum):
Neither = ""
King = "O-O"
Queen = "O-O-O"
class Move: class Move:
def __init__(self, is_capturing: bool) -> None: def __init__(self, piece: "Piece", pos: Position,/, is_capturing: bool = False, castle_side: CastleSide = CastleSide.Neither, en_passant: bool = False, becomes_en_passant_target: bool = False) -> None:
self.piece = piece
self.pos = pos
self.is_capturing = is_capturing self.is_capturing = is_capturing
self.castle_side = castle_side
self.becomes_en_passant_target = becomes_en_passant_target
self.en_passant = en_passant
def to_algebraic(self) -> str: def to_algebraic(self) -> str:
raise NotImplementedError("The move can't be translated to algbraic notation, as it was not implemented") raise NotImplementedError("The move can't be translated to algbraic notation, as it was not implemented")
@ -13,13 +23,18 @@ class Move:
def from_algebraic(move: str) -> "Move": def from_algebraic(move: str) -> "Move":
raise NotImplementedError("The move can't be translated from algbraic notation, as it was not implemented") raise NotImplementedError("The move can't be translated from algbraic notation, as it was not implemented")
def __str__(self) -> str:
if self.castle_side == CastleSide.King:
return "O-O"
if self.castle_side == CastleSide.Queen:
return "O-O-O"
class PieceMove(Move): ret = ""
def __init__(self, piece: "Piece", pos: Position,/, is_capturing: bool = False) -> None: if type(self.piece).__name__ != "Pawn":
super().__init__(is_capturing) ret += self.piece.letter().upper()
self.piece = piece
self.pos = pos
class Castle(Move, Enum): ret += str(self.pos)
KING_SIDE_CASTLE = False return ret
QUEEN_SIDE_CASTLE = False
def __repr__(self) -> str:
return str(self)

View File

@ -2,7 +2,7 @@ from logic.move import Move
from .piece import Piece from .piece import Piece
class Bishop(Piece): class Bishop(Piece):
def legal_moves(self, board: "Board") -> list[Move]: def legal_moves(self, board: "Board", / , looking_for_check = False) -> list[Move]:
ret = [] ret = []
# looking north east # looking north east
@ -17,5 +17,8 @@ class Bishop(Piece):
# looking north west # looking north west
ret.extend(self._look_direction(board, -1, 1)) ret.extend(self._look_direction(board, -1, 1))
if not looking_for_check and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board)
return ret return ret

View File

@ -1,5 +1,69 @@
from logic.move import CastleSide, Move
from logic.position import Position
from .piece import Piece from .piece import Piece
class King(Piece): class King(Piece):
pass def legal_moves(self, board: "Board") -> list[Move]:
ret = []
# -- Regular moves
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0: # skip current position
continue
x = self.pos.x + dx
y = self.pos.y + dy
move = self._move_for_position(board, x, y)
if move:
board_after_move = board.make_move(move)
if not board_after_move.is_check_for(self.colour):
ret.append(move)
if board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board)
# -- Castles
castling_rights = board.castling_rights_for(self.colour)
if len(castling_rights) == 0:
return ret
if CastleSide.King in castling_rights:
clear = True
for dx in range(1, 3):
x = self.pos.x + dx
y = self.pos.y
if board.piece_at(x, y) is not None:
clear = False
break
move = self._move_for_position(board, x, y)
board_after_move = board.make_move(move)
if board_after_move.is_check_for(self.colour):
clear = False
break
if clear:
ret.append(Move(self, Position(6, self.pos.y), castle_side=CastleSide.King))
if CastleSide.Queen in castling_rights:
clear = True
for dx in range(1, 3):
x = self.pos.x - dx
y = self.pos.y
if board.piece_at(x, y) is not None:
clear = False
break
move = self._move_for_position(board, x, y)
board_after_move = board.make_move(move)
if board_after_move.is_check_for(self.colour):
clear = False
break
if clear:
ret.append(Move(self, Position(2, self.pos.y), castle_side=CastleSide.Queen))
return ret

View File

@ -1,7 +1,10 @@
from .piece import Piece from .piece import Piece
class Knight(Piece): class Knight(Piece):
def legal_moves(self, board: "Board") -> list["Move"]: def letter(self):
return "n"
def legal_moves(self, board: "Board", / , looking_for_check = False) -> list["Move"]:
ret = [] ret = []
for dx, dy in [ for dx, dy in [
(+2, +1), (+1, +2), # north east (+2, +1), (+1, +2), # north east
@ -12,5 +15,9 @@ class Knight(Piece):
move = self._move_for_position(board, self.pos.x + dx, self.pos.y + dy) move = self._move_for_position(board, self.pos.x + dx, self.pos.y + dy)
if move is not None: if move is not None:
ret.append(move) ret.append(move)
if not looking_for_check and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board)
return ret return ret

View File

@ -1,9 +1,9 @@
from logic.move import Move, PieceMove from logic.move import Move
from logic.pieces.piece import Colour, Piece from logic.pieces.piece import Colour, Piece
from logic.position import Position from logic.position import Position
class Pawn(Piece): class Pawn(Piece):
def legal_moves(self, board) -> list[Move]: def legal_moves(self, board, / , looking_for_check = False) -> list[Move]:
ret = [] ret = []
# can we capture to the left? # can we capture to the left?
@ -13,7 +13,7 @@ class Pawn(Piece):
(self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x - 1, self.pos.y - 1))) (self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x - 1, self.pos.y - 1)))
): ):
if capturable_piece.colour != self.colour: if capturable_piece.colour != self.colour:
ret.append(PieceMove(self, capturable_piece.pos, is_capturing = True)) ret.append(Move(self, capturable_piece.pos, is_capturing = True))
# can we capture to the right? # can we capture to the right?
if self.pos.x < 7 and ( if self.pos.x < 7 and (
@ -22,19 +22,34 @@ class Pawn(Piece):
(self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x + 1, self.pos.y - 1))) (self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x + 1, self.pos.y - 1)))
): ):
if capturable_piece.colour != self.colour: if capturable_piece.colour != self.colour:
ret.append(PieceMove(self, capturable_piece.pos, is_capturing = True)) ret.append(Move(self, capturable_piece.pos, is_capturing = True))
# -- Can we capture en passant?
if board._en_passant_target is not None and \
board._en_passant_target.pos.y == self.pos.y and (
board._en_passant_target.pos.x == self.pos.x - 1
or board._en_passant_target.pos.x == self.pos.x + 1
):
if board._en_passant_target.colour != self.colour:
old_pos = board._en_passant_target.pos
new_pos = Position(old_pos.x, old_pos.y + (1 if self.colour == Colour.WHITE else -1))
ret.append(Move(self, new_pos, is_capturing = True, en_passant = True))
# -- Normal moves
if self.colour == Colour.WHITE: if self.colour == Colour.WHITE:
for dy in range(1, 3 if self.pos.y == 1 else 2): for dy in range(1, 3 if self.pos.y == 1 else 2):
if self.pos.y + dy > 7 or board.piece_at(self.pos.x, self.pos.y + dy): if self.pos.y + dy > 7 or board.piece_at(self.pos.x, self.pos.y + dy):
break break
pos = Position(self.pos.x, self.pos.y + dy) pos = Position(self.pos.x, self.pos.y + dy)
ret.append(PieceMove(self, pos)) ret.append(Move(self, pos, becomes_en_passant_target=dy==2))
else: else:
for dy in range(1, 3 if self.pos.y == 6 else 2): for dy in range(1, 3 if self.pos.y == 6 else 2):
if self.pos.y - dy < 0 or board.piece_at(self.pos.x, self.pos.y - dy): if self.pos.y - dy < 0 or board.piece_at(self.pos.x, self.pos.y - dy):
break break
pos = Position(self.pos.x, self.pos.y - dy) pos = Position(self.pos.x, self.pos.y - dy)
ret.append(PieceMove(self, pos)) ret.append(Move(self, pos, becomes_en_passant_target=dy==2))
if not looking_for_check and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board)
return ret return ret

View File

@ -1,4 +1,4 @@
from logic.move import Move, PieceMove from logic.move import Move
from logic.position import Position from logic.position import Position
from enum import Enum from enum import Enum
@ -7,12 +7,26 @@ class Colour(Enum):
WHITE = "white" WHITE = "white"
BLACK = "black" BLACK = "black"
def __str__(self) -> str:
return self.value
class Piece: class Piece:
def __init__(self, pos: Position, colour: Colour) -> None: def __init__(self, pos: Position, colour: Colour) -> None:
self.pos = pos self.pos = pos
assert colour == Colour.WHITE or colour == Colour.BLACK, "The colour of the piece must be either Piece.WHITE or Piece.BLACK" assert colour == Colour.WHITE or colour == Colour.BLACK, "The colour of the piece must be either Piece.WHITE or Piece.BLACK"
self.colour = colour self.colour = colour
def letter(self):
return type(self).__name__[0].lower()
def keep_only_blocking(self, candidates: list[Move], board: "Board") -> list[Move]:
ret = []
for move in candidates:
board_after_move = board.make_move(move)
if not board_after_move.is_check_for(self.colour):
ret.append(move)
return ret
def _look_direction(self, board: "Board", mult_dx: int, mult_dy: int): def _look_direction(self, board: "Board", mult_dx: int, mult_dy: int):
ret = [] ret = []
for d in range(1, 8): for d in range(1, 8):
@ -34,10 +48,10 @@ class Piece:
piece = board.piece_at(x, y) piece = board.piece_at(x, y)
if piece is None: if piece is None:
return PieceMove(self, Position(x, y)) return Move(self, Position(x, y))
if piece.colour != self.colour: if piece.colour != self.colour:
return PieceMove(self, Position(x, y), is_capturing=True) return Move(self, Position(x, y), is_capturing=True)
return None return None
def position(self) -> Position: def position(self) -> Position:
@ -47,5 +61,5 @@ class Piece:
ret = type(self)(pos, self.colour) ret = type(self)(pos, self.colour)
return ret return ret
def legal_moves(self, board: "Board") -> list["Move"]: def legal_moves(self, board: "Board", / , looking_for_check = False) -> list["Move"]:
raise NotImplementedError(f"Can't say what the legal moves are for {type(self).__name__}, the method hasn't been implemented yet") raise NotImplementedError(f"Can't say what the legal moves are for {type(self).__name__}, the method hasn't been implemented yet")

View File

@ -2,7 +2,7 @@ from logic.move import Move
from .piece import Piece from .piece import Piece
class Queen(Piece): class Queen(Piece):
def legal_moves(self, board: "Board") -> list[Move]: def legal_moves(self, board: "Board", / , looking_for_check = False) -> list[Move]:
ret = [] ret = []
# looking north east # looking north east
@ -29,4 +29,7 @@ class Queen(Piece):
# looking north # looking north
ret.extend(self._look_direction(board, 0, 1)) ret.extend(self._look_direction(board, 0, 1))
if not looking_for_check and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board)
return ret return ret

View File

@ -2,7 +2,7 @@ from logic.move import Move
from .piece import Piece from .piece import Piece
class Rook(Piece): class Rook(Piece):
def legal_moves(self, board: "Board") -> list[Move]: def legal_moves(self, board: "Board", / , looking_for_check = False) -> list[Move]:
ret = [] ret = []
# looking east # looking east
@ -17,4 +17,7 @@ class Rook(Piece):
# looking north # looking north
ret.extend(self._look_direction(board, 0, 1)) ret.extend(self._look_direction(board, 0, 1))
if not looking_for_check and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board)
return ret return ret

View File

@ -29,8 +29,7 @@ class Position:
return hash((self.x, self.y)) return hash((self.x, self.y))
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.x, self.y}" return f"{Position._FILES[self.x]}{Position._RANKS[self.y]}"
def __repr__(self) -> str: def __repr__(self) -> str:
return str(self) return str(self)

View File

@ -1,4 +1,5 @@
import tkinter as tk import tkinter as tk
from tkinter import messagebox
from typing import Type from typing import Type
from PIL import ImageTk, Image from PIL import ImageTk, Image
import os import os
@ -37,8 +38,6 @@ class GUI(View):
path = os.path.join(root, f"{"white" if colour == Colour.WHITE else "black"}-{piece_name}.png") path = os.path.join(root, f"{"white" if colour == Colour.WHITE else "black"}-{piece_name}.png")
img = Image.open(path) img = Image.open(path)
size = int(self.tile_size * .85)
# img = img.resize((size, size))
if img.mode == "LA": if img.mode == "LA":
img = img.convert(mode="RGBA") img = img.convert(mode="RGBA")
@ -62,6 +61,12 @@ class GUI(View):
self._controller.on_tile_selected(x, y) self._controller.on_tile_selected(x, y)
def notify_checkmate(self, colour: Colour) -> None:
messagebox.showinfo("Checkmate", f"{colour} is currently checkmated")
def notify_stalemate(self, colour: Colour) -> None:
messagebox.showinfo("Stalemate", f"{colour} is currently stalemated")
def update_board(self, board: Board, selected_piece: Piece, legal_moves: list[Move]) -> None: def update_board(self, board: Board, selected_piece: Piece, legal_moves: list[Move]) -> None:
self.canvas.delete("all") self.canvas.delete("all")
@ -92,17 +97,29 @@ class GUI(View):
if selected_piece is not None: if selected_piece is not None:
possible_positions = [move.pos for move in legal_moves] possible_positions = [move.pos for move in legal_moves]
if pos in possible_positions: if pos in possible_positions:
radius = .15 * self.tile_size
colour = circle_colours[(x + y) % 2] colour = circle_colours[(x + y) % 2]
self.canvas.create_oval( move = [move for move in legal_moves if move.pos == pos][0]
(x + .5) * self.tile_size - radius, if move.is_capturing:
(y + .5) * self.tile_size - radius, radius = .40 * self.tile_size
(x + .5) * self.tile_size + radius, self.canvas.create_oval(
(y + .5) * self.tile_size + radius, (x + .5) * self.tile_size - radius,
fill=colour, (y + .5) * self.tile_size - radius,
outline=colour, (x + .5) * self.tile_size + radius,
) (y + .5) * self.tile_size + radius,
fill="",
outline=colour,
width=.075 * self.tile_size,
)
else:
radius = .15 * self.tile_size
self.canvas.create_oval(
(x + .5) * self.tile_size - radius,
(y + .5) * self.tile_size - radius,
(x + .5) * self.tile_size + radius,
(y + .5) * self.tile_size + radius,
fill=colour,
outline=colour,
)
piece = board.piece_at(x, 7-y) piece = board.piece_at(x, 7-y)

View File

@ -1,6 +1,6 @@
from logic.board import Board from logic.board import Board
from logic.move import Move from logic.move import Move
from logic.pieces.piece import Piece from logic.pieces.piece import Colour, Piece
class View: class View:
@ -13,6 +13,12 @@ class View:
def update_board(self, board: Board, selected_piece: Piece, legal_moves: list[Move]) -> None: def update_board(self, board: Board, selected_piece: Piece, legal_moves: list[Move]) -> None:
raise NotImplementedError(f"Can't update the board, the update_board() method of {type(self)} is not implemented") raise NotImplementedError(f"Can't update the board, the update_board() method of {type(self)} is not implemented")
def notify_checkmate(self, colour: Colour) -> None:
raise NotImplementedError(f"Can't notify of the checkmate, the notify_checkmate() method of {type(self)} is not implemented")
def notify_stalemate(self, colour: Colour) -> None:
raise NotImplementedError(f"Can't notify of the stalemate, the notify_stalemate() method of {type(self)} is not implemented")
def set_controller(self, controller: "Controller") -> None: def set_controller(self, controller: "Controller") -> None:
self._controller = controller self._controller = controller