interactive bots: Extracted GameAdapter Class.
This commit is contained in:
parent
35829218b7
commit
2c8b369d14
|
@ -1,535 +1,56 @@
|
||||||
# @TODO: place bot owner name in config file, allow bot owner to run special commands
|
from zulip_bots.bots.connect_four.game_adapter import GameAdapter
|
||||||
|
|
||||||
import re
|
|
||||||
from copy import deepcopy
|
|
||||||
from zulip_bots.bots.connect_four.controller import ConnectFourModel
|
from zulip_bots.bots.connect_four.controller import ConnectFourModel
|
||||||
|
|
||||||
class InputVerification(object):
|
class ConnectFourMessageHandler(object):
|
||||||
verified_users = []
|
tokens = [':blue_circle:', ':red_circle:']
|
||||||
|
|
||||||
all_valid_commands = ['help', 'status', 'start game with computer', 'start game with \w+@\w+\.\w+',
|
def parse_board(self, board):
|
||||||
'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
|
# Header for the top of the board
|
||||||
board_str = ':one: :two: :three: :four: :five: :six: :seven:'
|
board_str = ':one: :two: :three: :four: :five: :six: :seven:'
|
||||||
|
|
||||||
for row in range(0, 6):
|
for row in range(0, 6):
|
||||||
board_str += '\n\n'
|
board_str += '\n\n'
|
||||||
for column in range(0, 7):
|
for column in range(0, 7):
|
||||||
if self.board[row][column] == 0:
|
if board[row][column] == 0:
|
||||||
board_str += ':heavy_large_circle: '
|
board_str += ':heavy_large_circle: '
|
||||||
elif self.board[row][column] == 1:
|
elif board[row][column] == 1:
|
||||||
board_str += ':blue_circle: '
|
board_str += ':blue_circle: '
|
||||||
elif self.board[row][column] == -1:
|
elif board[row][column] == -1:
|
||||||
board_str += ':red_circle: '
|
board_str += ':red_circle: '
|
||||||
|
|
||||||
return board_str
|
return board_str
|
||||||
|
|
||||||
|
def get_player_color(self, turn):
|
||||||
|
return self.tokens[turn]
|
||||||
|
|
||||||
def your_turn_message(self):
|
def alert_move_message(self, original_player, move_info):
|
||||||
return '**It\'s your move!**\n' +\
|
column_number = move_info
|
||||||
'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) + '**.'
|
return '**' + original_player + ' moved in column ' + str(column_number + 1) + '**.'
|
||||||
|
|
||||||
def append_game_over_messages(self, result):
|
def confirm_move_message(self, move_info):
|
||||||
if result == 'draw':
|
column_number = move_info
|
||||||
self.user_messages.append('**It\'s a draw!**')
|
return 'You placed your token in column ' + str(column_number + 1) + '.'
|
||||||
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):
|
def invalid_move_message(self):
|
||||||
player = self.main_bot_handler.inputVerification.verified_users.index(sender)
|
return 'Please specify a column between 1 and 7 with at least one open spot.'
|
||||||
# This computation will return 1 for player 0, and -1 for player 1, as is expected
|
|
||||||
return (-2) * player + 1
|
|
||||||
|
|
||||||
def toggle_turn(self):
|
class ConnectFourBotHandler(GameAdapter):
|
||||||
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
|
Bot that uses the Game Adapter class
|
||||||
or the computer in a game of Connect Four
|
to allow users to play other users
|
||||||
|
or the comptuer in a game of Connect
|
||||||
|
Four
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def get_stored_data(self):
|
def __init__(self):
|
||||||
# return self.data # Uncomment and rerun bot to reset data if users are abusing the bot
|
game_name = 'Connect Four'
|
||||||
return self.bot_handler.storage.get('connect_four')
|
bot_name = 'connect_four'
|
||||||
|
move_help_message = '* To make your move during a game, type\n' + \
|
||||||
def update_data(self):
|
'```move <column-number>```'
|
||||||
self.state = self.data['state']
|
move_regex = 'move (\d)$'
|
||||||
|
model = ConnectFourModel
|
||||||
if 'users' in self.data:
|
gameMessageHandler = ConnectFourMessageHandler
|
||||||
self.inputVerification.verified_users = self.data['users']
|
|
||||||
|
super(ConnectFourBotHandler, self).__init__(game_name, bot_name, move_help_message, move_regex, model, gameMessageHandler)
|
||||||
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
|
handler_class = ConnectFourBotHandler
|
||||||
|
|
|
@ -17,7 +17,10 @@ class ConnectFourModel(object):
|
||||||
[0, 0, 0, 0, 0, 0, 0]]
|
[0, 0, 0, 0, 0, 0, 0]]
|
||||||
|
|
||||||
current_board = blank_board
|
current_board = blank_board
|
||||||
|
|
||||||
|
def parse_move(self, move):
|
||||||
|
return int(move) - 1
|
||||||
|
|
||||||
def update_board(self, board):
|
def update_board(self, board):
|
||||||
self.current_board = deepcopy(board)
|
self.current_board = deepcopy(board)
|
||||||
|
|
||||||
|
|
527
zulip_bots/zulip_bots/bots/connect_four/game_adapter.py
Normal file
527
zulip_bots/zulip_bots/bots/connect_four/game_adapter.py
Normal file
|
@ -0,0 +1,527 @@
|
||||||
|
# @TODO: place bot owner name in config file, allow bot owner to run special commands
|
||||||
|
|
||||||
|
import re
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
# @TODO: allow superusers
|
||||||
|
class InputVerification(object):
|
||||||
|
def __init__(self, move_regex):
|
||||||
|
self.move_regex = move_regex
|
||||||
|
self.verified_commands = {
|
||||||
|
'waiting': ['start game with computer', 'start game with \w+@\w+\.\w+'],
|
||||||
|
'inviting': [['withdraw invitation'], ['accept', 'decline']],
|
||||||
|
'playing': [[move_regex, 'quit', 'confirm quit'], ['quit', 'confirm quit']]
|
||||||
|
}
|
||||||
|
self.all_valid_commands = ['help', 'status', 'start game with computer', 'start game with \w+@\w+\.\w+',
|
||||||
|
'withdraw invitation', 'accept', 'decline', self.move_regex, 'quit', 'confirm quit']
|
||||||
|
|
||||||
|
verified_users = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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(self.move_regex)
|
||||||
|
self.verified_commands['playing'][turn].append(self.move_regex)
|
||||||
|
|
||||||
|
def reset_commands(self):
|
||||||
|
self.verified_commands['playing'] = [[self.move_regex, '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.main_bot_handler.model())
|
||||||
|
|
||||||
|
self.user_messages.append('**You started a new game with the computer!**')
|
||||||
|
self.user_messages.append(self.main_bot_handler.gameMessageHandler.parse_board(self.main_bot_handler.model().blank_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.main_bot_handler.model())
|
||||||
|
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, model, board = 'blank', turn = 0):
|
||||||
|
super(GameHandler, self).__init__(main_bot_handler)
|
||||||
|
self.game_type = game_type
|
||||||
|
self.turn = turn
|
||||||
|
self.game_ended = False
|
||||||
|
self.model = model
|
||||||
|
self.board = model.blank_board if board == 'blank' else board
|
||||||
|
self.model.update_board(board)
|
||||||
|
|
||||||
|
def your_turn_message(self):
|
||||||
|
return '**It\'s your move!**\n' +\
|
||||||
|
'type ```move <column-number>``` to make your move\n\n' +\
|
||||||
|
'You are ' + self.main_bot_handler.gameMessageHandler.get_player_color(self.turn)
|
||||||
|
|
||||||
|
def wait_turn_message(self, opponent):
|
||||||
|
return 'Waiting for ' + opponent + ' to move'
|
||||||
|
|
||||||
|
def invalid_move_message(self):
|
||||||
|
return 'That\'s an invalid move. ' + self.main_bot_handler.gameMessageHandler.invalid_move_message()
|
||||||
|
|
||||||
|
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, move_info, token_number, player_one, player_two, computer_play = False):
|
||||||
|
if not self.model.validate_move(move_info):
|
||||||
|
self.user_messages.append(self.invalid_move_message())
|
||||||
|
return
|
||||||
|
|
||||||
|
self.board = self.model.make_move(move_info, token_number)
|
||||||
|
|
||||||
|
if not computer_play:
|
||||||
|
self.user_messages.append(self.main_bot_handler.gameMessageHandler.confirm_move_message(move_info))
|
||||||
|
self.user_messages.append(self.main_bot_handler.gameMessageHandler.parse_board(self.model.current_board))
|
||||||
|
|
||||||
|
self.opponent_messages.append(self.main_bot_handler.gameMessageHandler.alert_move_message(self.sender, move_info))
|
||||||
|
self.opponent_messages.append(self.main_bot_handler.gameMessageHandler.parse_board(self.model.current_board))
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.user_messages.append(self.main_bot_handler.gameMessageHandler.alert_move_message('the Computer', move_info))
|
||||||
|
self.user_messages.append(self.main_bot_handler.gameMessageHandler.parse_board(self.model.current_board))
|
||||||
|
|
||||||
|
game_over = self.model.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
|
||||||
|
move_regex = self.main_bot_handler.inputVerification.move_regex
|
||||||
|
|
||||||
|
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_regex).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 = re.compile(move_regex).search(content).group(1)
|
||||||
|
human_move = self.model.parse_move(human_move)
|
||||||
|
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.model.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
|
||||||
|
self.game_name = main_bot_handler.game_name
|
||||||
|
|
||||||
|
def confirm_new_invitation(self, opponent):
|
||||||
|
return 'You\'ve sent an invitation to play ' + self.game_name + ' 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 ' + self.game_name + '.**\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.gameMessageHandler.parse_board(self.main_bot_handler.model().blank_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 GameAdapter(object):
|
||||||
|
'''
|
||||||
|
Class that serves as a template to easily
|
||||||
|
create one and two player games
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, game_name, bot_name, move_help_message, move_regex, model, gameMessageHandler):
|
||||||
|
self.game_name = game_name
|
||||||
|
self.bot_name = bot_name
|
||||||
|
self.move_help_message = move_help_message
|
||||||
|
self.model = model
|
||||||
|
self.gameMessageHandler = gameMessageHandler()
|
||||||
|
self.inputVerification = InputVerification(move_regex)
|
||||||
|
|
||||||
|
def get_stored_data(self):
|
||||||
|
# @TODO: remove this comment when you create super users
|
||||||
|
# return self.data # Uncomment and rerun bot to reset data if users are abusing the bot
|
||||||
|
return self.bot_handler.storage.get(self.bot_name)
|
||||||
|
|
||||||
|
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'], self.model())
|
||||||
|
|
||||||
|
elif self.state == 'playing':
|
||||||
|
self.gameHandler = GameHandler(self, self.data['game_type'], self.model(), 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(self.bot_name, 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
|
||||||
|
|
||||||
|
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 = '**' + self.game_name + ' 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 '**' + self.game_name + ' 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 quit a game at any time, type\n' + \
|
||||||
|
'```quit```\n' + \
|
||||||
|
'* To withdraw an invitation, type\n' + \
|
||||||
|
'```cancel game```\n' + \
|
||||||
|
self.move_help_message
|
||||||
|
|
||||||
|
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 ''' + self.game_name + '''
|
||||||
|
|
||||||
|
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(self.bot_name):
|
||||||
|
bot_handler.storage.put(self.bot_name, 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()
|
|
@ -56,12 +56,12 @@ class TestConnectFourBot(BotTestCase):
|
||||||
'```start game with computer```\n' + \
|
'```start game with computer```\n' + \
|
||||||
'* To start a game against another player, type\n' + \
|
'* To start a game against another player, type\n' + \
|
||||||
'```start game with user@example.com```\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' + \
|
'* To quit a game at any time, type\n' + \
|
||||||
'```quit```\n' + \
|
'```quit```\n' + \
|
||||||
'* To withdraw an invitation, type\n' + \
|
'* To withdraw an invitation, type\n' + \
|
||||||
'```cancel game```'
|
'```cancel game```\n' +\
|
||||||
|
'* To make your move during a game, type\n' + \
|
||||||
|
'```move <column-number>```'
|
||||||
|
|
||||||
def no_game_status(self):
|
def no_game_status(self):
|
||||||
return '**Connect Four Game Status**\n' + \
|
return '**Connect Four Game Status**\n' + \
|
||||||
|
@ -136,8 +136,8 @@ class TestConnectFourBot(BotTestCase):
|
||||||
self.verify_response('withdraw invitation', 'Your invitation to play foo2@example.com has been withdrawn', 0, data=self.inviting_two_player_data)
|
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):
|
def test_move(self):
|
||||||
self.verify_response('move 8', 'That\'s an invalid move. Please specify a column' +
|
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)
|
'between 1 and 7 with at least one open spot.', 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', '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)
|
self.verify_response('move 1', '**the Computer moved in column 1**.', 3, data=self.start_one_player_data, computer_move=0)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue