interactive bots: Create connect four bot.
This commit is contained in:
		
							parent
							
								
									bcca5230a3
								
							
						
					
					
						commit
						35829218b7
					
				
					 5 changed files with 1265 additions and 0 deletions
				
			
		
							
								
								
									
										0
									
								
								zulip_bots/zulip_bots/bots/connect_four/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								zulip_bots/zulip_bots/bots/connect_four/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										535
									
								
								zulip_bots/zulip_bots/bots/connect_four/connect_four.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										535
									
								
								zulip_bots/zulip_bots/bots/connect_four/connect_four.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,535 @@
 | 
				
			||||||
 | 
					# @TODO: place bot owner name in config file, allow bot owner to run special commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					from copy import deepcopy
 | 
				
			||||||
 | 
					from zulip_bots.bots.connect_four.controller import ConnectFourModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InputVerification(object):
 | 
				
			||||||
 | 
					    verified_users = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    all_valid_commands = ['help', 'status', 'start game with computer', 'start game with \w+@\w+\.\w+',
 | 
				
			||||||
 | 
					                          'withdraw invitation', 'accept', 'decline', 'move \d$', 'quit', 'confirm quit']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Every command that can be run, in states requiring user verification, by each player
 | 
				
			||||||
 | 
					    verified_commands = {
 | 
				
			||||||
 | 
					        'waiting': ['start game with computer', 'start game with \w+@\w+\.\w+'],
 | 
				
			||||||
 | 
					        'inviting': [['withdraw invitation'], ['accept', 'decline']],
 | 
				
			||||||
 | 
					        'playing': [['move \d$', 'quit', 'confirm quit'], ['quit', 'confirm quit']]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def permission_lacking_message(self, command):
 | 
				
			||||||
 | 
					        return 'Sorry, but you can\'t run the command ```' + command + '```'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_commands(self, turn):
 | 
				
			||||||
 | 
					        self.verified_commands['playing'][-1 * turn + 1].remove('move \d$')
 | 
				
			||||||
 | 
					        self.verified_commands['playing'][turn].append('move \d$')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def reset_commands(self):
 | 
				
			||||||
 | 
					        self.verified_commands['playing'] = [['move \d$', 'quit', 'confirm quit'], ['quit', 'confirm quit']]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def regex_match_in_array(self, command_array, command):
 | 
				
			||||||
 | 
					        for command_regex in command_array:
 | 
				
			||||||
 | 
					            if re.compile(command_regex).match(command.lower()):
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def valid_command(self, command):
 | 
				
			||||||
 | 
					        return self.regex_match_in_array(self.all_valid_commands, command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def verify_user(self, user):
 | 
				
			||||||
 | 
					        return user in self.verified_users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def verify_command(self, user, command, state):
 | 
				
			||||||
 | 
					        if state != 'waiting':
 | 
				
			||||||
 | 
					            command_array = self.verified_commands[state][self.verified_users.index(user)]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            command_array = self.verified_commands[state]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.regex_match_in_array(command_array, command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StateManager(object):
 | 
				
			||||||
 | 
					    def __init__(self, main_bot_handler):
 | 
				
			||||||
 | 
					        self.users = None
 | 
				
			||||||
 | 
					        self.state = ''
 | 
				
			||||||
 | 
					        self.user_messages = []
 | 
				
			||||||
 | 
					        self.opponent_messages = []
 | 
				
			||||||
 | 
					        self.main_bot_handler = main_bot_handler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Updates to the main bot handler that all state managers must use
 | 
				
			||||||
 | 
					    def basic_updates(self):
 | 
				
			||||||
 | 
					        if self.users is not None:
 | 
				
			||||||
 | 
					            self.main_bot_handler.inputVerification.verified_users = self.users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.state:
 | 
				
			||||||
 | 
					            self.main_bot_handler.state = self.state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.main_bot_handler.user_messages = self.user_messages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.main_bot_handler.opponent_messages = self.opponent_messages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def reset_self(self):
 | 
				
			||||||
 | 
					        self.users = None
 | 
				
			||||||
 | 
					        self.user_messages = []
 | 
				
			||||||
 | 
					        self.opponent_messages = []
 | 
				
			||||||
 | 
					        self.state = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GameCreator(StateManager):
 | 
				
			||||||
 | 
					    def __init__(self, main_bot_handler):
 | 
				
			||||||
 | 
					        super(GameCreator, self).__init__(main_bot_handler)
 | 
				
			||||||
 | 
					        self.gameHandler = None
 | 
				
			||||||
 | 
					        self.invitationHandler = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_message(self, content, sender):
 | 
				
			||||||
 | 
					        if content == 'start game with computer':
 | 
				
			||||||
 | 
					            self.users = [sender]
 | 
				
			||||||
 | 
					            self.state = 'playing'
 | 
				
			||||||
 | 
					            self.gameHandler = GameHandler(self.main_bot_handler, 'one_player')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.user_messages.append('**You started a new game with the computer!**')
 | 
				
			||||||
 | 
					            self.user_messages.append(self.gameHandler.parse_board())
 | 
				
			||||||
 | 
					            self.user_messages.append(self.gameHandler.your_turn_message())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif re.compile('\w+@\w+\.\w+').search(content):
 | 
				
			||||||
 | 
					            opponent = re.compile('(\w+@\w+\.\w+)').search(content).group(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if opponent == sender:
 | 
				
			||||||
 | 
					                self.user_messages.append('You can\'t play against yourself!')
 | 
				
			||||||
 | 
					                self.update_main_bot_handler()
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.users = [sender, opponent]
 | 
				
			||||||
 | 
					            self.state = 'inviting'
 | 
				
			||||||
 | 
					            self.gameHandler = GameHandler(self.main_bot_handler, 'two_player')
 | 
				
			||||||
 | 
					            self.invitationHandler = InvitationHandler(self.main_bot_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.user_messages.append(self.invitationHandler.confirm_new_invitation(opponent))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.opponent_messages.append(self.invitationHandler.alert_new_invitation(sender))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.update_main_bot_handler()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_main_bot_handler(self):
 | 
				
			||||||
 | 
					        self.basic_updates()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.main_bot_handler.player_cache = self.users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.main_bot_handler.gameHandler = deepcopy(self.gameHandler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.invitationHandler:
 | 
				
			||||||
 | 
					            self.main_bot_handler.invitationHandler = deepcopy(self.invitationHandler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.reset_self()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GameHandler(StateManager):
 | 
				
			||||||
 | 
					    def __init__(self, main_bot_handler, game_type, board = ConnectFourModel().blank_board, turn = 0):
 | 
				
			||||||
 | 
					        super(GameHandler, self).__init__(main_bot_handler)
 | 
				
			||||||
 | 
					        self.game_type = game_type
 | 
				
			||||||
 | 
					        self.board = board
 | 
				
			||||||
 | 
					        self.turn = turn
 | 
				
			||||||
 | 
					        self.game_ended = False
 | 
				
			||||||
 | 
					        self.connectFourModel = ConnectFourModel()
 | 
				
			||||||
 | 
					        self.connectFourModel.update_board(board)
 | 
				
			||||||
 | 
					        self.tokens = [':blue_circle:', ':red_circle:']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parse_board(self):
 | 
				
			||||||
 | 
					        # Header for the top of the board
 | 
				
			||||||
 | 
					        board_str = ':one: :two: :three: :four: :five: :six: :seven:'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for row in range(0, 6):
 | 
				
			||||||
 | 
					            board_str += '\n\n'
 | 
				
			||||||
 | 
					            for column in range(0, 7):
 | 
				
			||||||
 | 
					                if self.board[row][column] == 0:
 | 
				
			||||||
 | 
					                    board_str += ':heavy_large_circle: '
 | 
				
			||||||
 | 
					                elif self.board[row][column] == 1:
 | 
				
			||||||
 | 
					                    board_str += ':blue_circle: '
 | 
				
			||||||
 | 
					                elif self.board[row][column] == -1:
 | 
				
			||||||
 | 
					                    board_str += ':red_circle: '
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return board_str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def your_turn_message(self):
 | 
				
			||||||
 | 
					        return '**It\'s your move!**\n' +\
 | 
				
			||||||
 | 
					               'type ```move <column-number>``` to make your move\n\n' +\
 | 
				
			||||||
 | 
					               'You are ' + self.tokens[self.turn]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def wait_turn_message(self, opponent):
 | 
				
			||||||
 | 
					        return 'Waiting for ' + opponent + ' to move'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def alert_move_message(self, original_player, column_number):
 | 
				
			||||||
 | 
					        return '**' + original_player + ' moved in column ' + str(column_number + 1) + '**.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def append_game_over_messages(self, result):
 | 
				
			||||||
 | 
					        if result == 'draw':
 | 
				
			||||||
 | 
					            self.user_messages.append('**It\'s a draw!**')
 | 
				
			||||||
 | 
					            self.opponent_messages.append('**It\'s a draw!**')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if result != 'the Computer':
 | 
				
			||||||
 | 
					                self.user_messages.append('**Congratulations, you win! :tada:**')
 | 
				
			||||||
 | 
					                self.opponent_messages.append('Sorry, but ' + result + ' won :cry:')
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.user_messages.append('Sorry, but ' + result + ' won :cry:')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_player_token(self, sender):
 | 
				
			||||||
 | 
					        player = self.main_bot_handler.inputVerification.verified_users.index(sender)
 | 
				
			||||||
 | 
					        # This computation will return 1 for player 0, and -1 for player 1, as is expected
 | 
				
			||||||
 | 
					        return (-2) * player + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def toggle_turn(self):
 | 
				
			||||||
 | 
					        self.turn = (-1) * self.turn + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def end_game(self):
 | 
				
			||||||
 | 
					        self.state = 'waiting'
 | 
				
			||||||
 | 
					        self.game_ended = True
 | 
				
			||||||
 | 
					        self.users = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_move(self, column_number, token_number, player_one, player_two, computer_play = False):
 | 
				
			||||||
 | 
					        if not self.connectFourModel.validate_move(column_number):
 | 
				
			||||||
 | 
					            self.user_messages.append('That\'s an invalid move. Please specify a column' +
 | 
				
			||||||
 | 
					                                      ' with at least one blank space, between 1 and 7')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.board = self.connectFourModel.make_move(column_number, token_number)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not computer_play:
 | 
				
			||||||
 | 
					            self.user_messages.append('You placed your token in column ' + str(column_number + 1) + '.')
 | 
				
			||||||
 | 
					            self.user_messages.append(self.parse_board())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.opponent_messages.append(self.alert_move_message(self.sender, column_number))
 | 
				
			||||||
 | 
					            self.opponent_messages.append(self.parse_board())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.user_messages.append(self.alert_move_message('the Computer', column_number))
 | 
				
			||||||
 | 
					            self.user_messages.append(self.parse_board())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        game_over = self.connectFourModel.determine_game_over(player_one, player_two)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if game_over:
 | 
				
			||||||
 | 
					            self.append_game_over_messages(game_over)
 | 
				
			||||||
 | 
					            self.end_game()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.toggle_turn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.main_bot_handler.inputVerification.update_commands(self.turn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not computer_play:
 | 
				
			||||||
 | 
					                self.user_messages.append(self.wait_turn_message(self.opponent))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.opponent_messages.append(self.your_turn_message())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.user_messages.append(self.your_turn_message())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_message(self, content, sender):
 | 
				
			||||||
 | 
					        self.sender = sender
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.game_type == 'two_player':
 | 
				
			||||||
 | 
					            opponent_array = deepcopy(self.main_bot_handler.inputVerification.verified_users)
 | 
				
			||||||
 | 
					            opponent_array.remove(sender)
 | 
				
			||||||
 | 
					            self.opponent = opponent_array[0]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.opponent = 'the Computer'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if content == 'quit':
 | 
				
			||||||
 | 
					            self.user_messages.append('Are you sure you want to quit? You will forfeit the game!\n' +
 | 
				
			||||||
 | 
					                                      'Type ```confirm quit``` to forfeit.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif content == 'confirm quit':
 | 
				
			||||||
 | 
					            self.end_game()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.user_messages.append('**You have forfeit the game**\nSorry, but you lost :cry:')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.opponent_messages.append('**' + sender + ' has forfeit the game**\nCongratulations, you win! :tada:')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif re.compile('move \d$').match(content):
 | 
				
			||||||
 | 
					            player_one = player_one = self.main_bot_handler.inputVerification.verified_users[0]
 | 
				
			||||||
 | 
					            player_two = 'the Computer' if self.game_type == 'one_player' else self.main_bot_handler.inputVerification.verified_users[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            human_move = int(re.compile('move (\d)$').search(content).group(1)) - 1
 | 
				
			||||||
 | 
					            human_token_number = self.get_player_token(sender)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.handle_move(human_move, human_token_number, player_one, player_two)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not self.game_ended and self.game_type == 'one_player':
 | 
				
			||||||
 | 
					                computer_move = self.connectFourModel.computer_move()
 | 
				
			||||||
 | 
					                computer_token_number = -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.handle_move(computer_move, computer_token_number, player_one, player_two, computer_play = True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.update_main_bot_handler()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_main_bot_handler(self):
 | 
				
			||||||
 | 
					        if self.game_type == 'one_player':
 | 
				
			||||||
 | 
					            self.opponent_messages = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.basic_updates()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.game_ended:
 | 
				
			||||||
 | 
					            self.main_bot_handler.gameHandler = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.reset_self()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InvitationHandler(StateManager):
 | 
				
			||||||
 | 
					    def __init__(self, main_bot_handler):
 | 
				
			||||||
 | 
					        super(InvitationHandler, self).__init__(main_bot_handler)
 | 
				
			||||||
 | 
					        self.game_cancelled = False
 | 
				
			||||||
 | 
					        self.gameHandler = object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def confirm_new_invitation(self, opponent):
 | 
				
			||||||
 | 
					        return 'You\'ve sent an invitation to play Connect Four with ' +\
 | 
				
			||||||
 | 
					            opponent + '. I\'ll let you know when they respond to the invitation'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def alert_new_invitation(self, challenger):
 | 
				
			||||||
 | 
					        # Since the first player invites, the challenger is always the first player
 | 
				
			||||||
 | 
					        return '**' + challenger + ' has invited you to play a game of Connect Four.**\n' +\
 | 
				
			||||||
 | 
					            'Type ```accept``` to accept the game invitation\n' +\
 | 
				
			||||||
 | 
					            'Type ```decline``` to decline the game invitation.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_message(self, content, sender):
 | 
				
			||||||
 | 
					        challenger = self.main_bot_handler.inputVerification.verified_users[0]
 | 
				
			||||||
 | 
					        opponent = self.main_bot_handler.inputVerification.verified_users[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if content.lower() == 'accept':
 | 
				
			||||||
 | 
					            self.state = 'playing'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.user_messages.append('You accepted the invitation to play with ' + challenger)
 | 
				
			||||||
 | 
					            self.user_messages.append(self.main_bot_handler.gameHandler.wait_turn_message(challenger))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.opponent_messages.append('**' + opponent + ' has accepted your invitation to play**')
 | 
				
			||||||
 | 
					            self.opponent_messages.append(self.main_bot_handler.gameHandler.parse_board())
 | 
				
			||||||
 | 
					            self.opponent_messages.append(self.main_bot_handler.gameHandler.your_turn_message())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif content.lower() == 'decline':
 | 
				
			||||||
 | 
					            self.state = 'waiting'
 | 
				
			||||||
 | 
					            self.users = []
 | 
				
			||||||
 | 
					            self.gameHandler = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.user_messages.append('You declined the invitation to play with ' + challenger)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.opponent_messages.append('**' + opponent + ' has declined your invitation to play**\n' +
 | 
				
			||||||
 | 
					                                          'Invite another player by typing ```start game with user@example.com```')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif content.lower() == 'withdraw invitation':
 | 
				
			||||||
 | 
					            self.state = 'waiting'
 | 
				
			||||||
 | 
					            self.users = []
 | 
				
			||||||
 | 
					            self.gameHandler = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.user_messages.append('Your invitation to play ' + opponent + ' has been withdrawn')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.opponent_messages.append('**' + challenger + ' has withdrawn his invitation to play you**\n' +
 | 
				
			||||||
 | 
					                                          'Type ``` start game with ' + challenger + '``` if you would like to play them.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.update_main_bot_handler()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_main_bot_handler(self):
 | 
				
			||||||
 | 
					        self.basic_updates()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.main_bot_handler.invitationHandler = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.gameHandler is None:
 | 
				
			||||||
 | 
					            self.main_bot_handler.gameHandler = self.gameHandler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.reset_self()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConnectFourBotHandler(object):
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    Bot that allows users to player another user
 | 
				
			||||||
 | 
					    or the computer in a game of Connect Four
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_stored_data(self):
 | 
				
			||||||
 | 
					        # return self.data # Uncomment and rerun bot to reset data if users are abusing the bot
 | 
				
			||||||
 | 
					        return self.bot_handler.storage.get('connect_four')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_data(self):
 | 
				
			||||||
 | 
					        self.state = self.data['state']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'users' in self.data:
 | 
				
			||||||
 | 
					            self.inputVerification.verified_users = self.data['users']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.state == 'inviting':
 | 
				
			||||||
 | 
					            self.invitationHandler = InvitationHandler(self)
 | 
				
			||||||
 | 
					            self.gameHandler = GameHandler(self, self.data['game_type'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif self.state == 'playing':
 | 
				
			||||||
 | 
					            self.gameHandler = GameHandler(self, self.data['game_type'], board = self.data['board'], turn = self.data['turn'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def put_stored_data(self):
 | 
				
			||||||
 | 
					        self.data = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.data['state'] = self.state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.inputVerification.verified_users:
 | 
				
			||||||
 | 
					            self.data['users'] = self.inputVerification.verified_users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.state == 'inviting':
 | 
				
			||||||
 | 
					            self.data['game_type'] = self.gameHandler.game_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif self.state == 'playing':
 | 
				
			||||||
 | 
					            self.data['game_type'] = self.gameHandler.game_type
 | 
				
			||||||
 | 
					            self.data['board'] = self.gameHandler.board
 | 
				
			||||||
 | 
					            self.data['turn'] = self.gameHandler.turn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.bot_handler.storage.put('connect_four', self.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Stores the current state of the game. Either 'waiting 'inviting' or 'playing'
 | 
				
			||||||
 | 
					    state = 'waiting'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Stores the users, in case one of the state managers modifies the verified users
 | 
				
			||||||
 | 
					    player_cache = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Object-wide storage to the bot_handler to allow custom message-sending function
 | 
				
			||||||
 | 
					    bot_handler = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inputVerification = InputVerification()
 | 
				
			||||||
 | 
					    invitationHandler = None
 | 
				
			||||||
 | 
					    gameHandler = None
 | 
				
			||||||
 | 
					    gameCreator = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user_messages = []
 | 
				
			||||||
 | 
					    opponent_messages = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Stores a compact version of all data the bot is managing
 | 
				
			||||||
 | 
					    data = {'state': 'waiting'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def status_message(self):
 | 
				
			||||||
 | 
					        prefix = '**Connect Four Game Status**\n' +\
 | 
				
			||||||
 | 
					            '*If you suspect users are abusing the bot,' +\
 | 
				
			||||||
 | 
					            ' please alert the bot owner*\n\n'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.state == 'playing':
 | 
				
			||||||
 | 
					            if self.gameHandler.game_type == 'one_player':
 | 
				
			||||||
 | 
					                message = 'The bot is currently running a single player game' +\
 | 
				
			||||||
 | 
					                          ' for ' + self.inputVerification.verified_users[0] + '.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            elif self.gameHandler.game_type == 'two_player':
 | 
				
			||||||
 | 
					                message = 'The bot is currently running a two player game ' +\
 | 
				
			||||||
 | 
					                          'between ' + self.inputVerification.verified_users[0] +\
 | 
				
			||||||
 | 
					                          ' and ' + self.inputVerification.verified_users[1] + '.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif self.state == 'inviting':
 | 
				
			||||||
 | 
					            message = self.inputVerification.verified_users[0] + '\'s' +\
 | 
				
			||||||
 | 
					                ' invitation to play ' + self.inputVerification.verified_users[1] +\
 | 
				
			||||||
 | 
					                ' is still pending. Wait for the game to finish to play a game.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif self.state == 'waiting':
 | 
				
			||||||
 | 
					            message = '**The bot is not running a game right now!**\n' + \
 | 
				
			||||||
 | 
					                'Type ```start game with user@example.com``` '  +\
 | 
				
			||||||
 | 
					                'to start a game with another user,\n' +\
 | 
				
			||||||
 | 
					                'or type ```start game with computer``` ' +\
 | 
				
			||||||
 | 
					                'to start a game with the computer'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return prefix + message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def help_message(self):
 | 
				
			||||||
 | 
					        return '**Connect Four Bot Help:**\n' + \
 | 
				
			||||||
 | 
					            '*Preface all commands with @bot-name*\n\n' + \
 | 
				
			||||||
 | 
					            '* To see the current status of the game, type\n' + \
 | 
				
			||||||
 | 
					            '```status```\n' + \
 | 
				
			||||||
 | 
					            '* To start a game against the computer, type\n' + \
 | 
				
			||||||
 | 
					            '```start game with computer```\n' +\
 | 
				
			||||||
 | 
					            '* To start a game against another player, type\n' + \
 | 
				
			||||||
 | 
					            '```start game with user@example.com```\n' + \
 | 
				
			||||||
 | 
					            '* To make your move during a game, type\n' + \
 | 
				
			||||||
 | 
					            '```move <column-number>```\n' + \
 | 
				
			||||||
 | 
					            '* To quit a game at any time, type\n' + \
 | 
				
			||||||
 | 
					            '```quit```\n' + \
 | 
				
			||||||
 | 
					            '* To withdraw an invitation, type\n' + \
 | 
				
			||||||
 | 
					            '```cancel game```'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def send_message(self, user, content):
 | 
				
			||||||
 | 
					        self.bot_handler.send_message(dict(
 | 
				
			||||||
 | 
					            type = 'private',
 | 
				
			||||||
 | 
					            to = user,
 | 
				
			||||||
 | 
					            content = content
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Sends messages returned from helper classes, where user, is the user who sent the bot the original messages
 | 
				
			||||||
 | 
					    def send_message_arrays(self, user):
 | 
				
			||||||
 | 
					        if self.opponent_messages:
 | 
				
			||||||
 | 
					            opponent_array = deepcopy(self.player_cache)
 | 
				
			||||||
 | 
					            opponent_array.remove(user)
 | 
				
			||||||
 | 
					            opponent = opponent_array[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for message in self.user_messages:
 | 
				
			||||||
 | 
					            self.send_message(user, message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for message in self.opponent_messages:
 | 
				
			||||||
 | 
					            self.send_message(opponent, message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.user_messages = []
 | 
				
			||||||
 | 
					        self.opponent_messages = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parse_message(self, message):
 | 
				
			||||||
 | 
					        content = message['content'].strip()
 | 
				
			||||||
 | 
					        sender = message['sender_email']
 | 
				
			||||||
 | 
					        return (content, sender)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def usage(self):
 | 
				
			||||||
 | 
					        return '''
 | 
				
			||||||
 | 
					        Bot that allows users to play another user
 | 
				
			||||||
 | 
					        or the computer in a game of Connect Four.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        To see the entire list of commands, type
 | 
				
			||||||
 | 
					        @bot-name help
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def initialize(self, bot_handler):
 | 
				
			||||||
 | 
					        self.gameCreator = GameCreator(self)
 | 
				
			||||||
 | 
					        self.inputVerification.reset_commands()
 | 
				
			||||||
 | 
					        if not bot_handler.storage.contains('connect_four'):
 | 
				
			||||||
 | 
					            bot_handler.storage.put('connect_four', self.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_message(self, message, bot_handler):
 | 
				
			||||||
 | 
					        self.bot_handler = bot_handler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.data = self.get_stored_data()
 | 
				
			||||||
 | 
					        self.update_data()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.player_cache = self.inputVerification.verified_users
 | 
				
			||||||
 | 
					        content, sender = self.parse_message(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not self.inputVerification.valid_command(content.lower()):
 | 
				
			||||||
 | 
					            self.send_message(sender, 'Sorry, but I couldn\'t understand your input.\n'
 | 
				
			||||||
 | 
					                                      'Type ```help``` to see a full list of commands.')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Messages that can be sent regardless of state or user
 | 
				
			||||||
 | 
					        elif content.lower() == 'help' or content == '':
 | 
				
			||||||
 | 
					            self.send_message(sender, self.help_message())
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif content.lower() == 'status':
 | 
				
			||||||
 | 
					            self.send_message(sender, self.status_message())
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif self.state == 'waiting':
 | 
				
			||||||
 | 
					            if not self.inputVerification.verify_command(sender, content.lower(), 'waiting'):
 | 
				
			||||||
 | 
					                self.send_message(sender, self.inputVerification.permission_lacking_message(content))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.gameCreator.handle_message(content, sender)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif not self.inputVerification.verify_user(sender):
 | 
				
			||||||
 | 
					            self.send_message(sender, 'Sorry, but other users are already using the bot.'
 | 
				
			||||||
 | 
					                                      'Type ```status``` to see the current status of the bot.')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif self.state == 'inviting':
 | 
				
			||||||
 | 
					            if not self.inputVerification.verify_command(sender, content.lower(), 'inviting'):
 | 
				
			||||||
 | 
					                self.send_message(sender, self.inputVerification.permission_lacking_message(content))
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.invitationHandler.handle_message(content, sender)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif self.state == 'playing':
 | 
				
			||||||
 | 
					            if not self.inputVerification.verify_command(sender, content.lower(), 'playing'):
 | 
				
			||||||
 | 
					                self.send_message(sender, self.inputVerification.permission_lacking_message(content))
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.gameHandler.handle_message(content, sender)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.send_message_arrays(sender)
 | 
				
			||||||
 | 
					        self.put_stored_data()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					handler_class = ConnectFourBotHandler
 | 
				
			||||||
							
								
								
									
										134
									
								
								zulip_bots/zulip_bots/bots/connect_four/controller.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								zulip_bots/zulip_bots/bots/connect_four/controller.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,134 @@
 | 
				
			||||||
 | 
					from copy import deepcopy
 | 
				
			||||||
 | 
					from random import randint
 | 
				
			||||||
 | 
					from functools import reduce
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConnectFourModel(object):
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    Object that manages running the Connect
 | 
				
			||||||
 | 
					    Four logic for the Connect Four Bot
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    blank_board = [
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    current_board = blank_board
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_board(self, board):
 | 
				
			||||||
 | 
					        self.current_board = deepcopy(board)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate_move(self, column_number):
 | 
				
			||||||
 | 
					        if column_number < 0 or column_number > 6:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        row = 0
 | 
				
			||||||
 | 
					        column = column_number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.current_board[row][column] == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def available_moves(self):
 | 
				
			||||||
 | 
					        available_moves = []
 | 
				
			||||||
 | 
					        row = 0
 | 
				
			||||||
 | 
					        for column in range(0, 7):
 | 
				
			||||||
 | 
					            if self.current_board[row][column] == 0:
 | 
				
			||||||
 | 
					                available_moves.append(column)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return available_moves
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def make_move(self, column_number, token_number):
 | 
				
			||||||
 | 
					        finding_move = True
 | 
				
			||||||
 | 
					        row = 5
 | 
				
			||||||
 | 
					        column = column_number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while finding_move:
 | 
				
			||||||
 | 
					            if self.current_board[row][column] == 0:
 | 
				
			||||||
 | 
					                self.current_board[row][column] = token_number
 | 
				
			||||||
 | 
					                finding_move = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            row -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return deepcopy(self.current_board)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def determine_game_over(self, first_player, second_player):
 | 
				
			||||||
 | 
					        def get_horizontal_wins(board):
 | 
				
			||||||
 | 
					            horizontal_sum = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for row in range(0, 6):
 | 
				
			||||||
 | 
					                for column in range(0, 4):
 | 
				
			||||||
 | 
					                    horizontal_sum = board[row][column] + board[row][column + 1] + \
 | 
				
			||||||
 | 
					                        board[row][column + 2] + board[row][column + 3]
 | 
				
			||||||
 | 
					                    if horizontal_sum == -4:
 | 
				
			||||||
 | 
					                        return -1
 | 
				
			||||||
 | 
					                    elif horizontal_sum == 4:
 | 
				
			||||||
 | 
					                        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def get_vertical_wins(board):
 | 
				
			||||||
 | 
					            vertical_sum = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for row in range(0, 3):
 | 
				
			||||||
 | 
					                for column in range(0, 7):
 | 
				
			||||||
 | 
					                    vertical_sum = board[row][column] + board[row + 1][column] + \
 | 
				
			||||||
 | 
					                        board[row + 2][column] + board[row + 3][column]
 | 
				
			||||||
 | 
					                    if vertical_sum == -4:
 | 
				
			||||||
 | 
					                        return -1
 | 
				
			||||||
 | 
					                    elif vertical_sum == 4:
 | 
				
			||||||
 | 
					                        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def get_diagonal_wins(board):
 | 
				
			||||||
 | 
					            major_diagonal_sum = 0
 | 
				
			||||||
 | 
					            minor_diagonal_sum = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Major Diagonl Sum
 | 
				
			||||||
 | 
					            for row in range(0, 3):
 | 
				
			||||||
 | 
					                for column in range(0, 4):
 | 
				
			||||||
 | 
					                    major_diagonal_sum = board[row][column] + board[row + 1][column + 1] + \
 | 
				
			||||||
 | 
					                        board[row + 2][column + 2] + board[row + 3][column + 3]
 | 
				
			||||||
 | 
					                    if major_diagonal_sum == -4:
 | 
				
			||||||
 | 
					                        return -1
 | 
				
			||||||
 | 
					                    elif major_diagonal_sum == 4:
 | 
				
			||||||
 | 
					                        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Minor Diagonal Sum
 | 
				
			||||||
 | 
					            for row in range(3, 6):
 | 
				
			||||||
 | 
					                for column in range(0, 4):
 | 
				
			||||||
 | 
					                    minor_diagonal_sum = board[row][column] + board[row - 1][column + 1] + \
 | 
				
			||||||
 | 
					                        board[row - 2][column + 2] + board[row - 3][column + 3]
 | 
				
			||||||
 | 
					                    if minor_diagonal_sum == -4:
 | 
				
			||||||
 | 
					                        return -1
 | 
				
			||||||
 | 
					                    elif minor_diagonal_sum == 4:
 | 
				
			||||||
 | 
					                        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # If all tokens in top row are filled (its a draw), product != 0
 | 
				
			||||||
 | 
					        top_row_multiple = reduce(lambda x, y: x*y, self.current_board[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if top_row_multiple != 0:
 | 
				
			||||||
 | 
					            return 'draw'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        winner = get_horizontal_wins(self.current_board) + \
 | 
				
			||||||
 | 
					            get_vertical_wins(self.current_board) + \
 | 
				
			||||||
 | 
					            get_diagonal_wins(self.current_board)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if winner == 1:
 | 
				
			||||||
 | 
					            return first_player
 | 
				
			||||||
 | 
					        elif winner == -1:
 | 
				
			||||||
 | 
					            return second_player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def computer_move(self):
 | 
				
			||||||
 | 
					        # @TODO: Make the computer more intelligent
 | 
				
			||||||
 | 
					        # perhaps by implementing minimax
 | 
				
			||||||
 | 
					        available_moves = deepcopy(self.available_moves())
 | 
				
			||||||
 | 
					        final_move = available_moves[randint(0, len(available_moves) - 1)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return final_move
 | 
				
			||||||
							
								
								
									
										77
									
								
								zulip_bots/zulip_bots/bots/connect_four/doc.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								zulip_bots/zulip_bots/bots/connect_four/doc.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					# Connect Four Bot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Connect Four bot is a Zulip bot that will allow users
 | 
				
			||||||
 | 
					to play a game of Connect Four against either another user,
 | 
				
			||||||
 | 
					or the computer. All games are run within private messages
 | 
				
			||||||
 | 
					sent between the user(s) and the bot.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Starting a new game with another user requires a simple command,
 | 
				
			||||||
 | 
					and the desired opponent's zulip-related email adress:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					@<bot-name> start game with user@example.com
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Starting a game with the computer is even simpler:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					@<bot-name> start game with computer
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**See Usage for a complete list of commands**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*Due to design contraints, the Connect Four Bot
 | 
				
			||||||
 | 
					can only run a single game at a time*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Connect Four Bot does not require a config file or API key.
 | 
				
			||||||
 | 
					It can be used without setup.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*All commands should be prefaced with* ```@<bot-name>```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. ```help``` : provides the user with relevant
 | 
				
			||||||
 | 
					commands for first time users.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. ```status``` : due to design contraints, the
 | 
				
			||||||
 | 
					bot can only run a single game at a time. This command allows
 | 
				
			||||||
 | 
					the user to see the current status of the bot, including
 | 
				
			||||||
 | 
					whether or not the bot is running a game, if the bot is waiting
 | 
				
			||||||
 | 
					for a player to accept an invitation to play, as well as who
 | 
				
			||||||
 | 
					is currently using the bot.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. ```start game with user@example.com``` : provided
 | 
				
			||||||
 | 
					that the bot is not running a game, this command can be used to
 | 
				
			||||||
 | 
					invite another player to play a game of Connect Four with the user.
 | 
				
			||||||
 | 
					Note that the user must be specified with their email adress, not
 | 
				
			||||||
 | 
					their username.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4. ```start game with computer``` : provided that the bot is not
 | 
				
			||||||
 | 
					running a game, this command will begin a single player game
 | 
				
			||||||
 | 
					between the user and a computer player. Note that the currently
 | 
				
			||||||
 | 
					implemented computer plays randomly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5. ```accept``` : a command that can only be run by an invited
 | 
				
			||||||
 | 
					player to accept an invitation to play Connect Four against
 | 
				
			||||||
 | 
					another user.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					6. ```decline``` : a command that can only be run by an invited
 | 
				
			||||||
 | 
					player to decline an invitation to play Connect Four against
 | 
				
			||||||
 | 
					another user.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					7. ```withdraw invitation``` : a command that can only be run by the
 | 
				
			||||||
 | 
					inviter to withdraw their invitation to play. Especially
 | 
				
			||||||
 | 
					useful if a player does not respond to an invitation for a
 | 
				
			||||||
 | 
					long period of time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					8. ```move <column-number>``` : during a game, a player may run
 | 
				
			||||||
 | 
					this command on their turn to place a token in the specified
 | 
				
			||||||
 | 
					column.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					9. ```quit``` : responds with a confirmation message that asks
 | 
				
			||||||
 | 
					the user to confirm they wish to forfeit the game.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					10. ```confirm quit``` : causes the user that runs this command
 | 
				
			||||||
 | 
					to forfeit the game.
 | 
				
			||||||
							
								
								
									
										519
									
								
								zulip_bots/zulip_bots/bots/connect_four/test_connect_four.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										519
									
								
								zulip_bots/zulip_bots/bots/connect_four/test_connect_four.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,519 @@
 | 
				
			||||||
 | 
					from zulip_bots.test_lib import BotTestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from contextlib import contextmanager
 | 
				
			||||||
 | 
					from mock import MagicMock
 | 
				
			||||||
 | 
					from zulip_bots.bots.connect_four.connect_four import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestConnectFourBot(BotTestCase):
 | 
				
			||||||
 | 
					    bot_name = 'connect_four'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def make_request_message(self, content, user='foo@example.com'):
 | 
				
			||||||
 | 
					        message = dict(
 | 
				
			||||||
 | 
					            sender_email=user,
 | 
				
			||||||
 | 
					            content=content,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Function that serves similar purpose to BotTestCase.verify_dialog, but allows for multiple responses to be handled
 | 
				
			||||||
 | 
					    def verify_response(self, request, expected_response, response_number, data=None, computer_move=None, user = 'foo@example.com'):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        This function serves a similar purpose
 | 
				
			||||||
 | 
					        to BotTestCase.verify_dialog, but allows
 | 
				
			||||||
 | 
					        for multiple responses to be validated,
 | 
				
			||||||
 | 
					        and for mocking of the bot's internal data
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bot, bot_handler = self._get_handlers()
 | 
				
			||||||
 | 
					        message = self.make_request_message(request, user)
 | 
				
			||||||
 | 
					        bot_handler.reset_transcript()
 | 
				
			||||||
 | 
					        stash = ConnectFourModel.computer_move
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if data:
 | 
				
			||||||
 | 
					            bot.get_stored_data = MagicMock(return_value = data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if computer_move is not None:
 | 
				
			||||||
 | 
					            ConnectFourModel.computer_move = MagicMock(return_value = computer_move)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bot.handle_message(message, bot_handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        responses = [
 | 
				
			||||||
 | 
					            message
 | 
				
			||||||
 | 
					            for (method, message)
 | 
				
			||||||
 | 
					            in bot_handler.transcript
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        first_response = responses[response_number]
 | 
				
			||||||
 | 
					        self.assertEqual(expected_response, first_response['content'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ConnectFourModel.computer_move = stash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def help_message(self):
 | 
				
			||||||
 | 
					        return '**Connect Four Bot Help:**\n' + \
 | 
				
			||||||
 | 
					            '*Preface all commands with @bot-name*\n\n' + \
 | 
				
			||||||
 | 
					            '* To see the current status of the game, type\n' + \
 | 
				
			||||||
 | 
					            '```status```\n' + \
 | 
				
			||||||
 | 
					            '* To start a game against the computer, type\n' + \
 | 
				
			||||||
 | 
					            '```start game with computer```\n' + \
 | 
				
			||||||
 | 
					            '* To start a game against another player, type\n' + \
 | 
				
			||||||
 | 
					            '```start game with user@example.com```\n' + \
 | 
				
			||||||
 | 
					            '* To make your move during a game, type\n' + \
 | 
				
			||||||
 | 
					            '```move <column-number>```\n' + \
 | 
				
			||||||
 | 
					            '* To quit a game at any time, type\n' + \
 | 
				
			||||||
 | 
					            '```quit```\n' + \
 | 
				
			||||||
 | 
					            '* To withdraw an invitation, type\n' + \
 | 
				
			||||||
 | 
					            '```cancel game```'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def no_game_status(self):
 | 
				
			||||||
 | 
					        return '**Connect Four Game Status**\n' + \
 | 
				
			||||||
 | 
					            '*If you suspect users are abusing the bot, please alert the bot owner*\n\n' +\
 | 
				
			||||||
 | 
					            '**The bot is not running a game right now!**\n' +\
 | 
				
			||||||
 | 
					            'Type ```start game with user@example.com``` to start a game with another user,\n' +\
 | 
				
			||||||
 | 
					            'or type ```start game with computer``` to start a game with the computer'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def inviting_status(self):
 | 
				
			||||||
 | 
					        return '**Connect Four Game Status**\n' +\
 | 
				
			||||||
 | 
					            '*If you suspect users are abusing the bot, please alert the bot owner*\n\n' +\
 | 
				
			||||||
 | 
					            'foo@example.com\'s invitation to play foo2@example.com' +\
 | 
				
			||||||
 | 
					            ' is still pending. Wait for the game to finish to play a game.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def one_player_status(self):
 | 
				
			||||||
 | 
					        return '**Connect Four Game Status**\n' +\
 | 
				
			||||||
 | 
					            '*If you suspect users are abusing the bot, please alert the bot owner*\n\n' +\
 | 
				
			||||||
 | 
					            'The bot is currently running a single player game for foo@example.com.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def two_player_status(self):
 | 
				
			||||||
 | 
					        return '**Connect Four Game Status**\n' +\
 | 
				
			||||||
 | 
					            '*If you suspect users are abusing the bot, please alert the bot owner*\n\n' +\
 | 
				
			||||||
 | 
					            'The bot is currently running a two player game ' +\
 | 
				
			||||||
 | 
					            'between foo@example.com and foo2@example.com.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    blank_board = [
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    almost_win_board = [
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [1, -1, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [1, -1, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					        [1, -1, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    almost_draw_board = [
 | 
				
			||||||
 | 
					        [1, -1, 1, -1, 1, -1, 0],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, -1],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, -1],
 | 
				
			||||||
 | 
					        [0, 0, 0, 0, 0, 0, 1]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    start_two_player_data = {'state': 'playing', 'game_type': 'two_player', 'board': blank_board, 'users': ['foo@example.com', 'foo2@example.com'], 'turn': 0}
 | 
				
			||||||
 | 
					    start_one_player_data = {'state': 'playing', 'game_type': 'one_player', 'board': blank_board, 'users': ['foo@example.com'], 'turn': 0}
 | 
				
			||||||
 | 
					    end_two_player_data = {'state': 'playing', 'game_type': 'two_player', 'board': almost_win_board, 'users': ['foo@example.com', 'foo2@example.com'], 'turn': 0}
 | 
				
			||||||
 | 
					    end_one_player_data = {'state': 'playing', 'game_type': 'one_player', 'board': almost_win_board, 'users': ['foo@example.com'], 'turn': 0}
 | 
				
			||||||
 | 
					    inviting_two_player_data = {'state': 'inviting', 'game_type': 'two_player', 'board': blank_board, 'users': ['foo@example.com', 'foo2@example.com'], 'turn': 0}
 | 
				
			||||||
 | 
					    draw_data = {'state': 'playing', 'game_type': 'one_player', 'board': almost_draw_board, 'users': ['foo@example.com', 'foo2@example.com'], 'turn': 0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_static_messages(self):
 | 
				
			||||||
 | 
					        self.verify_response('help', self.help_message(), 0)
 | 
				
			||||||
 | 
					        self.verify_response('status', self.no_game_status(), 0)
 | 
				
			||||||
 | 
					        self.verify_response('status', self.inviting_status(), 0, data=self.inviting_two_player_data)
 | 
				
			||||||
 | 
					        self.verify_response('status', self.one_player_status(), 0, data=self.start_one_player_data)
 | 
				
			||||||
 | 
					        self.verify_response('status', self.two_player_status(), 0, data=self.start_two_player_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_start_game(self):
 | 
				
			||||||
 | 
					        self.verify_response('start game with computer', '**You started a new game with the computer!**', 0)
 | 
				
			||||||
 | 
					        self.verify_response('start game with user@example.com', 'You\'ve sent an invitation to play Connect Four with user@example.com. I\'ll let you know when they respond to the invitation', 0)
 | 
				
			||||||
 | 
					        self.verify_response('start game with foo@example.com', 'You can\'t play against yourself!', 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_invitation(self):
 | 
				
			||||||
 | 
					        self.verify_response('accept', 'You accepted the invitation to play with foo@example.com', 0, data=self.inviting_two_player_data, user = 'foo2@example.com')
 | 
				
			||||||
 | 
					        self.verify_response('decline', 'You declined the invitation to play with foo@example.com', 0, data=self.inviting_two_player_data, user = 'foo2@example.com')
 | 
				
			||||||
 | 
					        self.verify_response('withdraw invitation', 'Your invitation to play foo2@example.com has been withdrawn', 0, data=self.inviting_two_player_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_move(self):
 | 
				
			||||||
 | 
					        self.verify_response('move 8', 'That\'s an invalid move. Please specify a column' +
 | 
				
			||||||
 | 
					                             ' with at least one blank space, between 1 and 7', 0, data=self.start_two_player_data)
 | 
				
			||||||
 | 
					        self.verify_response('move 1', 'You placed your token in column 1.', 0, data=self.start_two_player_data)
 | 
				
			||||||
 | 
					        self.verify_response('move 1', '**the Computer moved in column 1**.', 3, data=self.start_one_player_data, computer_move=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_game_over(self):
 | 
				
			||||||
 | 
					        self.verify_response('move 1', '**Congratulations, you win! :tada:**', 2, data=self.end_two_player_data)
 | 
				
			||||||
 | 
					        self.verify_response('move 3', 'Sorry, but the Computer won :cry:', 5, data=self.end_one_player_data, computer_move=1)
 | 
				
			||||||
 | 
					        self.verify_response('move 7', '**It\'s a draw!**', 2, data = self.draw_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_quit(self):
 | 
				
			||||||
 | 
					        self.verify_response('quit', 'Are you sure you want to quit? You will forfeit the game!\n'
 | 
				
			||||||
 | 
					                             'Type ```confirm quit``` to forfeit.', 0, data=self.start_two_player_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_confirm_quit(self):
 | 
				
			||||||
 | 
					        self.verify_response('confirm quit', '**You have forfeit the game**\nSorry, but you lost :cry:', 0, data=self.start_two_player_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_privilege_check(self):
 | 
				
			||||||
 | 
					        self.verify_response('move 4', 'Sorry, but you can\'t run the command ```move 4```', 0, data=self.inviting_two_player_data)
 | 
				
			||||||
 | 
					        self.verify_response('start game with computer', 'Sorry, but other users are already using the bot.'
 | 
				
			||||||
 | 
					                             'Type ```status``` to see the current status of the bot.', 0, data=self.inviting_two_player_data, user = 'foo3@example.com')
 | 
				
			||||||
 | 
					        self.verify_response('quit', 'Sorry, but you can\'t run the command ```quit```', 0)
 | 
				
			||||||
 | 
					        self.verify_response('accept', 'Sorry, but you can\'t run the command ```accept```', 0, data=self.end_two_player_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_connect_four_logic(self):
 | 
				
			||||||
 | 
					        def confirmAvailableMoves(good_moves, bad_moves, board):
 | 
				
			||||||
 | 
					            connectFourModel.update_board(board)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for move in good_moves:
 | 
				
			||||||
 | 
					                self.assertTrue(connectFourModel.validate_move(move))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for move in bad_moves:
 | 
				
			||||||
 | 
					                self.assertFalse(connectFourModel.validate_move(move))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def confirmMove(column_number, token_number, initial_board, final_board):
 | 
				
			||||||
 | 
					            connectFourModel.update_board(initial_board)
 | 
				
			||||||
 | 
					            test_board = connectFourModel.make_move(column_number, token_number)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertEqual(test_board, final_board)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def confirmGameOver(board, result):
 | 
				
			||||||
 | 
					            connectFourModel.update_board(board)
 | 
				
			||||||
 | 
					            game_over = connectFourModel.determine_game_over('first_player', 'second_player')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertEqual(game_over, result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def confirmWinStates(array):
 | 
				
			||||||
 | 
					            for board in array[0]:
 | 
				
			||||||
 | 
					                confirmGameOver(board, 'first_player')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for board in array[1]:
 | 
				
			||||||
 | 
					                confirmGameOver(board, 'second_player')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connectFourModel = ConnectFourModel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Basic Board setups
 | 
				
			||||||
 | 
					        blank_board = [
 | 
				
			||||||
 | 
					            [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					            [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					            [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					            [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					            [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					            [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        full_board = [
 | 
				
			||||||
 | 
					            [1, 1, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 1, 1, 1, 1]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        single_column_board = [
 | 
				
			||||||
 | 
					            [1, 1, 1, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					            [1, 1, 1, 0, 1, 1, 1]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        diagonal_board = [
 | 
				
			||||||
 | 
					            [0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					            [0, 0, 0, 0, 0, 1, 1],
 | 
				
			||||||
 | 
					            [0, 0, 0, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					            [0, 0, 0, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					            [0, 0, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					            [0, 1, 1, 1, 1, 1, 1]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Winning Board Setups
 | 
				
			||||||
 | 
					        # Each array if consists of two arrays:
 | 
				
			||||||
 | 
					        # The first stores win states for '1'
 | 
				
			||||||
 | 
					        # The second stores win state for '-1'
 | 
				
			||||||
 | 
					        # Note these are not necessarily valid board states
 | 
				
			||||||
 | 
					        # for simplicity (random -1 and 1s could be added)
 | 
				
			||||||
 | 
					        horizontal_win_boards = [
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [1, 1, 1, 1, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 1, 1, 1, 1, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [-1, -1, -1, -1, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, -1, -1, -1, -1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, -1, -1, -1, -1, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vertical_win_boards = [
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [1, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [1, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [1, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [1, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [-1, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [-1, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [-1, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [-1, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, -1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, -1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, -1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, -1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        major_diagonal_win_boards = [
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                [[1, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 1, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 1, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 1, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 1, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 1]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 1, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 1, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 1, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                [[-1, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, -1, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, -1, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, -1, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, -1, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, -1]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, -1, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, -1, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, -1, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        minor_diagonal_win_boards = [
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 1, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 1, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [1, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 1, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 1, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 1, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 1, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 1, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, -1, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, -1, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [-1, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, -1],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, -1, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, -1, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, -1, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, -1, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, -1, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, -1, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                 [0, 0, 0, 0, 0, 0, 0]]
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test Move Validation Logic
 | 
				
			||||||
 | 
					        confirmAvailableMoves([0, 1, 2, 3, 4, 5, 6], [-1, 7], blank_board)
 | 
				
			||||||
 | 
					        confirmAvailableMoves([3], [0, 1, 2, 4, 5, 6], single_column_board)
 | 
				
			||||||
 | 
					        confirmAvailableMoves([0, 1, 2, 3, 4, 5], [6], diagonal_board)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test Available Move Logic
 | 
				
			||||||
 | 
					        connectFourModel.update_board(blank_board)
 | 
				
			||||||
 | 
					        self.assertEqual(connectFourModel.available_moves(), [0, 1, 2, 3, 4, 5, 6])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connectFourModel.update_board(single_column_board)
 | 
				
			||||||
 | 
					        self.assertEqual(connectFourModel.available_moves(), [3])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connectFourModel.update_board(full_board)
 | 
				
			||||||
 | 
					        self.assertEqual(connectFourModel.available_moves(), [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test Move Logic
 | 
				
			||||||
 | 
					        confirmMove(0, 1, blank_board,
 | 
				
			||||||
 | 
					                    [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [1, 0, 0, 0, 0, 0, 0]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        confirmMove(0, -1, blank_board,
 | 
				
			||||||
 | 
					                    [[0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 0, 0],
 | 
				
			||||||
 | 
					                     [-1, 0, 0, 0, 0, 0, 0]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        confirmMove(0, 1, diagonal_board,
 | 
				
			||||||
 | 
					                    [[0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [1, 1, 1, 1, 1, 1, 1]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        confirmMove(1, 1, diagonal_board,
 | 
				
			||||||
 | 
					                    [[0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 1, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 1, 1, 1, 1, 1, 1]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        confirmMove(2, 1, diagonal_board,
 | 
				
			||||||
 | 
					                    [[0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 1, 1, 1, 1, 1, 1]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        confirmMove(3, 1, diagonal_board,
 | 
				
			||||||
 | 
					                    [[0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 1, 1, 1, 1, 1, 1]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        confirmMove(4, 1, diagonal_board,
 | 
				
			||||||
 | 
					                    [[0, 0, 0, 0, 0, 0, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 1, 1, 1, 1, 1, 1]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        confirmMove(5, 1, diagonal_board,
 | 
				
			||||||
 | 
					                    [[0, 0, 0, 0, 0, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 0, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 0, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 0, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 0, 1, 1, 1, 1, 1],
 | 
				
			||||||
 | 
					                     [0, 1, 1, 1, 1, 1, 1]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test Game Over Logic:
 | 
				
			||||||
 | 
					        confirmGameOver(blank_board, False)
 | 
				
			||||||
 | 
					        confirmGameOver(full_board, 'draw')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test Win States:
 | 
				
			||||||
 | 
					        confirmWinStates(horizontal_win_boards)
 | 
				
			||||||
 | 
					        confirmWinStates(vertical_win_boards)
 | 
				
			||||||
 | 
					        confirmWinStates(major_diagonal_win_boards)
 | 
				
			||||||
 | 
					        confirmWinStates(minor_diagonal_win_boards)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test Computer Move:
 | 
				
			||||||
 | 
					        connectFourModel.update_board(blank_board)
 | 
				
			||||||
 | 
					        self.assertTrue(connectFourModel.computer_move() in [0, 1, 2, 3, 4, 5, 6])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connectFourModel.update_board(single_column_board)
 | 
				
			||||||
 | 
					        self.assertEqual(connectFourModel.computer_move(), 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connectFourModel.update_board(diagonal_board)
 | 
				
			||||||
 | 
					        self.assertTrue(connectFourModel.computer_move() in [0, 1, 2, 3, 4, 5])
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue