interactive bots: Create Merels bot.

This commit is contained in:
Privisus 2017-12-29 13:39:22 +07:00 committed by Robert Hönig
parent a2557ccbe6
commit b80a0cb297
16 changed files with 1767 additions and 0 deletions

View file

@ -0,0 +1,3 @@
*.db
.idea/

View file

@ -0,0 +1,48 @@
"""Provide some constants that are crucial for running the game
"""
# Do NOT scramble these. This is written such that it starts from top left
# to bottom right.
ALLOWED_MOVES = ([0, 0], [0, 3], [0, 6],
[1, 1], [1, 3], [1, 5],
[2, 2], [2, 3], [2, 4],
[3, 0], [3, 1], [3, 2], [3, 4], [3, 5], [3, 6],
[4, 2], [4, 3], [4, 4],
[5, 1], [5, 3], [5, 5],
[6, 0], [6, 3], [6, 6])
AM = ALLOWED_MOVES
# Do NOT scramble these, This is written such that it starts from horizontal
# to vertical, top to bottom, left to right.
HILLS = ([AM[0], AM[1], AM[2]],
[AM[3], AM[4], AM[5]],
[AM[6], AM[7], AM[8]],
[AM[9], AM[10], AM[11]],
[AM[12], AM[13], AM[14]],
[AM[15], AM[16], AM[17]],
[AM[18], AM[19], AM[20]],
[AM[21], AM[22], AM[23]],
[AM[0], AM[9], AM[21]],
[AM[3], AM[10], AM[18]],
[AM[6], AM[11], AM[15]],
[AM[1], AM[4], AM[7]],
[AM[16], AM[19], AM[22]],
[AM[8], AM[12], AM[17]],
[AM[5], AM[13], AM[20]],
[AM[2], AM[14], AM[23]],
)
OUTER_SQUARE = ([0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6],
[1, 0], [2, 0], [3, 0], [4, 0], [5, 0], [6, 0],
[6, 0], [6, 1], [6, 2], [6, 3], [6, 4], [6, 5], [6, 6],
[0, 6], [1, 6], [2, 6], [3, 6], [4, 6], [5, 6])
MIDDLE_SQUARE = ([1, 1], [1, 2], [1, 3], [1, 4], [1, 5],
[2, 1], [3, 1], [4, 1], [5, 1],
[5, 1], [5, 2], [5, 3], [5, 4], [5, 5],
[1, 5], [2, 5], [3, 5], [4, 5])
INNER_SQUARE = ([2, 2], [2, 3], [2, 4], [3, 2], [3, 4], [4, 2], [4, 3], [4, 4])
EMPTY_BOARD = "NNNNNNNNNNNNNNNNNNNNNNNN"

View file

