game_handler: Support single player games and enforce 'rules' command.

Update tests for test_connect_four.py and test_game_handler_bot.py.
This commit is contained in:
Tarun Kumar 2018-03-03 02:53:03 +05:30 committed by showell
parent 0f45f28696
commit ee611d935e
3 changed files with 104 additions and 20 deletions

View file

@ -66,6 +66,8 @@ class TestConnectFourBot(BotTestCase):
`leaderboard` `leaderboard`
* To withdraw an invitation, type * To withdraw an invitation, type
`cancel game` `cancel game`
* To see rules of this game, type
`rules`
* To make your move during a game, type * To make your move during a game, type
```move <column-number>``` or ```<column-number>```''' ```move <column-number>``` or ```<column-number>```'''

View file

@ -123,6 +123,8 @@ class TestGameHandlerBot(BotTestCase):
`leaderboard` `leaderboard`
* To withdraw an invitation, type * To withdraw an invitation, type
`cancel game` `cancel game`
* To see rules of this game, type
`rules`
* To make your move during a game, type * To make your move during a game, type
```move <column-number>```''' ```move <column-number>```'''
@ -379,7 +381,7 @@ class TestGameHandlerBot(BotTestCase):
def test_invalid_move_message(self) -> None: def test_invalid_move_message(self) -> None:
bot = self.setup_game() bot = self.setup_game()
self.verify_response('move 9', 'Invalid Move.', 0, self.verify_response('move 9', 'Invalid Move.', 0,
bot=bot, stream='test', subject='test game', max_messages=1) bot=bot, stream='test', subject='test game', max_messages=2)
def test_get_game_id_by_email(self) -> None: def test_get_game_id_by_email(self) -> None:
bot = self.setup_game() bot = self.setup_game()

View file

