示例#1
0
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()
示例#2
0
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)