@ -0,0 +1,106 @@
"""This module is used for managing, storing, and operating certain
functions to said data. Almost every action of the data is wrapped with the
connection opening and closing. This module is supplied with a default name
for the database. If the user is not satisfied with the name, the user can
change it with their own database name for convenience.
Essentially, this database is used for storing static matches that hasn't
finished yet so any matches that are finished will be removed.
"""
import json
from .constants import EMPTY_BOARD
class MerelsStorage():
def __init__(self, storage):
"""Instantiate storage field.
The current database has this form:
TOPIC_NAME (UNIQUE)
+----> TURN
+----> X_TAKEN
+----> O_TAKEN
+----> BOARD
+----> HILL_UID
+----> TAKE_MODE
:param name: Name of the storage
"""
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.
:param topic_name: The name of the topic
:param turn: "X" or "O"
:param x_taken: How many X's are taken from the board during the
gameplay by O
:param o_taken: How many O's are taken from the board during the
gameplay by X
:param board: A compact representation of the grid
:param hill_uid: Unique hill id
:param take_mode: Whether the game is in take mode, which "turn" has
to take a piece
:return: None
"""
parameters = (
turn, x_taken, o_taken, board, hill_uid, take_mode)
self.storage.put(topic_name, json.dumps(parameters))
def remove_game(self, topic_name):
""" Removes the game from the database by setting it to an empty
string. An empty string marks an empty match.
:param topic_name: The name of the topic
:return: None
"""
self.storage.put(topic_name, "")
def get_game_data(self, topic_name):
"""Gets the game data
:param topic_name: The name of the topic
:return: A tuple containing the data
"""
try:
select = json.loads(self.storage.get(topic_name))
except (json.decoder.JSONDecodeError, KeyError):
select = ""
if select == "":
return None
else:
res = (topic_name, select[0], select[1], select[2], select[3],
select[4], select[5])
return res

View file

@ -0,0 +1,223 @@
"""This is the main component of the game, the core of the game that squishes
everything together and make the game work. Usually user can just import this
module and use the beat() function and everything will be fine, but will there
be any certain things that can't be accomplished that way, the user may also
freely import another modules.
"""
import re
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
:return: Info on how to start the game
"""
return "To start a game, mention me and add `create`. A game will start " \
"in that topic. "
def getHelp():
""" Gets the help message
:return: Help message
"""
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
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, " \
"put (v,h), take (v,h), move (v,h) -> (v,h)"
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
"""
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."
match = COMMAND_PATTERN.match(message)
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 = ""
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:
responses += mechanics.move_man(topic_name, p1, p2,
merels_storage) + "\n"
responses += after_event_checkup(responses, topic_name,
merels_storage)
mechanics.update_hill_uid(topic_name, merels_storage)
else:
responses += unknown_command()
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(",")]
# put 1,2
if command == "put":
responses = ""
if mechanics.get_take_status(topic_name, merels_storage) == 1:
responses += "Take is required to proceed. Please try " \
"again.\n"
else:
responses += mechanics.put_man(topic_name, p1[0], p1[1],
merels_storage) + "\n"
responses += 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
# 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)
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
else:
return "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
current player. This assumes that the previous action is successful and not
failed.
:param response: A response string
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: None
"""
if not ("Failed" in response):
if mechanics.can_take_mode(topic_name, merels_storage):
mechanics.update_toggle_take_mode(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
:param topic_name: Topic name
:param merels_storage: MerelsDatabase object
:return: A response string
"""
if not mechanics.can_make_any_move(topic_name, merels_storage):
mechanics.update_change_turn(topic_name, merels_storage)
return "Cannot make any move on the grid. Switching to " \
"previous player.\n"
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
:param response: Current response string. This is useful for checking
any failed previous commands
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: A response string
"""
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
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return:
"""
merels = database.MerelsStorage(merels_storage)
win = mechanics.who_won(topic_name, merels_storage)
if win != "None":
merels.remove_game(topic_name)
return "{0} wins the game!".format(win)
return ""

View file

@ -0,0 +1,88 @@
"""This serves as a bridge between the database and the other modules.
In a nutshell, this module parses a tuple from database then translates it
into a more convenient naming for easier access. It also adds certain
functions that are useful for the function of the game.
"""
from . import mechanics
from .interface import construct_grid
class GameData():
def __init__(self, game_data=(
'test', 'X', 0, 0, 'NNNNNNNNNNNNNNNNNNNNNNNN', '', 0)):
self.topic_name = game_data[0]
self.turn = game_data[1]
self.x_taken = game_data[2]
self.o_taken = game_data[3]
self.board = game_data[4]
self.hill_uid = game_data[5]
self.take_mode = game_data[6]
def __len__(self):
return len(self.construct())
def construct(self):
"""Constructs a tuple based on existing records
:return: A tuple containing all the game records
"""
res = (
self.topic_name, self.turn, self.x_taken, self.o_taken, self.board,
self.hill_uid, self.take_mode)
return res
def grid(self):
"""Returns the grid
:return: A 2-dimensional 7x7 list (the grid)
"""
return construct_grid(self.board)
def get_x_piece_possesed_not_on_grid(self):
"""Gets the amount of X pieces that the player X still have, but not
put yet on the grid
:return: Amount of pieces that X has, but not on grid
"""
return 9 - self.x_taken - mechanics.get_piece("X", self.grid())
def get_o_piece_possesed_not_on_grid(self):
"""Gets the amount of X pieces that the player O still have, but not
put yet on the grid
:return: Amount of pieces that O has, but not on grid
"""
return 9 - self.o_taken - mechanics.get_piece("O", self.grid())
def get_phase(self):
"""Gets the phase number for the current game
: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())
def switch_turn(self):
"""Switches turn between X and O
:return: None
"""
if self.turn == "X":
self.turn = "O"
else:
self.turn = "X"
def toggle_take_mode(self):
"""Toggles take mode
:return: None
"""
if self.take_mode == 0:
self.take_mode = 1
else:
self.take_mode = 0

View file

@ -0,0 +1,104 @@
"""Interface helps the game displaying and maintaining the board. This is
where the grid can get translated into a board, which is easier to manage
in the database, and board translated to grid, which is easier to manage in
the mechanics.
The display heavily depends on the availability of monospaced font. This is
why the graph_grid() is wrapped in `` (it is expected for the user to
provide a Markdown support)
"""
from . import constants
def draw_grid(grid):
"""Draws a board from a grid
:param grid: a 2-dimensional 7x7 list
:return: None
"""
print(graph_grid(grid))
def graph_grid(grid):
"""Creates a nice grid display, something like this:
0 1 2 3 4 5 6
0 [ ]---------------[ ]---------------[ ]
| | |
1 | [ ]---------[ ]---------[ ] |
| | | | |
2 | | [ ]---[ ]---[ ] | |
| | | | | |
3 [ ]---[ ]---[ ] [ ]---[ ]---[ ]
| | | | | |
4 | | [ ]---[ ]---[ ] | |
| | | | |
5 | [ ]---------[ ]---------[ ] |
| | |
6 [ ]---------------[ ]---------------[ ]
:param grid: a 2-dimensional 7x7 list.
:return: A nicer display of the grid
"""
return '''` 0 1 2 3 4 5 6
0 [{}]---------------[{}]---------------[{}]
| | |
1 | [{}]---------[{}]---------[{}] |
| | | | |
2 | | [{}]---[{}]---[{}] | |
| | | | | |
3 [{}]---[{}]---[{}] [{}]---[{}]---[{}]
| | | | | |
4 | | [{}]---[{}]---[{}] | |
| | | | |
5 | [{}]---------[{}]---------[{}] |
| | |
6 [{}]---------------[{}]---------------[{}]`'''.format(
grid[0][0], grid[0][3], grid[0][6],
grid[1][1], grid[1][3], grid[1][5],
grid[2][2], grid[2][3], grid[2][4],
grid[3][0], grid[3][1], grid[3][2], grid[3][4], grid[3][5], grid[3][6],
grid[4][2], grid[4][3], grid[4][4],
grid[5][1], grid[5][3], grid[5][5],
grid[6][0], grid[6][3], grid[6][6])
def construct_grid(board):
"""Constructs the original grid from the database
:param board: A compact representation of the grid (example:
"NONXONXONXONXONNOXNXNNOX")
:return: A grid
"""
grid = [[" " for _ in range(7)] for _ in range(7)]
for k, cell in enumerate(board):
if cell == "O" or cell == "X":
grid[constants.ALLOWED_MOVES[k][0]][
constants.ALLOWED_MOVES[k][1]] = cell
return grid
def construct_board(grid):
"""Constructs a board from a grid
:param grid: A 2-dimensional 7x7 list
:return: A board. Board is a compact representation of the grid
"""
board = ""
for cell_location in constants.ALLOWED_MOVES:
cell_content = grid[cell_location[0]][cell_location[1]]
if cell_content == "X" or cell_content == "O":
board += cell_content
else:
board += "N"
return board

