195 lines
7.6 KiB
Python
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
|
||
|
|