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 17 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
192 changes: 192 additions & 0 deletions firmware/raspi/chessboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
"""
adapted code from:
* Title: chess_tk
* Author: Guatam Sharma
* Date: May 19, 2016
* Availability: https://github.com/saavedra29/chess_tk
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update to:

"""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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a newline between import re and import pieces


START_PATTERN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having this be global, can you pls make it a class variable? You can add it right after the declaration of history = [] and then access it with Board.START_PATTERN



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 = []

def __init__(self, pat=None): # pylint: disable=super-init-not-called unused-argument
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having this pylint disable comment, please add

super().__init__()

as the first line of the function and remove the pylint comment

self.show(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: # pylint: disable=W0702
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having this pylint disable comment, please change the line except: to

except Exception as e:

and remove the pylint comment

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, p1, p2)

def move(self, p1, p2):
"""Moves a piece from p1 to p2
"""
piece = self[p1]
try:
dest = self[p2] #pylint: disable=W0612
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls change the name of dest to _ which signifies that you're intentionally not using the result. then you can remove the pylint disable

except: # pylint: disable=W0702
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above, except Exception as e:

pass
del self[p1]
self[p2] = piece

def complete_move(self, piece, dest, p1, p2): #pylint: disable=unused-argument
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls update the function signature to remove p1 and then remove the pylint disable; you'll have to update the line where this function is called to remove p1 from the arguments

"""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 pieces in self.items(): #pylint: disable=redefined-outer-name unused-variable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change the variable name pieces to _:

for _ in self.items():
    return bool(kingpos in self.all_moves_available(opponent))

and then remove #pylint: disable=redefined-outer-name unused-variable

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
"""
if coord[1] < 0 or coord[1] > 7 or coord[0] < 0 or coord[0] > 7:
return False
else:
return True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can condense this to

return all(coord[1] >= 0, coord[1] <= 7, coord[0] >= 0, coord[0] <= 7)


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])

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
15 changes: 8 additions & 7 deletions firmware/raspi/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,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 +74,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 +85,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(.01) # reduce the amount of polling while waiting for move to finish
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please make this a static class variable called SLEEP_TIME? And then update this and line 309 to use that variable.


if self.board.ser is None:
# Allows testing other game loop functionality with simulated connection to Arduino
Expand All @@ -105,7 +106,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(.01)

if self.board.msg_queue:
# We have a separate move counter for moves and instructions; to resolve conflicts
Expand Down Expand Up @@ -170,5 +171,5 @@ def process(self, player):

raise ValueError("We shouldn't reach this point in the function.")

if __name__ == '__main__':
print("No main for this file, please use `cliinterface.py`")
if __name__ == '__main__':
print("No main for this file, please use `cliinterface.py`")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be indented

Loading