Port merels to game_handler.
Fixes
MODIFY
zulip_bots/zulip_bots/bots/merels/libraries/database.py
zulip_bots/zulip_bots/bots/merels/libraries/database.py
@@ -14,7 +14,7 @@ from .constants import EMPTY_BOARD
class MerelsStorage():
- def __init__(self, storage):
+ def __init__(self, topic_name, storage):
"""Instantiate storage field.
The current database has this form:
@@ -30,29 +30,6 @@ class MerelsStorage():
"""
self.storage = storage
- def create_new_game(self, topic_name):
- """ Creates a new merels game if it doesn't exists yet.
-
- :param topic_name: Name of the topic
- :return: True, if the game is successfully created, False if otherwise.
- """
-
- parameters = ("X", 0, 0, EMPTY_BOARD, "", 0)
-
- # Checks whether the game exists yet
- # If it exists
-
- try:
- if not self.storage.contains(topic_name) or self.storage.get(
- topic_name) == "":
- self.storage.put(topic_name, json.dumps(parameters))
- return True
- else:
- return False
- except KeyError:
- self.storage.put(topic_name, json.dumps(parameters))
- return True
-
def update_game(self, topic_name, turn, x_taken, o_taken, board, hill_uid,
take_mode):
""" Updates the current status of the game to the database.

MODIFY
zulip_bots/zulip_bots/bots/merels/libraries/game.py
zulip_bots/zulip_bots/bots/merels/libraries/game.py
@@ -7,13 +7,13 @@ freely import another modules.
import re
+from zulip_bots.game_handler import BadMoveException
+
from . import database
from . import mechanics
-
COMMAND_PATTERN = re.compile(
"^(\\w*).*(\\d,\\d).*(\\d,\\d)|^(\\w+).*(\\d,\\d)")
-
def getInfo():
""" Gets the info on starting the game
@@ -22,7 +22,6 @@ def getInfo():
return "To start a game, mention me and add `create`. A game will start " \
"in that topic. "
-
def getHelp():
""" Gets the help message
@@ -30,8 +29,6 @@ def getHelp():
"""
return """Commands:
-create: Create a new game if it doesn't exist
-reset: Reset a current game
put (v,h): Put a man into the grid in phase 1
move (v,h) -> (v,h): Moves a man from one point to -> another point
take (v,h): Take an opponent's man from the grid in phase 2/3
@@ -39,76 +36,65 @@ take (v,h): Take an opponent's man from the grid in phase 2/3
v: vertical position of grid
h: horizontal position of grid"""
-
def unknown_command():
"""Returns an unknown command info
:return: A string containing info about available commands
"""
- return "Unknown command. Available commands: create, reset, help, " \
+ message = "Unknown command. Available commands: " \
"put (v,h), take (v,h), move (v,h) -> (v,h)"
+ raise BadMoveException(message)
-
+# def beat(message, topic_name, merels_storage):
def beat(message, topic_name, merels_storage):
""" This gets triggered every time a user send a message in any topic
-
:param message: User's message
:param topic_name: User's current topic
:param merels_storage: Merels' storage
- :return: A response string to reply to that topic, if any. If not, it
- returns an empty string
+ :return: a tuple of response string and message, non-empty string
+ we want to keep the turn of the same played,
+ an empty string otherwise.
"""
-
- merels = database.MerelsStorage(merels_storage)
-
- if "create" in message.lower():
- return mechanics.create_room(topic_name, merels_storage)
-
- if "help" in message.lower():
- return getHelp()
-
- if "reset" in message.lower():
- if merels.get_game_data(topic_name) is not None:
- return mechanics.reset_game(topic_name, merels_storage)
- else:
- return "No game created yet."
-
+ merels = database.MerelsStorage(topic_name, merels_storage)
match = COMMAND_PATTERN.match(message)
+ same_player_move = "" # message indicating move of the same player
if match is None:
return unknown_command()
-
- # Matches when user types the command in format of: "command v,h -> v,
- # h" or something similar that has three arguments
-
- if merels.get_game_data(topic_name) is not None:
if match.group(1) is not None and match.group(
2) is not None and match.group(3) is not None:
- responses = ""
+ responses = ""
command = match.group(1)
+
if command.lower() == "move":
+
p1 = [int(x) for x in match.group(2).split(",")]
p2 = [int(x) for x in match.group(3).split(",")]
- # Note that it doesn't have to be "move 1,1 -> 1,2".
- # It can also just be "move 1,1 1,2"
if mechanics.get_take_status(topic_name, merels_storage) == 1:
- responses += "Take is required to proceed. Please try " \
- "again.\n"
- else:
+
+ raise BadMoveException("Take is required to proceed."
+ " Please try again.\n")
+
responses += mechanics.move_man(topic_name, p1, p2,
merels_storage) + "\n"
- responses += after_event_checkup(responses, topic_name,
- merels_storage)
+ no_moves = after_event_checkup(responses, topic_name, merels_storage)
mechanics.update_hill_uid(topic_name, merels_storage)
+
+ responses += mechanics.display_game(topic_name, merels_storage) + "\n"
+
+ if no_moves != "":
+ same_player_move = no_moves
+
else:
- responses += unknown_command()
+ return unknown_command()
+
+ if mechanics.get_take_status(topic_name, merels_storage) == 1:
+ same_player_move = "Take is required to proceed.\n"
+ return responses, same_player_move
- responses += mechanics.display_game(topic_name,
- merels_storage) + "\n"
- return responses
elif match.group(4) is not None and match.group(5) is not None:
command = match.group(4)
p1 = [int(x) for x in match.group(5).split(",")]
@@ -118,46 +104,43 @@ def beat(message, topic_name, merels_storage):
responses = ""
if mechanics.get_take_status(topic_name, merels_storage) == 1:
- responses += "Take is required to proceed. Please try " \
- "again.\n"
- else:
+ raise BadMoveException("Take is required to proceed."
+ " Please try again.\n")
responses += mechanics.put_man(topic_name, p1[0], p1[1],
merels_storage) + "\n"
- responses += after_event_checkup(responses, topic_name,
- merels_storage)
+ no_moves = after_event_checkup(responses, topic_name, merels_storage)
mechanics.update_hill_uid(topic_name, merels_storage)
- responses += mechanics.display_game(topic_name,
- merels_storage) + "\n"
- return responses
+
+ responses += mechanics.display_game(topic_name, merels_storage) + "\n"
+
+ if no_moves != "":
+ same_player_move = no_moves
+ if mechanics.get_take_status(topic_name, merels_storage) == 1:
+ same_player_move = "Take is required to proceed.\n"
+ return responses, same_player_move
# take 5,3
elif command == "take":
responses = ""
if mechanics.get_take_status(topic_name, merels_storage) == 1:
responses += mechanics.take_man(topic_name, p1[0], p1[1],
merels_storage) + "\n"
- if not ("Failed" in responses):
- mechanics.update_toggle_take_mode(topic_name,
- merels_storage)
- mechanics.update_change_turn(topic_name,
- merels_storage)
+ if "Failed" in responses:
+ raise BadMoveException(responses)
+ mechanics.update_toggle_take_mode(topic_name, merels_storage)
+ no_moves = after_event_checkup(responses, topic_name, merels_storage)
mechanics.update_hill_uid(topic_name, merels_storage)
- responses += check_win(topic_name, merels_storage)
- if not ("win" in responses.lower()):
- responses += mechanics.display_game(topic_name,
- merels_storage) \
- + "\n"
- return responses
+ responses += mechanics.display_game(topic_name, merels_storage) + "\n"
+
+ if no_moves != "":
+ same_player_move = no_moves
+ return responses, same_player_move
else:
- return "Taking is not possible."
+ raise BadMoveException("Taking is not possible.")
else:
return unknown_command()
- else:
- return "No game created yet. You cannot do any of the game commands." \
- " Create the game first."
-
def check_take_mode(response, topic_name, merels_storage):
"""This checks whether the previous action can result in a take mode for
@@ -175,7 +158,6 @@ def check_take_mode(response, topic_name, merels_storage):
else:
mechanics.update_change_turn(topic_name, merels_storage)
-
def check_any_moves(topic_name, merels_storage):
"""Check whether the player can make any moves, if can't switch to another
player
@@ -191,7 +173,6 @@ def check_any_moves(topic_name, merels_storage):
return ""
-
def after_event_checkup(response, topic_name, merels_storage):
"""After doing certain moves in the game, it will check for take mode
availability and check for any possible moves
@@ -205,7 +186,6 @@ def after_event_checkup(response, topic_name, merels_storage):
check_take_mode(response, topic_name, merels_storage)
return check_any_moves(topic_name, merels_storage)
-
def check_win(topic_name, merels_storage):
"""Checks whether the current grid has a winner, if it does, finish the
game and remove it from the database
@@ -214,7 +194,7 @@ def check_win(topic_name, merels_storage):
:param merels_storage: Merels' storage
:return:
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
win = mechanics.who_won(topic_name, merels_storage)
if win != "None":

