Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Game loop GUI #247

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
77ec111
initial game loop gui code
RuthShryock Apr 11, 2022
5ec03c5
adding GUIPlayer methods and styling code
RuthShryock Apr 19, 2022
117ee4c
initial game loop gui code
RuthShryock Apr 11, 2022
6581f57
adding GUIPlayer methods and styling code
RuthShryock Apr 19, 2022
30f4776
updating branch
RuthShryock Apr 19, 2022
4dc02b5
removing info frame
RuthShryock Apr 20, 2022
3140930
reverted change in boardinterface.py
RuthShryock Apr 20, 2022
ecd9e30
adding docstrings to gui.py
RuthShryock Apr 20, 2022
e8dceb8
formatting changes
RuthShryock Apr 21, 2022
8d8aa2e
updating methods in gui.py to match with game.py methods
RuthShryock Apr 21, 2022
b6c1a21
changing loop structure
RuthShryock Apr 22, 2022
2c8eee4
fixed loop
RuthShryock Apr 22, 2022
6bf3a6f
Reduced length of time.sleep and made printing board status optional …
nashirj Apr 22, 2022
8f92c5b
Reduce poll time, and also remove some print statements
nashirj Apr 22, 2022
6622816
Fix merge conflicts
nashirj Apr 22, 2022
507ac33
adjusting for human player to play as black and fixed highlighting fo…
RuthShryock Apr 27, 2022
08ab6e2
removing print line
RuthShryock Apr 27, 2022
c07d357
fixing pr comments and styling code
RuthShryock May 30, 2022
f74ded9
updating branch
RuthShryock Jul 17, 2022
2af1212
merging
RuthShryock Aug 25, 2022
db1b27e
Added castling feature
Sep 7, 2022
5457e78
fixing random turn at start
RuthShryock Sep 7, 2022
21c7a54
updating branch
RuthShryock Sep 7, 2022
5937e34
fixed highlighting after click and styling comments
RuthShryock Oct 7, 2022
d6a5066
updating branch
RuthShryock Oct 7, 2022
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
212 changes: 212 additions & 0 deletions firmware/raspi/chessboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
"""Module containing class to display current board state.

Also contains some custom Exceptions.

Based on https://github.com/saavedra29/chess_tk
"""
from copy import deepcopy
import re

import pieces

class Board(dict):
"""Board class to disply current board state.
"""
y_axis = ("A", "B", "C", "D", "E", "F", "G", "H")
x_axis = (1, 2, 3, 4, 5, 6, 7, 8)
captured_pieces = {"white": [], "black": []}
player_turn = None
halfmove_clock = 0
fullmove_number = 1
history = []
START_PATTERN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'

def __init__(self, pat=None):
super().__init__()
self.show(Board.START_PATTERN)

def is_in_check_after_move(self, p1, p2):
"""Checks if the king is in check after move has been made
"""
tmp = deepcopy(self)
tmp.move(p1, p2)
return tmp.king_in_check(self[p1].color)

def shift(self, p1, p2):
"""Checks if a move is valid and then makes the move
"""
p1, p2 = p1.upper(), p2.upper()
piece = self[p1]
try:
dest = self[p2]
except Exception as e:
dest = None
if self.player_turn != piece.color:
raise NotYourTurn("Not " + piece.color + "'s turn!")
enemy = ("white" if piece.color == "black" else "black")
moves_available = piece.moves_available(p1)
if p2 not in moves_available:
raise InvalidMove
if self.all_moves_available(enemy):
if self.is_in_check_after_move(p1, p2):
raise Check
if not moves_available and self.king_in_check(piece.color):
raise CheckMate
elif not moves_available:
raise Draw
else:
self.move(p1, p2)
self.complete_move(piece, dest, p2)
self.updateCastleStatus(p1)

def move(self, p1, p2):
"""Moves a piece from p1 to p2
"""
piece = self[p1]
try:
_ = self[p2]
except Exception as e:
pass
del self[p1]
self[p2] = piece

def complete_move(self, piece, dest, p2):
"""Makes a complete move by updating values and changes the turn to the opponent
"""
enemy = ("white" if piece.color == "black" else "black")
if piece.color == "black":
self.fullmove_number += 1
self.halfmove_clock += 1
self.player_turn = enemy
abbr = piece.shortname
if abbr == "P":
abbr = ""
self.halfmove_clock = 0
if dest is None:
movetext = abbr + p2.lower()
else:
movetext = abbr + "x" + p2.lower()
self.halfmove_clock = 0
self.history.append(movetext)

def all_moves_available(self, color):
"""Returns an array of all the available moves
"""
result = []
for coord in self.keys():
if (self[coord] is not None) and self[coord].color == color:
moves = self[coord].moves_available(coord)
if moves: result += moves
return result

def occupied(self, color):
"""Returns an array of occupied coordinates
"""
result = []
for coord in iter(self.keys()):
if self[coord].color == color:
result.append(coord)
return result

def position_of_king(self, color):
"""Returns the position of the king
"""
for pos in self.keys():
if isinstance(self[pos], pieces.King) and self[pos].color == color:
return pos

