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