class TerminalGUI(object): def __init__(self, username): self.username = username self._server_protocol = None self._print_buffer = '' self._game_list = [] self._waiting_for_list = False self._waiting_for_join = False self._waiting_for_start = False self._in_game = False self.client = client.Client() self.game_id = None self.pump_return = None self._gui = CursesGUI() self._gui.choice_callback = self.handle_choice_input self._gui.command_callback = self.handle_command_input self._gui.help_callback = self.print_help self.fsm = StateMachine() self.fsm.add_state('START', None, lambda _ : 'MAINMENU') self.fsm.add_state('MAINMENU', self._main_menu_arrival, self._main_menu_transition) self.fsm.add_state('SELECTGAME', self._select_game_arrival, self._select_game_transition) self.fsm.add_state('END', None, None, True) self.fsm.set_start('START') self.fsm.pump(None) def set_log_level(self, level): """Set the log level as per the standard library logging module. Default is logging.INFO. """ self._gui.set_log_level(level) logging.getLogger('gtr.game').setLevel(level) logging.getLogger('gtr').setLevel(level) def set_server_protocol(self, p): """Set the protocol interface to the server. This should be a NetstringReceiver object. """ self._server_protocol = p def _input_choice(self, choice): """Provides an input for the state machine. This function potentially returns a GameAction object, which is a command to be sent to the server. """ # Convert from 1-indexed in the UI to the 0-indexed choices_list self.fsm.pump(int(choice)-1) return self.pump_return def print_help(self): lg.warn('Help! (q to quit)') def handle_command_input(self, command): lg.debug('Handling command input : ' + str(command)) if command in ['help', 'h', '?']: self.print_help() return elif command in ['restart', 'r']: self.client.restart_command() return elif command in ['refresh', 'f']: self.send_command(GameAction(message.REQGAMESTATE)) return elif command in ['quit', 'q']: lg.error('Quitting game') self._server_protocol.loseConnection() self._gui.quit() return if self._in_game and not self.client.builder: lg.error("It's not your turn") return def handle_choice_input(self, choice): """Handles an input choice (integer, 1-indexed). """ try: choice = int(choice) except ValueError: lg.warn('Invalid choice: {0}\n'.format(choice)) return lg.debug('Selection is ' + str(choice)) if self._in_game: self.client.make_choice(choice) action = self.client.check_action_builder() if action is None: lg.debug('More input is required, updating choices list') self.update_choices() else: lg.debug('Sending to server: ' + str(action)) self.client.builder = None self.send_command(action) else: game_action = self._input_choice(choice) if game_action: lg.debug('Sending to server: ' + str(game_action)) self.send_command(game_action) def _main_menu_arrival(self): choices = ['List games', 'Join game', 'Create game', 'Start game'] self.choices = [Choice(desc, desc) for desc in choices] def _main_menu_transition(self, choice): choice = self.choices[choice].item if choice == 'List games': self.pump_return = GameAction(message.REQGAMELIST) self._waiting_for_list = True return 'MAINMENU' elif choice == 'Join game': if len(self._game_list) == 0: lg.info('No games listed. Getting game list first.') self.pump_return = GameAction(message.REQGAMELIST) self._waiting_for_list = True return 'MAINMENU' else: return 'SELECTGAME' elif choice == 'Create game': lg.info('Creating new game.') self.pump_return = GameAction(message.REQCREATEGAME) self._waiting_for_join = True return 'MAINMENU' elif choice == 'Start game': lg.info('Requesting game start.') self.pump_return = GameAction(message.REQSTARTGAME) self._waiting_for_start = True return 'MAINMENU' else: return 'END' def _select_game_arrival(self): # we only get here if the game list is populated self.choices = [Choice(i, str(game)) for i, game in enumerate(self._game_list)] self._show_choices() def _select_game_transition(self, choice): game_id = self.choices[choice].item self._waiting_for_join = True self.pump_return = GameAction(message.REQJOINGAME, game_id) return 'MAINMENU' def _show_choices(self, prompt=None): """Returns the index in the choices_list selected by the user or raises a StartOverException or a CancelDialogExeption. The choices_list is a list of Choices. """ i_choice = 1 choices_list = [] if not self.choices: self.choices.append(Choice(None, "(none)")) for c in self.choices: line = '' if c.selectable: line = ' [{0:2d}] {1}'.format(i_choice, c.description) i_choice+=1 else: line = ' {0}'.format(c.description) choices_list.append(line) self._gui.update_choices(choices_list) if prompt is None: prompt = 'Please make a selection: ' self._gui.update_prompt(prompt) def send_command(self, game_action): self._server_protocol.send_command(self.username, self.game_id, game_action) def update_game_list(self, game_list): self._game_list = game_list if self._waiting_for_list: self._waiting_for_list = False game_list = ['Available games'] for record in self._game_list: game_list.append(' ' + str(record)) self._gui.update_state('\n'.join(game_list)) self._show_choices() def update_game_state(self, game_state): if game_state is None: return player_index = None for p in game_state.players: if p.name == self.username: player_index = game_state.players.index(p) self.client.player_id = player_index break if self._waiting_for_start or game_state.is_started: self._waiting_for_start = False self._in_game = True old_gs = self.client.game.game_state old_game_id = old_gs.game_id if old_gs else None new_game_id = game_state.game_id if old_game_id is None or old_game_id != new_game_id: self.client.update_game_state(game_state) action = self.client.check_action_builder() if action: lg.debug('Action is complete without requiring a choice.') self.send_command(action) else: lg.debug('Action requires user input. Displaying game state.') display = GameStateTextDisplay(game_state, game_state.players[player_index]) self._gui.update_state('\n'.join(display.public_text_string())) self._gui.update_game_log('\n'.join(game_state.game_log)) self.update_choices() def update_choices(self): choices = self.client.get_choices() if choices is None: lg.error('Choices list is empty') import pdb; pdb.set_trace() lines = [' [1] ERROR! Choices list is empty'] else: lines = [] i_choice = 1 for c in choices: if c.selectable: lines.append(' [{0:2d}] {1}'.format(i_choice, c.description)) i_choice+=1 else: lines.append(' {0}'.format(c.description)) self._gui.update_choices(lines) def set_player_id(self, i): self.client.player_id = i def join_game(self, game_id): lg.info('Joined game {0:d}.'.format(game_id)) self.game_id = game_id self._waiting_for_join = False self._show_choices()
class TerminalGUI(object): def __init__(self, username): self.username = username self._server_protocol = None self._print_buffer = '' self._game_list = [] self._waiting_for_list = False self._waiting_for_join = False self._waiting_for_start = False self._in_game = False self.client = client.Client() self.game_id = None self._update_interval = 15 self.pump_return = None self.fsm = StateMachine() self.fsm.add_state('START', None, lambda _: 'MAINMENU') self.fsm.add_state('MAINMENU', self._main_menu_arrival, self._main_menu_transition) self.fsm.add_state('SELECTGAME', self._select_game_arrival, self._select_game_transition) self.fsm.add_state('END', None, None, True) self.fsm.set_start('START') self.fsm.pump(None) def set_server_protocol(self, p): """Set the protocol interface to the server. This should be a NetstringReceiver object. """ self._server_protocol = p def _input_choice(self, choice): """Provides an input for the state machine. This function potentially returns a GameAction object, which is a command to be sent to the server. """ # Convert from 1-indexed in the UI to the 0-indexed choices_list self.fsm.pump(int(choice) - 1) return self.pump_return def handle_client_input(self, command): """Handle input from client. """ if command in ['help', 'h', '?']: self.print_help() return elif command in ['restart', 'r']: self.client.restart_command() return elif command in ['refresh', 'f']: self.send_command(GameAction(message.REQGAMESTATE)) return elif command in ['quit', 'q']: lg.info('Quitting game') self._server_protocol.loseConnection() from twisted.internet import reactor reactor.stop() return if self._in_game and not self.client.builder: lg.info("It's not your turn") return try: choice = int(command) except ValueError: sys.stderr.write('Invalid choice: {0}\n'.format(command)) self.print_help() return if self._in_game: action = self.client.make_choice(choice) if action is not None: print 'doing action ' + repr(action) self.send_command(action) else: game_action = self._input_choice(choice) if game_action: self.send_command(game_action) def _main_menu_arrival(self): choices = ['List games', 'Join game', 'Create game', 'Start game'] self.choices = [Choice(desc, desc) for desc in choices] def _main_menu_transition(self, choice): choice = self.choices[choice].item if choice == 'List games': self.pump_return = GameAction(message.REQGAMELIST) self._waiting_for_list = True return 'MAINMENU' elif choice == 'Join game': if len(self._game_list) == 0: lg.info('No games listed. Getting game list first.') self.pump_return = GameAction(message.REQGAMELIST) self._waiting_for_list = True return 'MAINMENU' else: return 'SELECTGAME' elif choice == 'Create game': lg.info('Creating new game...') self.pump_return = GameAction(message.REQCREATEGAME) self._waiting_for_join = True return 'MAINMENU' elif choice == 'Start game': lg.info('Requesting game start...') self.pump_return = GameAction(message.REQSTARTGAME) self._waiting_for_start = True return 'MAINMENU' else: return 'END' def _select_game_arrival(self): # we only get here if the game list is populated self.choices = [ Choice(i, str(game)) for i, game in enumerate(self._game_list) ] self._show_choices() def _select_game_transition(self, choice): game_id = self.choices[choice].item self._waiting_for_join = True self.pump_return = GameAction(message.REQJOINGAME, game_id) return 'MAINMENU' def _show_choices(self, prompt=None): """Returns the index in the choices_list selected by the user or raises a StartOverException or a CancelDialogExeption. The choices_list is a list of Choices. """ i_choice = 1 for c in self.choices: if c.selectable: lg.info(' [{0:2d}] {1}'.format(i_choice, c.description)) i_choice += 1 else: lg.info(' {0}'.format(c.description)) if prompt is not None: lg.info(prompt) else: lg.info('Please make a selection:') def send_command(self, game_action): self._server_protocol.send_command(self.username, self.game_id, game_action) def update_game_list(self, game_list): self._game_list = game_list if self._waiting_for_list: self._waiting_for_list = False print 'List of games:' for record in self._game_list: print ' ' + str(record) print self._show_choices() def update_game_state(self, game_state): if game_state is None: return for p in game_state.players: if p.name == self.username: player_index = game_state.players.index(p) self.client.player_id = player_index if self._waiting_for_start or game_state.is_started: lg.info('Starting game ...') self._waiting_for_start = False self._in_game = True old_gs = self.client.game.game_state old_game_id = old_gs.game_id if old_gs else None new_game_id = game_state.game_id if old_game_id is None or old_game_id != new_game_id: self.client.update_game_state(game_state) def set_player_id(self, i): self.client.player_id = i def join_game(self, game_id): lg.info('Joined game {0:d}, waiting to start...'.format(game_id)) self.game_id = game_id self._waiting_for_join = False self._show_choices() #self.routine_update() def routine_update(self): """Updates the game state routinely. """ self.send_command(GameAction(message.REQGAMESTATE)) from twisted.internet import reactor reactor.callLater(self._update_interval, self.routine_update)