@ -39,6 +39,7 @@ class GameAdapter(object):
move_regex: str, move_regex: str,
model: Any, model: Any,
gameMessageHandler: Any, gameMessageHandler: Any,
rules: str,
max_players: int=2, max_players: int=2,
min_players: int=2, min_players: int=2,
supports_computer: bool=False supports_computer: bool=False
@ -50,6 +51,7 @@ class GameAdapter(object):
self.model = model self.model = model
self.max_players = max_players self.max_players = max_players
self.min_players = min_players self.min_players = min_players
self.is_single_player = self.min_players == self.max_players == 1
self.supports_computer = supports_computer self.supports_computer = supports_computer
self.gameMessageHandler = gameMessageHandler() self.gameMessageHandler = gameMessageHandler()
self.invites = {} # type: Dict[str, Dict[str, str]] self.invites = {} # type: Dict[str, Dict[str, str]]
@ -57,6 +59,7 @@ class GameAdapter(object):
self.user_cache = {} # type: Dict[str, Dict[str, Any]] self.user_cache = {} # type: Dict[str, Dict[str, Any]]
self.pending_subject_changes = [] # type: List[str] self.pending_subject_changes = [] # type: List[str]
self.stream = 'games' self.stream = 'games'
self.rules = rules
# Values are [won, lost, drawn, total] new values can be added, but MUST be added to the end of the list. # Values are [won, lost, drawn, total] new values can be added, but MUST be added to the end of the list.
def add_user_statistics(self, user: str, values: Dict[str, int]) -> None: def add_user_statistics(self, user: str, values: Dict[str, int]) -> None:
@ -90,8 +93,41 @@ class GameAdapter(object):
`leaderboard` `leaderboard`
* To withdraw an invitation, type * To withdraw an invitation, type
`cancel game` `cancel game`
* To see rules of this game, type
`rules`
{}'''.format(self.game_name, self.get_bot_username(), self.play_with_computer_help(), self.move_help_message) {}'''.format(self.game_name, self.get_bot_username(), self.play_with_computer_help(), self.move_help_message)
def help_message_single_player(self) -> str:
return '''** {} Bot Help:**
*Preface all commands with @**{}***
* To start a game in a stream, type
`start game`
* To quit a game at any time, type
`quit`
* To see rules of this game, type
`rules`
{}'''.format(self.game_name, self.get_bot_username(), self.move_help_message)
def get_commands(self) -> Dict[str, str]:
action = self.help_message_single_player()
return {
'accept': action,
'decline': action,
'register': action,
'draw': action,
'forfeit': action,
'leaderboard': action,
'join': action,
}
def manage_command(self, command: str, message: Dict[str, Any]) -> int:
commands = self.get_commands()
if command not in commands:
return 1
action = commands[command]
self.send_reply(message, action)
return 0
def already_in_game_message(self) -> str: def already_in_game_message(self) -> str:
return 'You are already in a game. Type `quit` to leave.' return 'You are already in a game. Type `quit` to leave.'
@ -165,14 +201,29 @@ class GameAdapter(object):
self.add_user_to_cache(message) self.add_user_to_cache(message)
logging.info('Added {} to user cache'.format(sender)) logging.info('Added {} to user cache'.format(sender))
if self.is_single_player:
if content.lower().startswith('start game with') or content.lower().startswith('play game'):
self.send_reply(message, self.help_message_single_player())
return
else:
val = self.manage_command(content.lower(), message)
if val == 0:
return
if content.lower() == 'help' or content == '': if content.lower() == 'help' or content == '':
if self.is_single_player:
self.send_reply(message, self.help_message_single_player())
else:
self.send_reply(message, self.help_message()) self.send_reply(message, self.help_message())
return return
elif content.lower() == 'rules':
self.send_reply(message, self.rules)
elif content.lower().startswith('start game with '): elif content.lower().startswith('start game with '):
self.command_start_game_with(message, sender, content) self.command_start_game_with(message, sender, content)
elif content.lower().startswith('start game'): elif content.lower() == 'start game':
self.command_start_game(message, sender, content) self.command_start_game(message, sender, content)
elif content.lower().startswith('play game'): elif content.lower().startswith('play game'):
@ -203,6 +254,9 @@ class GameAdapter(object):
elif self.move_regex.match(content) is not None or content.lower() == 'draw' or content.lower() == 'forfeit': elif self.move_regex.match(content) is not None or content.lower() == 'draw' or content.lower() == 'forfeit':
self.send_reply( self.send_reply(
message, 'You are not in a game at the moment. Type `help` for help.') message, 'You are not in a game at the moment. Type `help` for help.')
else:
if self.is_single_player:
self.send_reply(message, self.help_message_single_player())
else: else:
self.send_reply(message, self.help_message()) self.send_reply(message, self.help_message())
except Exception as e: except Exception as e:
@ -225,6 +279,10 @@ class GameAdapter(object):
def command_start_game(self, message: Dict[str, Any], sender: str, content: str) -> None: def command_start_game(self, message: Dict[str, Any], sender: str, content: str) -> None:
if message['type'] == 'private': if message['type'] == 'private':
if self.is_single_player:
self.send_reply(message, 'You are not allowed to play games in private messages.')
return
else:
self.send_reply( self.send_reply(
message, 'If you are starting a game in private messages, you must invite players. Type `help` for commands.') message, 'If you are starting a game in private messages, you must invite players. Type `help` for commands.')
if not self.is_user_not_player(sender, message): if not self.is_user_not_player(sender, message):
@ -232,6 +290,8 @@ class GameAdapter(object):
message, self.already_in_game_message()) message, self.already_in_game_message())
return return
self.create_game_lobby(message) self.create_game_lobby(message)
if self.is_single_player:
self.command_play(message, sender, content)
def command_accept(self, message: Dict[str, Any], sender: str, content: str) -> None: def command_accept(self, message: Dict[str, Any], sender: str, content: str) -> None:
if not self.is_user_not_player(sender, message): if not self.is_user_not_player(sender, message):
@ -284,6 +344,12 @@ class GameAdapter(object):
self.game_name, self.game_name,
self.get_bot_username()) self.get_bot_username())
) )
if self.is_single_player:
self.broadcast(game_id, '**{}** is now going to play {}!'.format(
self.get_username_by_email(message['sender_email']),
self.game_name)
)
if self.email in users: if self.email in users:
self.broadcast(game_id, 'Wait... That\'s me!', self.broadcast(game_id, 'Wait... That\'s me!',
include_private=True) include_private=True)
@ -312,6 +378,9 @@ class GameAdapter(object):
def command_quit(self, message: Dict[str, Any], sender: str, content: str) -> None: def command_quit(self, message: Dict[str, Any], sender: str, content: str) -> None:
game_id = self.get_game_id_by_email(sender) game_id = self.get_game_id_by_email(sender)
if message['type'] == 'private' and self.is_single_player:
self.send_reply(message, 'You are not allowed to play games in private messages.')
return
if game_id is '': if game_id is '':
self.send_reply( self.send_reply(
message, 'You are not in a game. Type `help` for all commands.') message, 'You are not in a game. Type `help` for all commands.')
@ -423,6 +492,7 @@ class GameAdapter(object):
> {}/{} players'''.format(game_id, self.get_host(game_id), self.game_name, self.get_number_of_players(game_id), self.max_players) > {}/{} players'''.format(game_id, self.get_host(game_id), self.game_name, self.get_number_of_players(game_id), self.max_players)
if game_id in self.instances.keys(): if game_id in self.instances.keys():
instance = self.instances[game_id] instance = self.instances[game_id]
if not self.is_single_player:
object += '\n> **[Join Game](/#narrow/stream/{}/topic/{})**'.format( object += '\n> **[Join Game](/#narrow/stream/{}/topic/{})**'.format(
instance.stream, instance.subject) instance.stream, instance.subject)
return object return object
@ -496,6 +566,9 @@ class GameAdapter(object):
game_id = self.is_user_in_game(message['sender_email']) game_id = self.is_user_in_game(message['sender_email'])
game = self.get_game_info(game_id) game = self.get_game_info(game_id)
if message['type'] == 'private': if message['type'] == 'private':
if self.is_single_player:
self.send_reply(message, self.help_message_single_player())
return
self.send_reply(message, 'Join your game using the link below!\n\n{}'.format( self.send_reply(message, 'Join your game using the link below!\n\n{}'.format(
self.get_formatted_game_object(game_id))) self.get_formatted_game_object(game_id)))
return return
@ -751,6 +824,9 @@ class GameInstance(object):
return return
if self.is_turn_of(player_email): if self.is_turn_of(player_email):
self.handle_current_player_command(content) self.handle_current_player_command(content)
else:
if self.gameAdapter.is_single_player:
self.broadcast('It\'s your turn')
else: else:
user_turn_avatar = "!avatar({})".format(self.players[self.turn]) user_turn_avatar = "!avatar({})".format(self.players[self.turn])
self.broadcast('{} It\'s **{}**\'s ({}) turn.'.format( self.broadcast('{} It\'s **{}**\'s ({}) turn.'.format(
@ -784,6 +860,7 @@ class GameInstance(object):
return return
except BadMoveException as e: except BadMoveException as e:
self.broadcast(e.message) self.broadcast(e.message)
self.broadcast(self.parse_current_board())
return return
if not is_computer: if not is_computer:
self.current_messages.append(self.gameAdapter.gameMessageHandler.alert_move_message( self.current_messages.append(self.gameAdapter.gameMessageHandler.alert_move_message(
@ -829,6 +906,9 @@ class GameInstance(object):
self.turn += 1 self.turn += 1
if self.turn >= len(self.players): if self.turn >= len(self.players):
self.turn = 0 self.turn = 0
if self.gameAdapter.is_single_player:
self.current_messages.append('It\'s your turn.')
else:
user_turn_avatar = "!avatar({})".format(self.players[self.turn]) user_turn_avatar = "!avatar({})".format(self.players[self.turn])
self.current_messages.append('{} It\'s **{}**\'s ({}) turn.'.format( self.current_messages.append('{} It\'s **{}**\'s ({}) turn.'.format(
user_turn_avatar, user_turn_avatar,