View file

@ -0,0 +1,579 @@
"""Mechanics is what makes everything moves and works. It stores the game
mechanisms as well as some functions for accessing the database.
"""
from math import sqrt
from collections import Counter
from . import constants
from . import database
from . import game_data
from . import interface
def is_in_grid(vertical_pos, horizontal_pos):
"""Checks whether the cell actually exists or not
:param vertical_pos: Vertical position of the man, in int
:param horizontal_pos: Horizontal position of the man, in int
:return:
True, if it exists (meaning: in the grid)
False, if it doesn't exist (meaning: out of grid)
"""
return [vertical_pos, horizontal_pos] in constants.ALLOWED_MOVES
def is_empty(vertical_pos, horizontal_pos, grid):
"""Checks whether the current cell is empty
:param vertical_pos: Vertical position of the man, in int
:param horizontal_pos: Horizontal position of the man, in int
:param grid: A 2-dimensional 7x7 list
:return:
True, if it is empty
False, if it is not empty
"""
return grid[vertical_pos][horizontal_pos] == " "
def is_jump(vpos_before, hpos_before, vpos_after, hpos_after):
"""Checks whether the move is considered jumping
:param vpos_before: Vertical cell location before jumping
:param hpos_before: Horizontal cell location before jumping
:param vpos_after: Vertical cell location after jumping
:param hpos_after: Horizontal cell location after jumping
:return:
True, if it is jumping
False, if it is not jumping
"""
distance = sqrt(
(vpos_after - vpos_before) ** 2 + (hpos_after - hpos_before) ** 2)
# If the man is in outer square, the distance must be 3 or 1
if [vpos_before, hpos_before] in constants.OUTER_SQUARE:
return not (distance == 3 or distance == 1)
# If the man is in middle square, the distance must be 2 or 1
if [vpos_before, hpos_before] in constants.MIDDLE_SQUARE:
return not (distance == 2 or distance == 1)
# If the man is in inner square, the distance must be only 1
if [vpos_before, hpos_before] in constants.INNER_SQUARE:
return not (distance == 1)
def get_hills_numbers(grid):
"""Checks for hills, if it exists, get its relative position based on
constants.py
:param grid: A 7x7 2 dimensional grid
:return: A string, containing the relative position of hills based on
constants.py
"""
relative_hills = ""
for k, hill in enumerate(constants.HILLS):
v1, h1 = hill[0][0], hill[0][1]
v2, h2 = hill[1][0], hill[1][1]
v3, h3 = hill[2][0], hill[2][1]
if all(x == "O" for x in
(grid[v1][h1], grid[v2][h2], grid[v3][h3])) or all(
x == "X" for x in (grid[v1][h1], grid[v2][h2], grid[v3][h3])):
relative_hills += str(k)
return relative_hills
def move_man_legal(v1, h1, v2, h2, grid):
"""Moves a man into a specified cell, assuming it is a legal move
:param v1: Vertical position of cell
:param h1: Horizontal position of cell
:param v2: Vertical position of cell
:param h2: Horizontal version of cell
:param grid: A 2-dimensional 7x7 list
:return: None, since grid is mutable
"""
grid[v2][h2] = grid[v1][h1]
grid[v1][h1] = " "
def put_man_legal(turn, v, h, grid):
"""Puts a man into specified cell, assuming it is a legal move
:param turn: "X" or "O"
:param v: Vertical position of cell
:param h: Horizontal position of cell
:param grid: A 2-dimensional 7x7 grid
:return: None, since grid is mutable
"""
grid[v][h] = turn
def take_man_legal(v, h, grid):
"""Takes an opponent's man from a specified cell.
:param v: Vertical position of the cell
:param h: Horizontal position of the cell
:param grid: A 2-dimensional 7x7 list
:return: None, since grid is mutable
"""
grid[v][h] = " "
def is_legal_move(v1, h1, v2, h2, turn, phase, grid):
"""Determines whether the current move is legal or not
:param v1: Vertical position of man
:param h1: Horizontal position of man
:param v2: Vertical position of man
:param h2: Horizontal position of man
:param turn: "X" or "O"
:param phase: Current phase of the game
:param grid: A 2-dimensional 7x7 list
:return: True if it is legal, False it is not legal
"""
if phase == 1:
return False # Place all the pieces first before moving one
if phase == 3 and get_piece(turn, grid) == 3:
return is_in_grid(v2, h2) and is_empty(v2, h2, grid) and is_own_piece(
v1, h1, turn, grid)
return is_in_grid(v2, h2) and is_empty(v2, h2, grid) and (
not is_jump(v1, h1, v2, h2)) and is_own_piece(v1,
h1,
turn,
grid)
def is_own_piece(v, h, turn, grid):
"""Check if the player is using the correct piece
:param v: Vertical position of man
:param h: Horizontal position of man
:param turn: "X" or "O"
:param grid: A 2-dimensional 7x7 list
:return: True, if the player is using their own piece, False if otherwise.
"""
return grid[v][h] == turn
def is_legal_put(v, h, grid, phase_number):
"""Determines whether putting the man in specified cell location is legal
or not
:param v: Vertical position of man
:param h: Horizontal position of man
:param grid: A 2-dimensional 7x7 list
:param phase_number: 1, 2, or 3
:return: True if it is legal, False if it is not legal
"""
return is_in_grid(v, h) and is_empty(v, h, grid) and phase_number == 1
def is_legal_take(v, h, turn, grid, take_mode):
"""Determines whether taking a man in that cell is legal or not
:param v: Vertical position of man
:param h: Horizontal position of man
:param turn: "X" or "O"
:param grid: A 2-dimensional 7x7 list
:param take_mode: 1 or 0
:return: True if it is legal, False if it is not legal
"""
return is_in_grid(v, h) and not is_empty(v, h, grid) and not is_own_piece(
v, h, turn, grid) and take_mode == 1
def get_piece(turn, grid):
"""Counts the current piece on the grid
:param turn: "X" or "O"
:param grid: A 2-dimensional 7x7 list
:return: Number of pieces of "turn" on the grid
"""
grid_combined = []
for row in grid:
grid_combined += row
counter = Counter(tuple(grid_combined))
return counter[turn]
def who_won(topic_name, merels_storage):
"""Who won the game? If there was any at that moment
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: "None", if there is no one, "X" if X is winning, "O" if O
is winning
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
if data.get_phase() > 1:
if get_piece("X", data.grid()) <= 2:
return "O"
if get_piece("O", data.grid()) <= 2:
return "X"
return "None"
def get_phase_number(grid, turn, x_pieces_possessed_not_on_grid,
o_pieces_possessed_not_on_grid):
"""Updates current game phase
:param grid: A 2-dimensional 7x7 list
:param turn: "X" or "O"
:param x_pieces_possessed_not_on_grid: Amount of man that X currently have,
but not placed yet
:param o_pieces_possessed_not_on_grid: Amount of man that O currently have,
but not placed yet
:return: Phase number. 1 is "placing pieces", 2 is "moving pieces", and 3
is "flying"
"""
if x_pieces_possessed_not_on_grid != 0 or o_pieces_possessed_not_on_grid \
!= 0:
# Placing pieces
return 1
else:
if get_piece("X", grid) <= 3 or get_piece("O", grid) <= 3:
# Flying
return 3
else:
# Moving pieces
return 2
def create_room(topic_name, merels_storage):
"""Creates a game in current topic
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: A response string
"""
merels = database.MerelsStorage(merels_storage)
if merels.create_new_game(topic_name):
response = ""
response += "A room has been created in {0}. Starting game now.\n". \
format(topic_name)
response += display_game(topic_name, merels_storage)
return response
else:
return "Failed: Cannot create an already existing game in {0}. " \
"Please finish the game first.".format(topic_name)
def display_game(topic_name, merels_storage):
"""Displays the current layout of the game, with additional info such as
phase number and turn.
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: A response string
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
response = ""
if data.take_mode == 1:
take = "Yes"
else:
take = "No"
response += interface.graph_grid(data.grid()) + "\n"
response += """Phase {}, {}'s turn. Take mode: {}.
X taken: {}, O taken: {}.
""".format(data.get_phase(), data.turn, take, data.x_taken, data.o_taken)
return response
def reset_game(topic_name, merels_storage):
"""Resets the game in current topic
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: A response string
"""
merels = database.MerelsStorage(merels_storage)
merels.remove_game(topic_name)
return "Game removed.\n" + create_room(topic_name,
merels_storage) + "Game reset.\n"
def move_man(topic_name, p1, p2, merels_storage):
"""Moves the current man in topic_name from p1 to p2
:param topic_name: Topic name
:param p1: First cell location
:param p2: Second cell location
:param merels_storage: Merels' storage
:return: A response string
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
# Get the grid
grid = data.grid()
# Check legal move
if is_legal_move(p1[0], p1[1], p2[0], p2[1], data.turn, data.get_phase(),
data.grid()):
# Move the man
move_man_legal(p1[0], p1[1], p2[0], p2[1], grid)
# Construct the board back from updated grid
board = interface.construct_board(grid)
# Insert/update the current board
data.board = board
# Update the game data
merels.update_game(data.topic_name, data.turn, data.x_taken,
data.o_taken, data.board, data.hill_uid,
data.take_mode)
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."
def put_man(topic_name, v, h, merels_storage):
"""Puts a man into the specified cell in topic_name
:param topic_name: Topic name
:param v: Vertical position of cell
:param h: Horizontal position of cell
:param merels_storage: MerelsDatabase object
:return: A response string
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
# Get the grid
grid = data.grid()
# Check legal put
if is_legal_put(v, h, grid, data.get_phase()):
# Put the man
put_man_legal(data.turn, v, h, grid)
# Construct the board back from updated grid
board = interface.construct_board(grid)
# Insert/update form current board
data.board = board
# Update the game data
merels.update_game(data.topic_name, data.turn, data.x_taken,
data.o_taken, data.board, data.hill_uid,
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."
def take_man(topic_name, v, h, merels_storage):
"""Takes a man from the grid
:param topic_name: Topic name
:param v: Vertical position of cell
:param h: Horizontal position of cell
:param merels_storage: Merels' storage
:return: A response string
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
# Get the grid
grid = data.grid()
# Check legal put
if is_legal_take(v, h, data.turn, grid, data.take_mode):
# Take the man
take_man_legal(v, h, grid)
if data.turn == "X":
data.o_taken += 1
else:
data.x_taken += 1
# Construct the board back from updated grid
board = interface.construct_board(grid)
# Insert/update form current board
data.board = board
# Update the game data
merels.update_game(data.topic_name, data.turn, data.x_taken,
data.o_taken, data.board, data.hill_uid,
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."
def update_hill_uid(topic_name, merels_storage):
"""Updates the hill_uid then store it to the database
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: None
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
data.hill_uid = get_hills_numbers(data.grid())
merels.update_game(data.topic_name, data.turn, data.x_taken,
data.o_taken, data.board, data.hill_uid,
data.take_mode)
def update_change_turn(topic_name, merels_storage):
"""Changes the turn of player, from X to O and from O to X
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: None
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
data.switch_turn()
merels.update_game(data.topic_name, data.turn, data.x_taken,
data.o_taken, data.board, data.hill_uid,
data.take_mode)
def update_toggle_take_mode(topic_name, merels_storage):
"""Toggle take_mode from 0 to 1 and from 1 to 0
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: None
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
data.toggle_take_mode()
merels.update_game(data.topic_name, data.turn, data.x_taken,
data.o_taken, data.board, data.hill_uid,
data.take_mode)
def get_take_status(topic_name, merels_storage):
"""Gets the take status
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: 1 or 0
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
return data.take_mode
def can_take_mode(topic_name, merels_storage):
"""Check if current turn can trigger take mode.
This process can be thought of as seeing the differences between previous
hill_uid and current hill_uid.
Previous hill_uid can be obtained before updating the hill_uid, and current
hill_uid can be obtained after updating the grid.
If the differences and length decreases after, then it is not possible that
he player forms a new hill.
If the differences or length increases, it is possible that the player that
makes the move forms a new hill. This
essentially triggers the take mode, as the player must take one opponent's
piece from the grid.
Essentially, how this works is by checking an updated, but not fully
finished complete database. So the differences between hill_uid can be
seen.
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: True if this turn can trigger take mode, False if otherwise
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
current_hill_uid = data.hill_uid
updated_grid = data.grid()
updated_hill_uid = get_hills_numbers(updated_grid)
if current_hill_uid != updated_hill_uid and len(updated_hill_uid) >= len(
current_hill_uid):
return True
else:
return False
def check_moves(turn, grid):
"""Checks whether the player can make any moves from specified grid and turn
:param turn: "X" or "O"
:param grid: A 2-dimensional 7x7 list
:return: True, if there is any, False if otherwise
"""
for hill in constants.HILLS:
for k in range(0, 2):
g1 = grid[hill[k][0]][hill[k][1]]
g2 = grid[hill[k + 1][0]][hill[k + 1][1]]
if (g1 == " " and g2 == turn) or (g2 == " " and g1 == turn):
return True
return False
def can_make_any_move(topic_name, merels_storage):
"""Checks whether the player can actually make a move. If it is phase 1,
don't check it and return True instead
:param topic_name: Topic name
:param merels_storage: Merels' storage
:return: True if the player has a way, False if there isn't
"""
merels = database.MerelsStorage(merels_storage)
data = game_data.GameData(merels.get_game_data(topic_name))
if data.get_phase() != 1:
return check_moves(data.turn, data.grid())
return True

View file

@ -0,0 +1,42 @@
from zulip_bots.bots.merels.libraries import game
class MerelsBot(object):
"""
Simulate the merels game to the chat
"""
def __init__(self):
pass
def usage(self):
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

View file

@ -0,0 +1,50 @@
import unittest
from libraries import constants
class CheckIntegrity(unittest.TestCase):
def test_grid_layout_integrity(self):
grid_layout = ([0, 0], [0, 3], [0, 6],
[1, 1], [1, 3], [1, 5],
[2, 2], [2, 3], [2, 4],
[3, 0], [3, 1], [3, 2], [3, 4], [3, 5], [3, 6],
[4, 2], [4, 3], [4, 4],
[5, 1], [5, 3], [5, 5],
[6, 0], [6, 3], [6, 6])
self.assertEqual(constants.ALLOWED_MOVES, grid_layout,
"Incorrect grid layout.")
def test_relative_hills_integrity(self):
grid_layout = ([0, 0], [0, 3], [0, 6],
[1, 1], [1, 3], [1, 5],
[2, 2], [2, 3], [2, 4],
[3, 0], [3, 1], [3, 2], [3, 4], [3, 5], [3, 6],
[4, 2], [4, 3], [4, 4],
[5, 1], [5, 3], [5, 5],
[6, 0], [6, 3], [6, 6])
AM = grid_layout
relative_hills = ([AM[0], AM[1], AM[2]],
[AM[3], AM[4], AM[5]],
[AM[6], AM[7], AM[8]],
[AM[9], AM[10], AM[11]],
[AM[12], AM[13], AM[14]],
[AM[15], AM[16], AM[17]],
[AM[18], AM[19], AM[20]],
[AM[21], AM[22], AM[23]],
[AM[0], AM[9], AM[21]],
[AM[3], AM[10], AM[18]],
[AM[6], AM[11], AM[15]],
[AM[1], AM[4], AM[7]],
[AM[16], AM[19], AM[22]],
[AM[8], AM[12], AM[17]],
[AM[5], AM[13], AM[20]],
[AM[2], AM[14], AM[23]],
)
self.assertEqual(constants.HILLS, relative_hills,
"Incorrect relative hills arrangement")

View file

@ -0,0 +1,66 @@
import unittest
from libraries import database
from libraries import game_data
from zulip_bots.simple_lib import SimpleStorage
class DatabaseTest(unittest.TestCase):
def setUp(self):
self.storage = SimpleStorage()
self.merels = database.MerelsStorage(self.storage)
def test_create_duplicate_game(self):
self.merels.create_new_game("test")
self.assertEqual(self.merels.create_new_game("test"), False)
def test_obtain_gamedata(self):
self.merels.create_new_game("test")
res = self.merels.get_game_data("test")
self.assertTupleEqual(res, (
'test', 'X', 0, 0, 'NNNNNNNNNNNNNNNNNNNNNNNN', "", 0))
self.assertEqual(len(res), 7)
def test_obtain_nonexisting_gamedata(self):
res = self.merels.get_game_data("test")
self.assertEqual(res, None)
def test_game_session(self):
self.merels.create_new_game("test")
self.merels.update_game("test", "O", 5, 4, "XXXXOOOOONNNNNNNNNNNNNNN",
"",
0)
self.merels.create_new_game("test2")
self.assertTrue(self.storage.contains("test"), self.storage.contains(
"test2"))
self.assertEqual(
game_data.GameData(self.merels.get_game_data("test")).board,
"XXXXOOOOONNNNNNNNNNNNNNN")
def test_no_duplicates(self):
self.merels.create_new_game("test")
self.merels.update_game("test", "X", 0, 0, "XXXNNNOOOXXXNNNOOOXXXNNN",
"", 1)
self.merels.create_new_game("test")
self.merels.create_new_game("test")
self.merels.create_new_game("test")
self.merels.create_new_game("test")
self.merels.create_new_game("test")
self.merels.create_new_game("test")
self.merels.create_new_game("test")
self.assertEqual(game_data.GameData(self.merels.get_game_data(
"test")).board, "XXXNNNOOOXXXNNNOOOXXXNNN")
def test_remove_game(self):
self.merels.create_new_game("test")
self.merels.remove_game("test")
self.assertTrue(self.merels.create_new_game("test"))

View file

@ -0,0 +1,117 @@
import unittest
from libraries import game
from libraries import database
from zulip_bots.simple_lib import SimpleStorage
class GameTest(unittest.TestCase):
def setUp(self):
self.storage = SimpleStorage()
self.topic_name = "test"
def test_reset_game_output(self):
game.beat("create", self.topic_name, self.storage)
self.assertTrue("reset" in game.beat("reset", self.topic_name,
self.storage))
def test_reset_no_game_output(self):
self.assertTrue("No game created yet" in game.beat("reset",
self.topic_name,
self.storage))
def test_command_when_no_game_created_output(self):
self.assertTrue("cannot do any of the game commands" in game.beat(
"put 0,0", self.topic_name, self.storage))
def test_put_piece_output(self):
game.beat("create", self.topic_name, self.storage)
self.assertTrue("Put a man" in game.beat("put 0,0", self.topic_name,
self.storage))
def test_not_possible_put_piece_output(self):
game.beat("create", self.topic_name, self.storage)
self.assertTrue("Failed" in game.beat("put 0,1", self.topic_name,
self.storage))
def test_take_before_put_output(self):
merels = database.MerelsStorage(self.storage)
game.beat("create", self.topic_name, self.storage)
merels.update_game(self.topic_name, "X", 0, 0,
"XXXNNNOOOXXXNNNOOOXXXNNN", "", 1)
self.assertTrue("Take is required", game.beat("put 1,1",
self.topic_name,
self.storage))
def test_move_piece_output(self):
merels = database.MerelsStorage(self.storage)
game.beat("create", self.topic_name, self.storage)
merels.update_game(self.topic_name, "X", 0, 0,
"XXXNNNOOOXXXNNNOOOXXXOOO", "", 0)
self.assertTrue("Moved a man" in game.beat("move 0,3 1,3",
self.topic_name,
self.storage))
def test_not_possible_move_piece_output(self):
merels = database.MerelsStorage(self.storage)
game.beat("create", self.topic_name, self.storage)
merels.update_game(self.topic_name, "X", 0, 0,
"XXXNNNOOOXXXNNNOOOXXXOOO", "", 0)
self.assertTrue("Failed" in game.beat("move 0,3 1,2",
self.topic_name,
self.storage))
def test_cannot_make_any_move_output(self):
merels = database.MerelsStorage(self.storage)
game.beat("create", self.topic_name, self.storage)
merels.update_game(self.topic_name, "X", 3, 4,
"OOXOXNOXNNOXNNNNNNXNNXNN", "", 0)
self.assertTrue("Switching" in game.beat("move 6,0 3,0",
self.topic_name,
self.storage))
def test_take_before_move_output(self):
merels = database.MerelsStorage(self.storage)
game.beat("create", self.topic_name, self.storage)
merels.update_game(self.topic_name, "X", 6, 6,
"XXXNNNOOONNNNNNNNNNNNNNN", "", 1)
self.assertTrue("Take is required", game.beat("move 0,1 1,3",
self.topic_name,
self.storage))
def test_unknown_command(self):
merels = database.MerelsStorage(self.storage)
game.beat("create", self.topic_name, self.storage)
merels.update_game(self.topic_name, "X", 6, 6,
"XXXNNNOOONNNNNNNNNNNNNNN", "", 1)
self.assertTrue("Unknown command", game.beat("magic 2,2",
self.topic_name,
self.storage))
def test_take_piece_output(self):
merels = database.MerelsStorage(self.storage)
game.beat("create", self.topic_name, self.storage)
merels.update_game(self.topic_name, "X", 0, 0,
"XXXNNNOOOXXXNNNOOOXXXOOO", "", 1)
self.assertTrue("Taken a man" in game.beat("take 2,2",
self.topic_name,
self.storage))
def test_not_possible_take_piece_output(self):
merels = database.MerelsStorage(self.storage)
game.beat("create", self.topic_name, self.storage)
merels.update_game(self.topic_name, "X", 6, 6,
"XXXNNNOOOXXXNNNOOOXXXOOO", "", 0)
self.assertTrue("Taking is not possible" in game.beat("take 2,2",
self.topic_name,
self.storage))
def test_win_output(self):
merels = database.MerelsStorage(self.storage)
game.beat("create", self.topic_name, self.storage)
merels.update_game(self.topic_name, "X", 6, 6,
"XXXNNNOOONNNNNNNNNNNNNNN", "", 1)
self.assertTrue("wins the game!", game.beat("take 2,2",
self.topic_name,
self.storage))

View file

@ -0,0 +1,78 @@
import unittest
from libraries import interface
class BoardLayoutTest(unittest.TestCase):
def test_empty_layout_arrangement(self):
grid = interface.construct_grid("NNNNNNNNNNNNNNNNNNNNNNNN")
self.assertEqual(interface.graph_grid(grid), '''` 0 1 2 3 4 5 6
0 [ ]---------------[ ]---------------[ ]
| | |
1 | [ ]---------[ ]---------[ ] |
| | | | |
2 | | [ ]---[ ]---[ ] | |
| | | | | |
3 [ ]---[ ]---[ ] [ ]---[ ]---[ ]
| | | | | |
4 | | [ ]---[ ]---[ ] | |
| | | | |
5 | [ ]---------[ ]---------[ ] |
| | |
6 [ ]---------------[ ]---------------[ ]`''')
def test_full_layout_arragement(self):
grid = interface.construct_grid("NXONXONXONXONXONXONXONXO")
self.assertEqual(interface.graph_grid(grid), '''` 0 1 2 3 4 5 6
0 [ ]---------------[X]---------------[O]
| | |
1 | [ ]---------[X]---------[O] |
| | | | |
2 | | [ ]---[X]---[O] | |
| | | | | |
3 [ ]---[X]---[O] [ ]---[X]---[O]
| | | | | |
4 | | [ ]---[X]---[O] | |
| | | | |
5 | [ ]---------[X]---------[O] |
| | |
6 [ ]---------------[X]---------------[O]`''')
def test_illegal_character_arrangement(self):
grid = interface.construct_grid("ABCDABCDABCDABCDABCDXXOO")
self.assertEqual(interface.graph_grid(grid), '''` 0 1 2 3 4 5 6
0 [ ]---------------[ ]---------------[ ]
| | |
1 | [ ]---------[ ]---------[ ] |
| | | | |
2 | | [ ]---[ ]---[ ] | |
| | | | | |
3 [ ]---[ ]---[ ] [ ]---[ ]---[ ]
| | | | | |
4 | | [ ]---[ ]---[ ] | |
| | | | |
5 | [ ]---------[ ]---------[X] |
| | |
6 [X]---------------[O]---------------[O]`''')
class ParsingTest(unittest.TestCase):
def test_consistent_parse(self):
boards = ["NNNNOOOOXXXXNNNNOOOOXXXX",
"NOXNXOXNOXNOXOXOXNOXONON",
"OOONXNOXNONXONOXNXNNONOX",
"NNNNNNNNNNNNNNNNNNNNNNNN",
"OOOOOOOOOOOOOOOOOOOOOOOO",
"XXXXXXXXXXXXXXXXXXXXXXXX"]
for board in boards:
self.assertEqual(board, interface.construct_board(
interface.construct_grid(
interface.construct_board(
interface.construct_grid(board)
)
)
)
)

View file

@ -0,0 +1,186 @@
import unittest
from libraries import database
from libraries import game_data
from libraries import interface
from libraries import mechanics
from zulip_bots.simple_lib import SimpleStorage
class GridTest(unittest.TestCase):
def test_out_of_grid(self):
points = [[v, h] for h in range(7) for v in range(7)]
expected_outcomes = [True, False, False, True, False, False, True,
False, True, False, True, False, True, False,
False, False, True, True, True, False, False,
True, True, True, False, True, True, True,
False, False, True, True, True, False, False,
False, True, False, True, False, True, False,
True, False, False, True, False, False, True]
test_outcomes = [mechanics.is_in_grid(point[0], point[1]) for point in
points]
self.assertListEqual(test_outcomes, expected_outcomes)
def test_jump_and_grids(self):
points = [[0, 0, 1, 1], [1, 1, 2, 2], [2, 2, 3, 3], [0, 0, 0, 2],
[0, 0, 2, 2], [6, 6, 5, 4]]
expected_outcomes = [True, True, True, True, True, True]
test_outcomes = [
mechanics.is_jump(point[0], point[1], point[2], point[3]) for point
in points]
self.assertListEqual(test_outcomes, expected_outcomes)
def test_jump_special_cases(self):
points = [[0, 0, 0, 3], [0, 0, 3, 0], [6, 0, 6, 3], [4, 2, 6, 2],
[4, 3, 3, 4], [4, 3, 2, 2],
[0, 0, 0, 6], [0, 0, 1, 1], [0, 0, 2, 2], [3, 0, 3, 1],
[3, 0, 3, 2], [3, 1, 3, 0], [3, 1, 3, 2]]
expected_outcomes = [False, False, False, True, True, True, True, True,
True, False, True, False, False]
test_outcomes = [
mechanics.is_jump(point[0], point[1], point[2], point[3]) for point
in points]
self.assertListEqual(test_outcomes, expected_outcomes)
def test_not_populated_move(self):
grid = interface.construct_grid("XXXNNNOOOXXXNNNOOOXXXNNN")
moves = [[0, 0, 1, 1], [0, 3, 1, 3], [5, 1, 5, 3], [0, 0, 0, 3],
[0, 0, 3, 0]]
expected_outcomes = [True, True, False, False, False]
test_outcomes = [mechanics.is_empty(move[2], move[3], grid) for move in
moves]
self.assertListEqual(test_outcomes, expected_outcomes)
def test_legal_move(self):
grid = interface.construct_grid("XXXNNNOOONNNNNNOOONNNNNN")
presets = [[0, 0, 0, 3, "X", 1], [0, 0, 0, 6, "X", 2],
[0, 0, 3, 6, "X", 3], [0, 0, 2, 2, "X", 3]]
expected_outcomes = [False, False, True, False]
test_outcomes = [
mechanics.is_legal_move(preset[0], preset[1], preset[2], preset[3],
preset[4], preset[5], grid)
for preset in presets]
self.assertListEqual(test_outcomes, expected_outcomes)
def test_legal_put(self):
grid = interface.construct_grid("XXXNNNOOOXXXNNNOOOXXXNNN")
presets = [[0, 0, 1], [0, 3, 2], [0, 6, 3], [1, 1, 2], [1, 3, 1],
[1, 6, 1], [1, 5, 1]]
expected_outcomes = [False, False, False, False, True, False, True]
test_outcomes = [
mechanics.is_legal_put(preset[0], preset[1], grid, preset[2]) for
preset in presets]
self.assertListEqual(test_outcomes, expected_outcomes)
def test_legal_take(self):
grid = interface.construct_grid("XXXNNNOOOXXXNNNOOOXXXNNN")
presets = [[0, 0, "X", 1], [0, 1, "X", 1], [0, 0, "O", 1],
[0, 0, "O", 0], [0, 1, "O", 1], [2, 2, "X", 1],
[2, 3, "X", 1], [2, 4, "O", 1]]
expected_outcomes = [False, False, True, False, False, True, True,
False]
test_outcomes = [
mechanics.is_legal_take(preset[0], preset[1], preset[2], grid,
preset[3]) for preset in
presets]
self.assertListEqual(test_outcomes, expected_outcomes)
def test_own_piece(self):
grid = interface.construct_grid("XXXNNNOOOXXXNNNOOOXXXNNN")
presets = [[0, 0, "X"], [0, 0, "O"], [0, 6, "X"], [0, 6, "O"],
[1, 1, "X"], [1, 1, "O"]]
expected_outcomes = [True, False, True, False, False, False]
test_outcomes = [
mechanics.is_own_piece(preset[0], preset[1], preset[2], grid) for
preset in presets]
self.assertListEqual(test_outcomes, expected_outcomes)
def test_can_make_any_move(self):
grid = interface.construct_grid("NONNNNNNNNNNNNNNNNNNNNXN")
self.assertEqual(mechanics.check_moves("O", grid), True)
self.assertEqual(mechanics.check_moves("X", grid), True)
grid = interface.construct_grid("XXXXXXOXXXXXXXXXXXXXXXNX")
self.assertEqual(mechanics.check_moves("O", grid), False)
self.assertEqual(mechanics.check_moves("X", grid), True)
grid = interface.construct_grid("NXNNNNNNNNNNNNNNNNNNNNNN")
self.assertEqual(mechanics.check_moves("O", grid), False)
self.assertEqual(mechanics.check_moves("X", grid), True)
class HillsTest(unittest.TestCase):
def test_unchanged_hills(self):
grid = interface.construct_grid("XXXNNNOOOXXXXNNOOOXXXNNN")
hills_uid = "02356"
mechanics.move_man_legal(3, 4, 3, 5, grid)
updated_hills_uid = mechanics.get_hills_numbers(grid)
self.assertEqual(updated_hills_uid, hills_uid)
def test_no_diagonal_hills(self):
grid = interface.construct_grid("XXXNNXOONXXXXNNOOOXXXNNN")
hills_uid = "0356"
mechanics.move_man_legal(3, 4, 2, 4, grid)
updated_hills_uid = mechanics.get_hills_numbers(grid)
self.assertEqual(updated_hills_uid, hills_uid)
class PhaseTest(unittest.TestCase):
def test_new_game_phase(self):
storage = SimpleStorage()
merels = database.MerelsStorage(storage)
merels.create_new_game("test")
res = game_data.GameData(merels.get_game_data("test"))
self.assertEqual(res.get_phase(), 1)
merels.update_game(res.topic_name, "O", 5, 4,
"XXXXNNNOOOOONNNNNNNNNNNN", "03", 0)
res = game_data.GameData(merels.get_game_data("test"))
self.assertEqual(res.board, "XXXXNNNOOOOONNNNNNNNNNNN")
self.assertEqual(res.get_phase(), 2)
merels.update_game(res.topic_name, "X", 6, 4,
"XXXNNNNOOOOONNNNNNNNNNNN", "03", 0)
res = game_data.GameData(merels.get_game_data("test"))
self.assertEqual(res.board, "XXXNNNNOOOOONNNNNNNNNNNN")
self.assertEqual(res.get_phase(), 3)

View file

@ -0,0 +1,77 @@
"""
Most of the testing for the actual game are done in test_database
This is only to really verify the output of the chat
"""
from unittest import mock
import zulip_bots.bots.merels.merels
import zulip_bots.test_lib
class TestFollowUpBot(zulip_bots.test_lib.BotTestCase):
bot_name = "merels"
def test_no_command(self):
message = dict(
content='magic',
type='stream',
)
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 ''')