MODIFY
zulip_bots/zulip_bots/bots/merels/libraries/game_data.py
zulip_bots/zulip_bots/bots/merels/libraries/game_data.py
@@ -11,7 +11,7 @@ from .interface import construct_grid
class GameData():
def __init__(self, game_data=(
- 'test', 'X', 0, 0, 'NNNNNNNNNNNNNNNNNNNNNNNN', '', 0)):
+ 'merels', 'X', 0, 0, 'NNNNNNNNNNNNNNNNNNNNNNNN', '', 0)):
self.topic_name = game_data[0]
self.turn = game_data[1]
self.x_taken = game_data[2]
@@ -41,7 +41,7 @@ class GameData():
"""
return construct_grid(self.board)
- def get_x_piece_possesed_not_on_grid(self):
+ def get_x_piece_possessed_not_on_grid(self):
"""Gets the amount of X pieces that the player X still have, but not
put yet on the grid
@@ -49,7 +49,7 @@ class GameData():
"""
return 9 - self.x_taken - mechanics.get_piece("X", self.grid())
- def get_o_piece_possesed_not_on_grid(self):
+ def get_o_piece_possessed_not_on_grid(self):
"""Gets the amount of X pieces that the player O still have, but not
put yet on the grid
@@ -64,8 +64,8 @@ class GameData():
:return: A phase number (1, 2, or 3)
"""
return mechanics.get_phase_number(self.grid(), self.turn,
- self.get_x_piece_possesed_not_on_grid(),
- self.get_o_piece_possesed_not_on_grid())
+ self.get_x_piece_possessed_not_on_grid(),
+ self.get_o_piece_possessed_not_on_grid())
def switch_turn(self):
"""Switches turn between X and O