def king_in_check(self, color):
"""Checks if the king is in check
"""
kingpos = self.position_of_king(color)
opponent = ("black" if color == "white" else "white")
for _ in self.items():
return bool(kingpos in self.all_moves_available(opponent))

def alpha_notation(self, xycoord):
"""Returns x,y coordinates in numbers and letters (e.g., "e6") of
position on chessboard.
"""
if xycoord[0] < 0 or xycoord[0] > 7 or xycoord[1] < 0 or xycoord[
1] > 7:
return
return self.y_axis[int(xycoord[1])] + str(self.x_axis[int(xycoord[0])])

def num_notation(self, coord):
"""Returns x,y coordinates in numbers of position on chessboard.
"""
return int(coord[1]) - 1, self.y_axis.index(coord[0])

def is_on_board(self, coord):
"""Checks if the given coord is on the board
"""
list = [coord[1] >= 0, coord[1] <= 7, coord[0] >= 0, coord[0] <= 7]
return all(list)

def show(self, pat):
"""Prints a visual representation of the board state based on a
fen string.
"""
self.clear()
pat = pat.split(" ")

def expand(match):
return " " * int(match.group(0))

pat[0] = re.compile(r"\d").sub(expand, pat[0])
for x, row in enumerate(pat[0].split("/")):
for y, letter in enumerate(row):
if letter == " ":
continue
coord = self.alpha_notation((7 - x, y))
self[coord] = pieces.create_piece(letter)
self[coord].place(self)
if pat[1] == "w":
self.player_turn = "white"
else:
self.player_turn = "black"
self.halfmove_clock = int(pat[4])
self.fullmove_number = int(pat[5])

def updateCastleStatus(self, p1):
if p1 == "A1":
pieces.leftWhiteMoved = True
elif p1 == "H1":
pieces.rightWhiteMoved = True
elif p1 == "A8":
pieces.leftBlackMoved = True
elif p1 == "H8":
pieces.rightBlackMoved = True
elif p1 == "E1":
pieces.whiteKingHome = False
elif p1 == "E8":
pieces.blackKingHome = False

if pieces.whiteKingHome:
pieces.rightWhiteCheck = self.king_in_check("white") or self.is_in_check_after_move("E1", "F1")
pieces.leftWhiteCheck = self.king_in_check("white") or self.is_in_check_after_move("E1", "D1")
if pieces.blackKingHome:
pieces.rightBlackCheck = self.king_in_check("black") or self.is_in_check_after_move("E8", "F8")
pieces.leftBlackCheck = self.king_in_check("black") or self.is_in_check_after_move("E8", "D8")


class ChessError(Exception):
pass

class Check(ChessError):
pass

class InvalidMove(ChessError):
pass

class CheckMate(ChessError):
pass

class Draw(ChessError):
pass

class NotYourTurn(ChessError):
pass

class InvalidCoord(ChessError):
pass
13 changes: 8 additions & 5 deletions firmware/raspi/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class Game:
The main program logic is in the `process` function. `process` should be called in a loop in
the frontend code.
"""
SLEEP_TIME = 0.01

def __init__(self, mode_of_interaction, interact_w_arduino, human_plays_white_pieces=None):
self.mode_of_interaction = mode_of_interaction
# TODO: Set up board with either white or black on human side.
Expand Down Expand Up @@ -45,7 +47,7 @@ def is_white_turn(self):
def last_made_move(self):
"""Returns the last made move, if applicable. If no moves have been made, returns None.
"""
if self.board.engine.chess_board.move_stack:
if not self.board.engine.chess_board.move_stack:
return None
return self.board.engine.chess_board.peek().uci()

Expand Down Expand Up @@ -74,7 +76,7 @@ def send_uci_move_to_board(self, uci_move):
"Should we loop until move is valid? What if "
"the board is messed up? Need to revisit.")

def process(self, player):
def process(self, player, verbose=False):
"""One iteration of main game loop.

Note: expects caller to check game.is_game_over before calling.
Expand All @@ -85,11 +87,12 @@ def process(self, player):
made_move: boolean that is True if turn changes, otherwise False.
"""
arduino_status = self.board.get_status_from_arduino()
print(f"\nBoard Status: {arduino_status}")
if verbose:
print(f"\nBoard Status: {arduino_status}")

if arduino_status.status == status.ArduinoStatus.EXECUTING_MOVE:
# Wait for move in progress to finish executing
time.sleep(1) # reduce the amount of polling while waiting for move to finish
time.sleep(self.SLEEP_TIME) # reduce the amount of polling while waiting for move to finish

if self.board.ser is None:
# Allows testing other game loop functionality with simulated connection to Arduino
Expand All @@ -105,7 +108,7 @@ def process(self, player):

if arduino_status.status == status.ArduinoStatus.IDLE:
# Don't spam new Arduino messages too frequently if waiting for Arduino status to update
time.sleep(1)
time.sleep(self.SLEEP_TIME)

if self.board.msg_queue:
# We have a separate move counter for moves and instructions; to resolve conflicts
Expand Down
Loading