This repository has been archived on 2022-12-29. You can view files and clone it, but cannot push or open issues or pull requests.
zeropod/plugin/chess/game.py

195 lines
7.6 KiB
Python

from os import path
from enum import Enum, auto
import time
from cinput import ControlInput, Button
from graphics import Graphics
from .draw import Draw
from . import sunfish
from . import util
class GameState(Enum):
MAIN_MENU = auto()
THINKING = auto()
CHOOSE_SRC = auto()
CHOOSE_DST = auto()
GAME_OVER = auto()
class ChessGame:
SAVE_FILE = path.join("data", "chess_state.fen")
MAX_THINKING_SECONDS = 3
def __init__(self, cinput: ControlInput, graphics: Graphics):
self._cinput = cinput
self._draw = Draw(graphics)
self._state = GameState.MAIN_MENU
self._menu_index = 0
self._searcher = sunfish.Searcher()
def _init_new_game(self, pos: sunfish.Position):
self._hist = [pos]
self._last_move = "Begin!"
self._all_moves = dict()
self._src_idx = 0
self._dst_idx = 0
self._draw.draw_board(self._hist[-1], self._player_color)
def _load_saved_or_init(self):
if path.exists(self.SAVE_FILE):
with open(self.SAVE_FILE, "r") as fen:
fenstr = fen.read()
pos = util.parseFEN(fenstr)
self._player_color = util.get_color(pos)
self._init_new_game(pos)
else:
self._player_color = "w"
self._init_new_game(sunfish.Position(
sunfish.initial, 0, (True,True), (True,True), 0, 0))
def _save_game(self):
with open(self.SAVE_FILE, "w") as fen:
fen.write(util.renderFEN(self._hist[-1]))
def run(self):
# either load the save game or start a new one
self._load_saved_or_init()
while True:
key = None
move = None
src = ""
################
# HANDLE STATE #
################
# draw menu
if self._state == GameState.MAIN_MENU:
self._draw.draw_menu(self._menu_index)
key = self._cinput.get_one_shot()
# computer makes a move
elif self._state == GameState.THINKING:
self._draw.draw_thinking(self._last_move)
start = time.time()
for _, self._move, self._score in self._searcher.search(
self._hist[-1], self._hist):
if time.time() - start > self.MAX_THINKING_SECONDS:
break
self._hist.append(self._hist[-1].move(self._move))
self._draw.draw_board(self._hist[-1], self._player_color)
self._last_move = util.move_str(move, self._player_color == "w")
# reset user moves and move state to user's turn
self._all_moves = dict()
self._state = GameState.CHOOSE_SRC
# user picks source piece
elif self._state == GameState.CHOOSE_SRC:
if len(list(self._all_moves)) == 0:
self._all_moves = util.get_all_moves(self._hist[-1])
src = list(self._all_moves)[self._src_idx]
self._draw.draw_select(self._last_move, src)
key = self._cinput.get_one_shot()
# user picks dest square
elif self._state == GameState.CHOOSE_DST:
src = list(self._all_moves)[self._src_idx]
dst = self._all_moves[src][self._dst_idx]
self._draw.draw_select(self._last_move, src, dst)
key = self._cinput.get_one_shot()
# game has ended
elif self._state == GameState.GAME_OVER:
key = self._cinput.get_one_shot()
################
# HANDLE INPUT #
################
# handle user input on main menu
if self._state == GameState.MAIN_MENU:
# menu cursor up and down
if key == Button.DIR_U:
self._menu_index -= 1
if self._menu_index < 0:
self._menu_index = 0
elif key == Button.DIR_D:
self._menu_index += 1
if self._menu_index > 3:
self._menu_index = 3
# select current menu item
elif key == Button.BTN_B:
if self._menu_index == 0: # play current game
self._state = GameState.CHOOSE_SRC
elif self._menu_index == 1: # save and quit
self._save_game()
return
elif self._menu_index == 2 or self._menu_index == 3: # new game
pos = sunfish.Position(
sunfish.initial, 0, (True,True), (True,True), 0, 0)
if self._menu_index == 3:
self._player_color = "b"
self._state = GameState.THINKING
else:
self._player_color = "w"
self._state = GameState.CHOOSE_SRC
self._init_new_game(pos)
# TODO: figure out why I need to do this
self._draw.draw_board(
pos if self._player_color == "w" else pos.rotate(), self._player_color)
# handle user input when selecting source piece
# TODO: some kind of cursor indicator on board
elif self._state == GameState.CHOOSE_SRC:
# move between source pieces
if key == Button.DIR_D or key == Button.DIR_R:
self._src_idx += 1
if self._src_idx >= len(self._all_moves.keys()):
self._src_idx = 0
elif key == Button.DIR_U or key == Button.DIR_L:
self._src_idx -= 1
if self._src_idx < 0:
self._src_idx = len(list(self._all_moves)) - 1
# back out to main menu
elif key == Button.BTN_A:
self._state = GameState.MAIN_MENU
# move to destination picker
elif key == Button.BTN_B:
self._state = GameState.CHOOSE_DST
# handle user input when selecting dest piece
elif self._state == GameState.CHOOSE_DST:
# move between dest squares for the given source
if key == Button.DIR_D or key == Button.DIR_R:
self._dst_idx += 1
if self._dst_idx >= len(self._all_moves[src]):
self._dst_idx = 0
elif key == Button.DIR_U or key == Button.DIR_L:
self._dst_idx -= 1
if self._dst_idx < 0:
self._dst_idx = len(self._all_moves[src]) - 1
# back out to choose a different source piece
elif key == Button.BTN_A:
self._dst_idx = 0
self._state = GameState.CHOOSE_SRC
# make the move
elif key == Button.BTN_B:
dst = self._all_moves[src][self._dst_idx]
# TODO: fix rotation depending on player color
self._last_move = src + " - " + dst
sfmove = sunfish.parse(src), sunfish.parse(dst)
self._hist.append(self._hist[-1].move(sfmove))
self._draw.draw_board(self._hist[-1].rotate(), self._player_color)
self._state = GameState.THINKING
# handle user input on game over
else:
if key == Button.BTN_A or key == Button.BTN_B:
self._state = GameState.MAIN_MENU