MODIFY
zulip_bots/zulip_bots/bots/merels/libraries/mechanics.py
zulip_bots/zulip_bots/bots/merels/libraries/mechanics.py
@@ -10,7 +10,7 @@ from . import constants
from . import database
from . import game_data
from . import interface
-
+from zulip_bots.game_handler import BadMoveException
def is_in_grid(vertical_pos, horizontal_pos):
"""Checks whether the cell actually exists or not
@@ -228,7 +228,7 @@ def who_won(topic_name, merels_storage):
is winning
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
if data.get_phase() > 1:
@@ -275,7 +275,7 @@ def create_room(topic_name, merels_storage):
:param merels_storage: Merels' storage
:return: A response string
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
if merels.create_new_game(topic_name):
response = ""
@@ -297,7 +297,7 @@ def display_game(topic_name, merels_storage):
:param merels_storage: Merels' storage
:return: A response string
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
@@ -309,9 +309,9 @@ def display_game(topic_name, merels_storage):
take = "No"
response += interface.graph_grid(data.grid()) + "\n"
- response += """Phase {}, {}'s turn. Take mode: {}.
+ response += """Phase {}. Take mode: {}.
X taken: {}, O taken: {}.
- """.format(data.get_phase(), data.turn, take, data.x_taken, data.o_taken)
+ """.format(data.get_phase(), take, data.x_taken, data.o_taken)
return response
@@ -323,7 +323,7 @@ def reset_game(topic_name, merels_storage):
:param merels_storage: Merels' storage
:return: A response string
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
merels.remove_game(topic_name)
return "Game removed.\n" + create_room(topic_name,
@@ -339,7 +339,7 @@ def move_man(topic_name, p1, p2, merels_storage):
:param merels_storage: Merels' storage
:return: A response string
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
# Get the grid
@@ -361,8 +361,7 @@ def move_man(topic_name, p1, p2, merels_storage):
return "Moved a man from ({0}, {1}) -> ({2}, {3}) for {4}.".format(
p1[0], p1[1], p2[0], p2[1], data.turn)
else:
- return "Failed: That's not a legal move. Please try again."
-
+ raise BadMoveException("Failed: That's not a legal move. Please try again.")
def put_man(topic_name, v, h, merels_storage):
"""Puts a man into the specified cell in topic_name
@@ -373,7 +372,7 @@ def put_man(topic_name, v, h, merels_storage):
:param merels_storage: MerelsDatabase object
:return: A response string
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
# Get the grid
@@ -393,7 +392,7 @@ def put_man(topic_name, v, h, merels_storage):
data.take_mode)
return "Put a man to ({0}, {1}) for {2}.".format(v, h, data.turn)
else:
- return "Failed: That's not a legal put. Please try again."
+ raise BadMoveException("Failed: That's not a legal put. Please try again.")
def take_man(topic_name, v, h, merels_storage):
@@ -405,7 +404,7 @@ def take_man(topic_name, v, h, merels_storage):
:param merels_storage: Merels' storage
:return: A response string
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
# Get the grid
@@ -431,7 +430,7 @@ def take_man(topic_name, v, h, merels_storage):
data.take_mode)
return "Taken a man from ({0}, {1}) for {2}.".format(v, h, data.turn)
else:
- return "Failed: That's not a legal take. Please try again."
+ raise BadMoveException("Failed: That's not a legal take. Please try again.")
def update_hill_uid(topic_name, merels_storage):
@@ -442,7 +441,7 @@ def update_hill_uid(topic_name, merels_storage):
:return: None
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
data.hill_uid = get_hills_numbers(data.grid())
@@ -460,7 +459,7 @@ def update_change_turn(topic_name, merels_storage):
:return: None
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
data.switch_turn()
@@ -478,7 +477,7 @@ def update_toggle_take_mode(topic_name, merels_storage):
:return: None
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
data.toggle_take_mode()
@@ -496,7 +495,7 @@ def get_take_status(topic_name, merels_storage):
:return: 1 or 0
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
return data.take_mode
@@ -528,7 +527,7 @@ def can_take_mode(topic_name, merels_storage):
:return: True if this turn can trigger take mode, False if otherwise
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
current_hill_uid = data.hill_uid
@@ -570,7 +569,7 @@ def can_make_any_move(topic_name, merels_storage):
:return: True if the player has a way, False if there isn't
"""
- merels = database.MerelsStorage(merels_storage)
+ merels = database.MerelsStorage(topic_name, merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
if data.get_phase() != 1:

MODIFY
zulip_bots/zulip_bots/bots/merels/merels.py
zulip_bots/zulip_bots/bots/merels/merels.py
-from zulip_bots.bots.merels.libraries import game
+from typing import List, Any
+from zulip_bots.bots.merels.libraries import (
+ game,
+ mechanics,
+ database,
+ game_data
+)
+from zulip_bots.game_handler import GameAdapter, SamePlayerMove
+class Storage(object):
+ data = {}
-class MerelsBot(object):
- """
- Simulate the merels game to the chat
- """
+ def __init__(self, topic_name):
+ self.data[topic_name] = '["X", 0, 0, "NNNNNNNNNNNNNNNNNNNNNNNN", "", 0]'
- def __init__(self):
- pass
+ def put(self, topic_name, value: str):
+ self.data[topic_name] = value
- def usage(self):
+ def get(self, topic_name):
+ return self.data[topic_name]
+
+class MerelsModel(object):
+
+ def __init__(self, board: Any=None) -> None:
+ self.topic = "merels"
+ self.storage = Storage(self.topic)
+ self.current_board = mechanics.display_game(self.topic, self.storage)
+ self.token = ['O', 'X']
+
+ def determine_game_over(self, players: List[str]) -> str:
+ if self.contains_winning_move(self.current_board):
+ return 'current turn'
+ return ''
+
+ def contains_winning_move(self, board: Any) -> bool:
+ merels = database.MerelsStorage(self.topic, self.storage)
+ data = game_data.GameData(merels.get_game_data(self.topic))
+
+ if data.get_phase() > 1:
+ if (mechanics.get_piece("X", data.grid()) <= 2) or\
+ (mechanics.get_piece("O", data.grid()) <= 2):
+ return True
+ return False
+
+ def make_move(self, move: str, player_number: int, computer_move: bool=False) -> Any:
+ if self.storage.get(self.topic) == '["X", 0, 0, "NNNNNNNNNNNNNNNNNNNNNNNN", "", 0]':
+ self.storage.put(
+ self.topic,
+ '["{}", 0, 0, "NNNNNNNNNNNNNNNNNNNNNNNN", "", 0]'.format(
+ self.token[player_number]
+ ))
+ self.current_board, same_player_move = game.beat(move, self.topic, self.storage)
+ if same_player_move != "":
+ raise SamePlayerMove(same_player_move)
+ return self.current_board
+
+class MerelsMessageHandler(object):
+ tokens = [':o_button:', ':cross_mark_button:']
+
+ def parse_board(self, board: Any) -> str:
+ return board
+
+ def get_player_color(self, turn: int) -> str:
+ return self.tokens[turn]
+
+ def alert_move_message(self, original_player: str, move_info: str) -> str:
+ return original_player + " :"
+
+ def game_start_message(self) -> str:
+ return game.getHelp()
+
+class MerelsHandler(GameAdapter):
+ '''
+ You can play merels! Make sure your message starts with
+ "@mention-bot".
+ '''
+ META = {
+ 'name': 'merels',
+ 'description': 'Lets you play merels against any player.',
+ }
+
+ def usage(self) -> str:
return game.getInfo()
- def handle_message(self, message, bot_handler):
- room_name = self.compose_room_name(message)
- content = message['content']
-
- response = game.beat(content, room_name, bot_handler.storage)
-
- bot_handler.send_reply(message, response)
-
- def compose_room_name(self, message):
- room_name = "test"
- if "type" in message:
- if message['type'] == "stream":
- if 'subject' in message:
- realm = message['sender_realm_str']
- stream = message['display_recipient']
- topic = message['subject']
- room_name = "{}-{}-{}".format(realm, stream, topic)
- else:
- # type == "private"
- realm = message['sender_realm_str']
- users_list = [recipient['email'] for recipient in message[
- 'display_recipient']]
- users = "-".join(sorted(users_list))
- room_name = "{}-{}".format(realm, users)
- return room_name
-
-
-handler_class = MerelsBot
+ def __init__(self) -> None:
+ game_name = 'Merels'
+ bot_name = 'merels'
+ move_help_message = ""
+ move_regex = '.*'
+ model = MerelsModel
+ gameMessageHandler = MerelsMessageHandler
+ super(MerelsHandler, self).__init__(
+ game_name,
+ bot_name,
+ move_help_message,
+ move_regex,
+ model,
+ gameMessageHandler,
+ supports_computer=False
+ )
+
+handler_class = MerelsHandler

MODIFY
zulip_bots/zulip_bots/bots/merels/test_merels.py
zulip_bots/zulip_bots/bots/merels/test_merels.py
@@ -17,61 +17,12 @@ class TestFollowUpBot(zulip_bots.test_lib.BotTestCase):
message = dict(
content='magic',
type='stream',
+ sender_email="boo@email.com",
+ sender_full_name="boo"
)
res = self.get_response(message)
self.assertEqual(res['content'],
- 'Unknown command. Available commands: create, '
- 'reset, help, put (v,h), take (v,h), move (v,'
- 'h) -> (v,h)')
-
- def test_help_command(self):
- message = dict(
- content='help',
- type='stream',
- )
-
- res = self.get_response(message)
-
- self.assertEqual(res['content'], "Commands:\ncreate: Create a new "
- "game if it doesn't exist\nreset: "
- "Reset a current game\nput (v,"
- "h): Put a man into the grid in "
- "phase 1\nmove (v,h) -> (v,"
- "h): Moves a man from one point to "
- "-> another point\ntake (v,h): Take "
- "an opponent's man from the grid in "
- "phase 2/3\n\nv: vertical position "
- "of grid\nh: horizontal position of "
- "grid")
-
- def test_create_new_game(self):
- message = dict(
- content='create',
- type='stream',
- subject='test'
- )
-
- with mock.patch.object(zulip_bots.bots.merels.merels.MerelsBot,
- 'compose_room_name',
- return_value="test"):
- res = self.get_response(message)
-
- self.assertEqual(res['content'], '''A room has been created in test. Starting game now.
-` 0 1 2 3 4 5 6
- 0 [ ]---------------[ ]---------------[ ]
- | | |
- 1 | [ ]---------[ ]---------[ ] |
- | | | | |
- 2 | | [ ]---[ ]---[ ] | |
- | | | | | |
- 3 [ ]---[ ]---[ ] [ ]---[ ]---[ ]
- | | | | | |
- 4 | | [ ]---[ ]---[ ] | |
- | | | | |
- 5 | [ ]---------[ ]---------[ ] |
- | | |
- 6 [ ]---------------[ ]---------------[ ]`
-Phase 1, X's turn. Take mode: No.
-X taken: 0, O taken: 0.\n ''')
+ 'You are not in a game at the moment.'
+ ' Type `help` for help.')

Port merels to game_handler.

Fixes #305