Game Adapter Class: Allow superusers to moderate bots.
This commit is contained in:
parent
2c8b369d14
commit
b9905e5fc8
|
@ -0,0 +1,2 @@
|
|||
[connect_four]
|
||||
superusers = ["user@example.com"]
|
|
@ -3,7 +3,7 @@ from zulip_bots.bots.connect_four.controller import ConnectFourModel
|
|||
|
||||
class ConnectFourMessageHandler(object):
|
||||
tokens = [':blue_circle:', ':red_circle:']
|
||||
|
||||
|
||||
def parse_board(self, board):
|
||||
# Header for the top of the board
|
||||
board_str = ':one: :two: :three: :four: :five: :six: :seven:'
|
||||
|
@ -19,7 +19,7 @@ class ConnectFourMessageHandler(object):
|
|||
board_str += ':red_circle: '
|
||||
|
||||
return board_str
|
||||
|
||||
|
||||
def get_player_color(self, turn):
|
||||
return self.tokens[turn]
|
||||
|
||||
|
@ -41,16 +41,16 @@ class ConnectFourBotHandler(GameAdapter):
|
|||
or the comptuer in a game of Connect
|
||||
Four
|
||||
'''
|
||||
|
||||
|
||||
def __init__(self):
|
||||
game_name = 'Connect Four'
|
||||
bot_name = 'connect_four'
|
||||
move_help_message = '* To make your move during a game, type\n' + \
|
||||
'```move <column-number>```'
|
||||
move_help_message = '* To make your move during a game, type\n' \
|
||||
'```move <column-number>```'
|
||||
move_regex = 'move (\d)$'
|
||||
model = ConnectFourModel
|
||||
gameMessageHandler = ConnectFourMessageHandler
|
||||
|
||||
|
||||
super(ConnectFourBotHandler, self).__init__(game_name, bot_name, move_help_message, move_regex, model, gameMessageHandler)
|
||||
|
||||
handler_class = ConnectFourBotHandler
|
||||
|
|
|
@ -17,10 +17,10 @@ class ConnectFourModel(object):
|
|||
[0, 0, 0, 0, 0, 0, 0]]
|
||||
|
||||
current_board = blank_board
|
||||
|
||||
|
||||
def parse_move(self, move):
|
||||
return int(move) - 1
|
||||
|
||||
|
||||
def update_board(self, board):
|
||||
self.current_board = deepcopy(board)
|
||||
|
||||
|
|
|
@ -25,8 +25,12 @@ 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.
|
||||
To set moderators for the bot, modify the connect_four.conf
|
||||
file as shown:
|
||||
|
||||
superusers = ["user@example.com", "user@example2.com", ...]
|
||||
|
||||
Moderators can run ```force reset``` in case any user abuse the bot
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -61,7 +65,7 @@ another user.
|
|||
player to decline an invitation to play Connect Four against
|
||||
another user.
|
||||
|
||||
7. ```withdraw invitation``` : a command that can only be run by the
|
||||
7. ```cancel game``` : 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.
|
||||
|
@ -75,3 +79,7 @@ the user to confirm they wish to forfeit the game.
|
|||
|
||||
10. ```confirm quit``` : causes the user that runs this command
|
||||
to forfeit the game.
|
||||
|
||||
11. ```force reset``` : a command that can only be run by the bot
|
||||
owner and moderators (see 'Usage' for specifying). Destroys any
|
||||
game currently being run if users are abusing the bot
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
# @TODO: place bot owner name in config file, allow bot owner to run special commands
|
||||
|
||||
import re
|
||||
import json
|
||||
from copy import deepcopy
|
||||
|
||||
# @TODO: allow superusers
|
||||
class InputVerification(object):
|
||||
def __init__(self, move_regex):
|
||||
def __init__(self, move_regex, superusers):
|
||||
self.move_regex = move_regex
|
||||
self.verified_commands = {
|
||||
'waiting': ['start game with computer', 'start game with \w+@\w+\.\w+'],
|
||||
|
@ -13,17 +11,16 @@ class InputVerification(object):
|
|||
'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 = []
|
||||
'withdraw invitation', 'accept', 'decline', self.move_regex, 'quit', 'confirm quit', 'force reset']
|
||||
self.superusers = superusers
|
||||
|
||||
|
||||
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'] = [['quit', 'confirm quit'], ['quit', 'confirm quit']]
|
||||
self.verified_commands['playing'][turn].append(self.move_regex)
|
||||
|
||||
def reset_commands(self):
|
||||
|
@ -50,6 +47,9 @@ class InputVerification(object):
|
|||
|
||||
return self.regex_match_in_array(command_array, command)
|
||||
|
||||
def verify_superuser(self, user):
|
||||
return user in self.superusers
|
||||
|
||||
class StateManager(object):
|
||||
def __init__(self, main_bot_handler):
|
||||
self.users = None
|
||||
|
@ -331,11 +331,9 @@ class GameAdapter(object):
|
|||
self.move_help_message = move_help_message
|
||||
self.model = model
|
||||
self.gameMessageHandler = gameMessageHandler()
|
||||
self.inputVerification = InputVerification(move_regex)
|
||||
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):
|
||||
|
@ -343,13 +341,17 @@ class GameAdapter(object):
|
|||
|
||||
if 'users' in self.data:
|
||||
self.inputVerification.verified_users = self.data['users']
|
||||
else:
|
||||
self.inputVerification.verified_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'], )
|
||||
self.gameHandler = GameHandler(self, self.data['game_type'], self.model(),
|
||||
board = self.data['board'], turn = self.data['turn'])
|
||||
self.inputVerification.update_commands(self.data['turn'])
|
||||
|
||||
def put_stored_data(self):
|
||||
self.data = {}
|
||||
|
@ -470,11 +472,26 @@ class GameAdapter(object):
|
|||
'''
|
||||
|
||||
def initialize(self, bot_handler):
|
||||
self.config_info = bot_handler.get_config_info('connect_four')
|
||||
if self.config_info:
|
||||
self.inputVerification.superusers = json.loads(self.config_info['superusers'])
|
||||
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 force_reset(self, sender):
|
||||
for user in self.inputVerification.verified_users:
|
||||
self.send_message(user, 'A bot moderator determined you were abusing the bot, and quit your game.'
|
||||
' Please make sure you finish all your games in a timely fashion.')
|
||||
|
||||
self.send_message(sender, 'The game has been force reset')
|
||||
|
||||
self.data = data = {'state': 'waiting'}
|
||||
self.update_data()
|
||||
self.put_stored_data()
|
||||
|
||||
def handle_message(self, message, bot_handler):
|
||||
self.bot_handler = bot_handler
|
||||
|
||||
|
@ -489,7 +506,10 @@ class GameAdapter(object):
|
|||
'Type ```help``` to see a full list of commands.')
|
||||
return
|
||||
|
||||
# Messages that can be sent regardless of state or user
|
||||
elif self.inputVerification.verify_superuser(sender) and content.lower() == 'force reset':
|
||||
self.force_reset(sender)
|
||||
return
|
||||
|
||||
elif content.lower() == 'help' or content == '':
|
||||
self.send_message(sender, self.help_message())
|
||||
return
|
||||
|
@ -524,4 +544,4 @@ class GameAdapter(object):
|
|||
self.gameHandler.handle_message(content, sender)
|
||||
|
||||
self.send_message_arrays(sender)
|
||||
self.put_stored_data()
|
||||
self.put_stored_data()
|
||||
|
|
|
@ -137,7 +137,7 @@ class TestConnectFourBot(BotTestCase):
|
|||
|
||||
def test_move(self):
|
||||
self.verify_response('move 8', 'That\'s an invalid move. Please specify a column '
|
||||
'between 1 and 7 with at least one open spot.', 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', '**the Computer moved in column 1**.', 3, data=self.start_one_player_data, computer_move=0)
|
||||
|
||||
|
@ -149,16 +149,19 @@ class TestConnectFourBot(BotTestCase):
|
|||
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_force_reset(self):
|
||||
with self.mock_config_info({'superusers': '["foo@example.com"]'}):
|
||||
self.verify_response('force reset', 'The game has been force reset', 1, data=self.start_one_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)
|
||||
self.verify_response('force reset', 'Sorry, but you can\'t run the command ```force reset```', 0)
|
||||
|
||||
def test_connect_four_logic(self):
|
||||
def confirmAvailableMoves(good_moves, bad_moves, board):
|
||||
|
|
Loading…
Reference in a new issue