예제 #1
0
class TestUDontKnowMe(unittest.TestCase):
    def setUp(self):
        self.brian = Player("Brian")
        self.billy = Player("Billy")
        self.john = Player("John")
        self.jordan = Player("Jordan")
        self.rob = Player("Rob")
        self.all_players = [self.brian, self.billy, self.john, self.jordan, self.rob]  # convenience attribute
        self.udontknowme = Game()

    def test_add_player(self):
        self.udontknowme.add_player(self.brian)
        self.assertTrue(len(self.udontknowme.players) == 1)
        self.assertTrue(self.udontknowme.players[0] is self.brian)
        self.udontknowme.add_player(self.jordan)
        self.assertTrue(len(self.udontknowme.players) == 2)
        self.assertTrue(self.udontknowme.players[1] is self.jordan)

    def test_add_players(self):
        self.udontknowme.add_players([self.brian, self.billy])
        self.assertTrue(len(self.udontknowme.players) == 2)
        self.assertTrue(self.udontknowme.players[0] is self.brian)
        self.assertTrue(self.udontknowme.players[1] is self.billy)

    def test_setup_questions(self):
        self.udontknowme.add_players(self.all_players)
        self.udontknowme.setup_questions()
        self.assertEqual(len(self.udontknowme.questions), 10)  # 2 questions per person
        for player in self.all_players:
            self.assertEqual(
                len(filter(lambda question: question.about_player is player, self.udontknowme.questions)),
                2
            )

    def test_get_next_question(self):
        self.udontknowme.add_players(self.all_players)
        self.udontknowme.setup_questions()
        questions = []
        while True:
            question = self.udontknowme.go_to_next_question()
            if question is not None:
                questions.append(question)
            else:
                break  # make sure we eventually get through all our questions and return None
        self.assertEqual(
            len(questions),
            10
        )

    def test_question_add_answer_and_add_guess_and_num_guesses_and_award_points(self):
        """ This should probably be in a TestQuestion(TestCase) class but I'm being lazy..."""
        self.udontknowme.add_players(self.all_players)
        self.udontknowme.setup_questions()
        question = self.udontknowme.go_to_next_question()
        about_player = question.about_player
        other_players = filter(lambda player: player is not about_player, self.all_players)
        question.add_answer(
            about_player,
            ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
        )
        for other_player in other_players:
            # http://stackoverflow.com/a/2257449/211496
            random_string = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
            question.add_answer(other_player, random_string)
        self.assertTrue(
            len(question.answers),
            5,
        )
        self.assertTrue(
            len(question.guesses.keys()),
            5,
        )
        # about_player will get other_players[0] and other_players[1] to guess them
        # about_player = 2000 pts
        # other_players[0] = 1000 pts
        # other_players[1] = 1000 pts
        question.add_guess(
            other_players[0], filter(lambda answer: answer.player is about_player, question.answers)[0].answer
        )
        question.add_guess(
            other_players[1], filter(lambda answer: answer.player is about_player, question.answers)[0].answer
        )
        # other_players[0] will get other_players[2] to guess them
        # other_players[0] = 1500 pts
        question.add_guess(
            other_players[2], filter(lambda answer: answer.player is other_players[0], question.answers)[0].answer
        )
        # other_players[2] will get other_players[3] to guess them
        # other_players[2] = 500 pts
        question.add_guess(
            other_players[3], filter(lambda answer: answer.player is other_players[2], question.answers)[0].answer
        )
        self.assertTrue(
            question.num_guesses(),
            4,
        )
        question.award_points()
        # make sure the players have the right pts
        self.assertEqual(
            about_player.points,
            2000,
        )
        self.assertEqual(
            other_players[0].points,
            1500,
        )
        self.assertEqual(
            other_players[1].points,
            1000,
        )
        self.assertEqual(
            other_players[2].points,
            500,
        )
        self.assertEqual(
            other_players[3].points,
            0,
        )

    def test_full_game(self):
        self.udontknowme.add_players(self.all_players)
        self.udontknowme.setup_questions()
        while True:
            question = self.udontknowme.go_to_next_question()
            if question is not None:
                about_player = question.about_player
                other_players = filter(lambda player: player is not about_player, self.all_players)
                question.add_answer(about_player, "brains")
                for other_player in other_players:
                    # http://stackoverflow.com/a/2257449/211496
                    random_string = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
                    question.add_answer(other_player, random_string)
                # about_player will get other_players[0] and other_players[1] to guess them
                # about_player = 2000 pts
                # other_players[0] = 1000 pts
                # other_players[1] = 1000 pts
                question.add_guess(
                    other_players[0], filter(lambda answer: answer.player is about_player, question.answers)[0].answer
                )
                question.add_guess(
                    other_players[1], filter(lambda answer: answer.player is about_player, question.answers)[0].answer
                )
                # other_players[0] will get other_players[2] to guess them
                # other_players[0] = 1500 pts
                question.add_guess(
                    other_players[2], filter(lambda answer: answer.player is other_players[0], question.answers)[0].answer
                )
                # other_players[2] will get other_players[3] to guess them
                # other_players[2] = 500 pts
                question.add_guess(
                    other_players[3], filter(lambda answer: answer.player is other_players[2], question.answers)[0].answer
                )
                question.award_points()
            else:
                break
        # GAME IS OVER
        # TODO - Are these numbers correct? Just did a print to get the values and assumed it worked....
        self.assertEqual(
            self.brian.points,
            16000,
        )
        self.assertEqual(
            self.udontknowme.players_sorted_by_points()[0],
            self.brian,
        )
        self.assertEqual(
            self.billy.points,
            13000,
        )
        self.assertEqual(
            self.udontknowme.players_sorted_by_points()[1],
            self.billy,
        )
        self.assertEqual(
            self.john.points,
            10000,
        )
        self.assertEqual(
            self.udontknowme.players_sorted_by_points()[2],
            self.john,
        )
        self.assertEqual(
            self.jordan.points,
            7000,
        )
        self.assertEqual(
            self.udontknowme.players_sorted_by_points()[3],
            self.jordan,
        )
        self.assertEqual(
            self.rob.points,
            4000,
        )
        self.assertEqual(
            self.udontknowme.players_sorted_by_points()[4],
            self.rob,
        )
예제 #2
0
class GameStateMachine(object):
    """
    The state machine that takes incomming messages and moves the state properly.
    """

    TIMER_LENGTH = 31  # seconds

    def __init__(self):
        self.game = Game()
        self.states = self.states()
        self.current_state = 'lobby'
        self.timer = None

    def timer_over(self):
        """
            Return None if there is no timer set
            Return True if the timer that was set previously is over
            Return False if the timer that was set previously is not over
        """
        if self.timer:
            if (datetime.datetime.utcnow() - datetime.timedelta(seconds=self.TIMER_LENGTH)) > self.timer:
                self.timer = None
                return True
            else:
                return False
        else:
            return None

    def seconds_left_in_timer(self):
        if self.timer:
            return max(
                0,
                self.TIMER_LENGTH - (datetime.datetime.utcnow() - self.timer).seconds
            )
        else:
            return 0

    def states(self):
        return {
            'lobby': self.state_lobby,
            'intro': self.state_intro,
            'question_ask': self.state_question_ask,
            'question_guess': self.state_question_guess,
            'show_results': self.state_show_results,
            'show_points': self.state_show_points,
            'game_over': self.state_game_over,
        }

    def new_game(self):
        self.game = Game()  # new game
        self.current_state = 'lobby'  # STATE CHANGE
        message = self.blank_message()
        message['state'] = self.current_state
        message['data']['message'] = 'new_game'
        message['data']['players'] = []
        return message

    def blank_message(self):
        """
        All messages will be in this format
        """
        return {
            'state': '',
            'data': {
                'message': None,
                'timer': None,
            },
        }

    def error_message(self, error):
        message = self.blank_message()
        message['state'] = 'error'
        message['data']['error'] = error
        return message

    def read_message(self, message_dict):
        """
            Message should be a dictionary. For player:
            {
                'player_type': 'console' or 'player'
                'player_name': 'name of player' or None,
                'message': 'message',
            }
        """
        # make sure the message has proper attributes
        if sorted(['player_type', 'player_name', 'message']) == sorted(message_dict.keys()):
            player_type = message_dict['player_type']
            player_name = message_dict['player_name']
            message = message_dict['message']
        else:
            return self.message_format_error('Improperly Formatted Message')
        if message == 'identify':
            return self.identify(player_type, player_name, message=message)
        if message == 'new_game':
            return self.new_game()
        else:
            return self.states[self.current_state](player_type=player_type, player_name=player_name, message=message)

    def identify(self, player_type, player_name, message):
        if player_type == 'console':
            return self.states[self.current_state]()
        elif player_type == 'player':
            if self.current_state == 'lobby':
                # let the user register for the game
                return self.states[self.current_state](player_type=player_type, player_name=player_name, message=message)
            else:
                # check if the user is in this game. If she is, pass the current state, else send an error
                if player_name in [player.name for player in self.game.players]:
                    return self.states[self.current_state]()
                else:
                    return self.error_message('The game has already started. No new players can join')
        else:
            return self.message_format_error('Bad Player Type')

    def state_lobby(self, **kwargs):
        # The only job of the lobby is to add players to the game (if the player_type is 'player')
        if kwargs and kwargs['player_type'] == 'player':
            # add this player to the lobby
            if kwargs['player_name'] not in [player.name for player in self.game.players]:
                self.game.add_player(kwargs['player_name'])
            # check if the user has requested for the game to start
            if kwargs['message'] == 'start':
                self.current_state = 'intro'  # STATE CHANGE
                return self.states[self.current_state]()
        message = self.blank_message()
        message['state'] = self.current_state
        message['data']['players'] = [player.name for player in self.game.players]  # this is mostly for the console
        return message

    def state_intro(self, **kwargs):
        # the only message we want to do anything with here is from the conso
        if kwargs and kwargs['player_type'] == 'console':
            if kwargs['message'] == 'intro_complete':
                self.game.setup_questions()  # set the game up
                self.game.go_to_next_question()
                self.current_state = 'question_ask'  # STATE CHANGE
                return self.states[self.current_state]()
        message = self.blank_message()
        message['state'] = self.current_state
        return message

    def state_question_ask(self, **kwargs):
        question = self.game.current_question
        if kwargs and kwargs['player_type'] == 'system':  # the system is telling us the timer is up
            if kwargs['message'] == 'timer_over':
                self.current_state = 'question_guess'  # STATE CHANGE
                return self.states[self.current_state]()
        # TODO this is async code. We shouldn't allow the user to submit after the timer was set and it's timer_over is True
        elif kwargs and kwargs['player_type'] == 'player':  # at this point we're just waiting on players
            player = self.game.get_player_by_name(kwargs['player_name'])
            question.add_answer(player, kwargs['message'])
            if player is question.about_player:
                self.timer = datetime.datetime.utcnow()
            if len(question.answers) == len(self.game.players):
                self.current_state = 'question_guess'  # STATE CHANGE
                self.timer = datetime.datetime.utcnow()
                return self.states[self.current_state]()
        message = self.blank_message()
        message['state'] = self.current_state
        message['data']['about'] = question.about_player.name
        message['data']['question'] = question.question
        message['data']['submitted_answers'] = [answer.player.name for answer in question.answers]
        message['data']['timer'] = self.seconds_left_in_timer()
        return message

    def state_question_guess(self, **kwargs):
        question = self.game.current_question
        if kwargs and kwargs['player_type'] == 'system':  # the system is telling us the timer is up
            if kwargs['message'] == 'timer_over':
                question.award_points()
                self.current_state = 'show_results'  # STATE CHANGE
                return self.states[self.current_state]()
        # This is async code. We shouldn't allow th euser to submit after the timr was set and it's timer_over is True
        elif kwargs and kwargs['player_type'] == 'player':  # at this point we're just waiting on players
            player = self.game.get_player_by_name(kwargs['player_name'])
            question.add_guess(player, kwargs['message'])
            if question.num_guesses() == len(self.game.players) - 1:  # because the user it's about doesn't guess
                self.timer = None
                question.award_points()
                self.current_state = 'show_results'  # STATE CHANGE
                return self.states[self.current_state]()
        message = self.blank_message()
        message['state'] = self.current_state
        message['data']['timer'] = self.seconds_left_in_timer()
        message['data']['answers'] = [
            {
                'answer': answer_string,
                'players': [answer.player.name for answer in filter(lambda answer: answer.answer == answer_string, question.answers)],  # in case multiple users put the same answer
            }
            for answer_string in set([answer.answer for answer in question.answers])  # keep the answer strings unique
        ]
        # TODO THIS IS CRAY
        message['data']['submitted_guesses'] = []
        for player_array in question.guesses.values():
            for player in player_array:
                message['data']['submitted_guesses'].append(player.name)
        return message

    def state_show_results(self, **kwargs):
        question = self.game.current_question
        if kwargs and kwargs['player_type'] == 'console':  # at this point we're just waiting on the console to say it's done
            if kwargs['message'] == 'results_complete':
                self.current_state = 'show_points'  # STATE CHANGE
                return self.states[self.current_state]()
        message = self.blank_message()
        message['state'] = self.current_state
        message['data']['answers'] = [
            {
                'answer': result[1],
                'guessed': result[2],
                'wrote': [result[0]],  # TODO this needs to be fixed if 2 users "wrote" the same thing....
                'truth': False,
            }
            for result in question.who_guessed_what()
        ]
        message['data']['answers'][-1]['truth'] = True
        return message

    def state_show_points(self, **kwargs):
        if kwargs and kwargs['player_type'] == 'console':  # at this point we're just waiting on the console to say it's done
            if kwargs['message'] == 'points_complete':
                self.current_state = 'question_ask'  # STATE CHANGE
                if self.game.go_to_next_question() is not None:
                    return self.states[self.current_state]()
                else:
                    self.current_state = 'game_over'
                    return self.states[self.current_state]()
        message = self.blank_message()
        message['state'] = self.current_state
        message['data']['points'] = [
            {
                'player': player.name,
                'points': player.points,
            }
            for player in self.game.players_sorted_by_points()
        ]
        return message

    def state_game_over(self, **kwargs):
        message = self.blank_message()
        message['state'] = self.current_state
        message['data']['points'] = [
            {
                'player': player.name,
                'points': player.points,
            }
            for player in self.game.players_sorted_by_points()
        ]
        return message