reversi/main.ipynb
2023-02-12 14:06:46 +01:00

1065 lines
82 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Deep Otello AI\n",
"\n",
"The game reversi is a very good game to apply deep learning methods to.\n",
"\n",
"Othello also known as reversi is a board game first published in 1883 by eiter Lewis Waterman or John W. Mollet in England (each one was denouncing the other as fraud).\n",
"It is a strickt turn based zero-sum game with a clear Markov chain and now hidden states like in card games with an unknown distribution of cards or unknown player allegiance.\n",
"There is like for the game go only one set of stones with two colors which is much easier to abstract than chess with its 6 unique pieces.\n",
"The game has a symmetrical game board wich allows to play with rotating the state around an axis to allow for a breaking of sequences or interesting ANN architectures, quadruple the data generation by simulation or interesting test cases where a symetry in turns should be observable if the AI reaches an \"objective\" policy."
]
},
{
"cell_type": "markdown",
"source": [
"\n",
"## Content\n",
"\n",
"* [The game rules](#the-game-rules) A short overview over the rules of the game.\n",
"* [Some common Otello strategies](#some-common-otello-strategies) introduces some easy approaches to a classic Otello AI and defines some behavioral expectations."
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "markdown",
"source": [
"\n",
"## The game rules\n",
"\n",
"Othello is played on a board with 8 x 8 fields for two player.\n",
"The board geometry is equal to a chess game.\n",
"The game is played with game stones that are black on one siede and white on the other.\n",
"![Othello game board example](reversi_example.png)\n",
"The player take turns.\n",
"A player places a stone with his or her color up on the game board.\n",
"The player can only place stones when he surrounds a number of stones with the opponents color with the new stone and already placed stones of his color.\n",
"Those surrounded stones can either be horizontally, vertically and/or diagonally be placed.\n",
"All stones thus surrounded will be flipped to be of the players color.\n",
"Turns are only possible if the player is also changing the color of the opponents stones. If a player can't act he is skipped.\n",
"The game ends if both players can't act. The player with the most stones wins.\n",
"If the score is counted in detail unclaimed fields go to the player with more stones of his or her color on the board.\n",
"The game begins with four stones places in the center of the game. Each player gets two. They are placed diagonally to each other.\n",
"\n",
"\n",
"![Startaufstellung.png](Startaufstellung.png)\n",
"\n",
"## Some common Othello strategies\n",
"\n",
"As can be easily understood the placement of stones and on the bord is always a careful balance of attack and defence.\n",
"If the player occupies huge homogenous stretches on the board it can be attacked easier.\n",
"The boards corners provide safety from wich occupied territory is impossible to loos but since it is only possible to reach the corners if the enemy is forced to allow this or calculates the cost of giving a stable base to the enemy it is difficult to obtain.\n",
"There are some text on otello computer strategies which implement greedy algorithms for reversi based on a modified score to each field.\n",
"Those different values are score modifiers for a traditional greedy algorithm.\n",
"If a players stone has captured such a filed the score reached is multiplied by the modifier.\n",
"The total score is the score reached by the player subtracted with the score of the enemy.\n",
"The scores change in the course of the game and converges against one. This gives some indications of what to expect from an Othello AI.\n",
"\n",
"![ComputerPossitionScore](computer-score.png)\n"
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%load_ext blackcellmagic"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Imports"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import abc\n",
"from typing import Final\n",
"from scipy.ndimage import binary_dilation\n",
"import matplotlib.pyplot as plt\n",
"from abc import ABC"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Constants"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"BOARD_SIZE: Final[int] = 8 # defines the board side length as 8\n",
"PLAYER: Final[int] = 1 # defines the number symbolising the player as 1\n",
"ENEMY: Final[int] = -1 # defines the number symbolising the enenemy as 1"
]
},
{
"cell_type": "markdown",
"source": [
"The directions array contains all the numerical offsets needed to move along one of the 8 directions in a 2 dimensional grid. This will allow an iteration over the game board.\n",
"![8-directions.png](8-directions.png \"Offset in 8 directions\")"
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "array([[-1, -1],\n [-1, 0],\n [-1, 1],\n [ 0, -1],\n [ 0, 1],\n [ 1, -1],\n [ 1, 0],\n [ 1, 1]])"
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"DIRECTIONS: Final[np.ndarray] = np.array(\n",
" [[i, j] for i in range(-1, 2) for j in range(-1, 2) if j != 0 or i != 0],\n",
" dtype=int,\n",
")\n",
"DIRECTIONS.setflags(write=False)\n",
"DIRECTIONS"
]
},
{
"cell_type": "markdown",
"source": [
"Another constant needed is the initial start square at the center of the board."
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "code",
"execution_count": 23,
"outputs": [
{
"data": {
"text/plain": "array([[-1, 1],\n [ 1, -1]])"
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"START_SQUARE: Final[np.ndarray] = np.array(\n",
" [[ENEMY, PLAYER], [PLAYER, ENEMY]], dtype=int\n",
")\n",
"START_SQUARE.setflags(write=False)\n",
"START_SQUARE"
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Creating new boards\n",
"\n",
"The first function implemented and tested is a function to generate the starting environment as a stack of games.\n",
"As described above I simply placed a 2 by 2 square in the center of an empty stack of boards."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "array([[ 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 0, 0, -1, 1, 0, 0, 0],\n [ 0, 0, 0, 1, -1, 0, 0, 0],\n [ 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 0, 0, 0, 0, 0, 0, 0]])"
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def get_new_games(number_of_games: int) -> np.ndarray:\n",
" \"\"\"Generates a stack of initialised game boards.\n",
"\n",
" Args:\n",
" number_of_games: The size of the board stack.\n",
"\n",
" Returns: The generates stack of games as a stack n x 8 x 8.\n",
"\n",
" \"\"\"\n",
" empty = np.zeros([number_of_games, BOARD_SIZE, BOARD_SIZE], dtype=int)\n",
" empty[:, 3:5, 3:5] = START_SQUARE\n",
" return empty\n",
"\n",
"\n",
"get_new_games(1)[0]"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"test_number_of_games = 3\n",
"assert get_new_games(test_number_of_games).shape == (\n",
" test_number_of_games,\n",
" BOARD_SIZE,\n",
" BOARD_SIZE,\n",
")\n",
"np.testing.assert_equal(\n",
" get_new_games(test_number_of_games).sum(axis=1),\n",
" np.zeros(\n",
" [\n",
" test_number_of_games,\n",
" 8,\n",
" ]\n",
" ),\n",
")\n",
"np.testing.assert_equal(\n",
" get_new_games(test_number_of_games).sum(axis=2),\n",
" np.zeros(\n",
" [\n",
" test_number_of_games,\n",
" 8,\n",
" ]\n",
" ),\n",
")\n",
"assert np.all(get_new_games(test_number_of_games)[:, 3:4, 3:4] != 0)\n",
"del test_number_of_games"
]
},
{
"cell_type": "markdown",
"source": [
"## Visualisation tools\n",
"\n",
"In this section a visualisation help was implemented for debugging of the game and a proper display of the results.\n",
"For this visualisation ChatGPT was used as a prompted code generator that was later reviewed and refactored by hand to integrate seamlessly into the project as a whole."
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "<Figure size 300x300 with 1 Axes>",
"image/png": "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\n"
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def plot_othello_board(board, ax=None):\n",
" \"\"\"Plots a single otello board.\n",
"\n",
" If a matplot axis object is given the board will be plotted into that axis. If not an axis object will be generated.\n",
"\n",
" Args:\n",
" board: The bord that should be plotted. Only a single games is allowed. A numpy array of the form 8x8 is expected.\n",
" ax: If needed the\n",
"\n",
" Returns:\n",
"\n",
" \"\"\"\n",
" plot_all = False\n",
" if ax is None:\n",
" fig_size = 3\n",
" plot_all = True\n",
" fig, ax = plt.subplots(figsize=(fig_size, fig_size))\n",
"\n",
" ax.set_facecolor(\"#006400\")\n",
" for i in range(BOARD_SIZE):\n",
" for j in range(BOARD_SIZE):\n",
" if board[i, j] == -1:\n",
" color = \"white\"\n",
" elif board[i, j] == 1:\n",
" color = \"black\"\n",
" else:\n",
" continue\n",
" ax.scatter(j, i, s=300 if plot_all else 150, c=color)\n",
" for i in range(-1, 8):\n",
" ax.axhline(i + 0.5, color=\"black\", lw=2)\n",
" ax.axvline(i + 0.5, color=\"black\", lw=2)\n",
" ax.set_xlim(-0.5, 7.5)\n",
" ax.set_ylim(7.5, -0.5)\n",
" ax.set_xticks(np.arange(8))\n",
" ax.set_xticklabels(list(\"ABCDEFGH\"))\n",
" ax.set_yticks(np.arange(8))\n",
" ax.set_yticklabels(list(\"12345678\"))\n",
" if plot_all:\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"\n",
"plot_othello_board(get_new_games(1)[0])"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def plot_othello_boards(boards: np.ndarray) -> None:\n",
" assert boards.shape[0] < 70\n",
" plots_per_row = 4\n",
" rows = int(np.ceil(boards.shape[0] / plots_per_row))\n",
" fig, axs = plt.subplots(rows, plots_per_row, figsize=(12, 3 * rows))\n",
" for game_index, ax in enumerate(axs.flatten()):\n",
" if game_index >= boards.shape[0]:\n",
" fig.delaxes(ax)\n",
" else:\n",
" plot_othello_board(boards[game_index], ax)\n",
" plt.tight_layout()\n",
" plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": "array([[[1, 1, 1],\n [1, 0, 1],\n [1, 1, 1]]])"
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"SURROUNDING: Final = np.array([[[1, 1, 1], [1, 0, 1], [1, 1, 1]]])\n",
"SURROUNDING"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "array([[[False, False, False, False, False, False, False, False],\n [False, False, False, False, False, False, False, False],\n [False, False, False, True, False, False, False, False],\n [False, False, True, False, False, False, False, False],\n [False, False, False, False, False, True, False, False],\n [False, False, False, False, True, False, False, False],\n [False, False, False, False, False, False, False, False],\n [False, False, False, False, False, False, False, False]]])"
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def recursive_steps(_array, rec_direction, rec_position, step_one=True) -> bool:\n",
" rec_position = rec_position + rec_direction\n",
" if np.any((rec_position >= BOARD_SIZE) | (rec_position < 0)):\n",
" return False\n",
" next_field = _array[tuple(rec_position.tolist())]\n",
" if next_field == 0:\n",
" return False\n",
" if next_field == -1:\n",
" return recursive_steps(_array, rec_direction, rec_position, step_one=False)\n",
" if next_field == 1:\n",
" return not step_one\n",
"\n",
"\n",
"def get_possible_turns(boards: np.ndarray) -> np.ndarray:\n",
" try:\n",
" _poss_turns = boards == 0\n",
" _poss_turns &= binary_dilation(boards == -1, SURROUNDING)\n",
" except RuntimeError as err:\n",
" print(boards)\n",
" print(boards == -1)\n",
" print(\"err\")\n",
" raise err\n",
" for game in range(boards.shape[0]):\n",
" for idx in range(BOARD_SIZE):\n",
" for idy in range(BOARD_SIZE):\n",
"\n",
" position = idx, idy\n",
" if _poss_turns[game, idx, idy]:\n",
" _poss_turns[game, idx, idy] = any(\n",
" recursive_steps(boards[game, :, :], direction, position)\n",
" for direction in DIRECTIONS\n",
" )\n",
" return _poss_turns\n",
"\n",
"\n",
"# %timeit get_possible_turns(get_new_games(10))\n",
"# %timeit get_possible_turns(get_new_games(100))\n",
"get_possible_turns(get_new_games(3))[:1]"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "(array([2, 2, 2]), array([2, 2, 2]))"
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def board_evaluation_final(array: np.ndarray):\n",
" score1, score2 = np.sum(array == 1, axis=(1, 2)), np.sum(array == -1, axis=(1, 2))\n",
" player_1_won = score1 > score2\n",
" player_2_won = score1 < score2\n",
" score1_final = 64 - score2[player_1_won]\n",
" score2_final = 64 - score1[player_2_won]\n",
" score1[player_1_won] = score1_final\n",
" score2[player_2_won] = score2_final\n",
" return score1, score2\n",
"\n",
"\n",
"def board_evaluation(array: np.ndarray):\n",
" score1, score2 = np.sum(array == 1, axis=(1, 2)), np.sum(array == -1, axis=(1, 2))\n",
" return score1, score2\n",
"\n",
"\n",
"board_evaluation(get_new_games(3))\n",
"board_evaluation_final(get_new_games(3))"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"def move_possible(board: np.ndarray, move: np.ndarray) -> bool:\n",
" if np.all(move == -1):\n",
" return not np.any(get_possible_turns(np.reshape(board, (1, 8, 8))))\n",
" return any(\n",
" recursive_steps(board[:, :], direction, move) for direction in DIRECTIONS\n",
" )\n",
"\n",
"\n",
"assert move_possible(get_new_games(1)[0], np.array([2, 3])) is True\n",
"assert move_possible(get_new_games(1)[0], np.array([3, 2])) is True\n",
"assert move_possible(get_new_games(1)[0], np.array([2, 2])) is False\n",
"assert move_possible(np.zeros((8, 8)), np.array([3, 2])) is False\n",
"assert move_possible(np.ones((8, 8)) * 1, np.array([-1, -1])) is True\n",
"assert move_possible(np.ones((8, 8)) * -1, np.array([-1, -1])) is True\n",
"assert move_possible(np.ones((8, 8)) * 0, np.array([-1, -1])) is True"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"def moves_possible(boards: np.ndarray, moves: np.ndarray) -> np.ndarray:\n",
" arr_moves_possible = np.zeros(boards.shape[0], dtype=bool)\n",
" for game in range(boards.shape[0]):\n",
" if np.all(moves[game] == -1):\n",
" arr_moves_possible[game] = not np.any(\n",
" get_possible_turns(np.reshape(boards[game], (1, 8, 8)))\n",
" )\n",
" else:\n",
" arr_moves_possible[game] = any(\n",
" recursive_steps(boards[game, :, :], direction, moves[game])\n",
" for direction in DIRECTIONS\n",
" )\n",
" return arr_moves_possible\n",
"\n",
"\n",
"np.testing.assert_array_equal(\n",
" moves_possible(np.ones((3, 8, 8)) * 1, np.array([[-1, -1]] * 3)),\n",
" np.array([True] * 3),\n",
")\n",
"\n",
"np.testing.assert_array_equal(\n",
" moves_possible(get_new_games(3), np.array([[2, 3], [3, 2], [3, 2]])),\n",
" np.array([True] * 3),\n",
")\n",
"np.testing.assert_array_equal(\n",
" moves_possible(get_new_games(3), np.array([[2, 2], [1, 1], [0, 0]])),\n",
" np.array([False] * 3),\n",
")\n",
"np.testing.assert_array_equal(\n",
" moves_possible(np.ones((3, 8, 8)) * -1, np.array([[-1, -1]] * 3)),\n",
" np.array([True] * 3),\n",
")\n",
"np.testing.assert_array_equal(\n",
" moves_possible(np.zeros((3, 8, 8)), np.array([[-1, -1]] * 3)),\n",
" np.array([True] * 3),\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "array([[ 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 0, 0, 1, 0, 0, 0, 0],\n [ 0, 0, 0, 1, 1, 0, 0, 0],\n [ 0, 0, 0, 1, -1, 0, 0, 0],\n [ 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 0, 0, 0, 0, 0, 0, 0]])"
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class InvalidTurn(ValueError):\n",
" pass\n",
"\n",
"\n",
"def do_moves(boards: np.ndarray, moves: np.ndarray) -> np.ndarray:\n",
" def _do_directional_move(\n",
" board: np.ndarray, rec_move: np.ndarray, rev_direction, step_one=True\n",
" ) -> bool:\n",
" rec_position = rec_move + rev_direction\n",
" if np.any((rec_position >= 8) | (rec_position < 0)):\n",
" return False\n",
" next_field = board[tuple(rec_position.tolist())]\n",
" if next_field == 0:\n",
" return False\n",
" if next_field == 1:\n",
" return not step_one\n",
" if next_field == -1:\n",
" if _do_directional_move(board, rec_position, rev_direction, step_one=False):\n",
" board[tuple(rec_position.tolist())] = 1\n",
" return True\n",
" return False\n",
"\n",
" def _do_move(_board: np.ndarray, move: np.ndarray) -> None:\n",
" if np.all(move == -1):\n",
" return\n",
" if _board[tuple(move.tolist())] != 0:\n",
" raise InvalidTurn\n",
" action = False\n",
" for direction in DIRECTIONS:\n",
" if _do_directional_move(_board, move, direction):\n",
" action = True\n",
" if not action:\n",
" raise InvalidTurn()\n",
" _board[tuple(move.tolist())] = 1\n",
"\n",
" boards = boards.copy()\n",
" for game in range(boards.shape[0]):\n",
" _do_move(boards[game], moves[game])\n",
" return boards\n",
"\n",
"\n",
"do_moves(get_new_games(10), np.array([[2, 3]] * 10))[0]"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"class GamePolicy(ABC):\n",
"\n",
" IMPOSSIBLE: np.ndarray = np.array([-1, -1], dtype=int)\n",
"\n",
" @property\n",
" @abc.abstractmethod\n",
" def policy_name(self) -> str:\n",
" raise NotImplementedError()\n",
"\n",
" @abc.abstractmethod\n",
" def internal_policy(self, boards: np.ndarray) -> np.ndarray:\n",
" raise NotImplementedError()\n",
"\n",
" def get_policy(self, boards: np.ndarray) -> np.ndarray:\n",
" policies = self.internal_policy(boards)\n",
" possible_turns = get_possible_turns(boards)\n",
" policies[possible_turns == False] = -1.0\n",
" max_indices = [\n",
" np.unravel_index(policy.argmax(), policy.shape) for policy in policies\n",
" ]\n",
" policy_vector = np.array(max_indices)\n",
"\n",
" no_turn_possible = np.all(policy_vector == 0, 1) & (policies[:, 0, 0] == -1.0)\n",
"\n",
" policy_vector[no_turn_possible] = GamePolicy.IMPOSSIBLE\n",
" return policy_vector"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"class RandomPolicy(GamePolicy):\n",
" @property\n",
" def policy_name(self) -> str:\n",
" return \"random\"\n",
"\n",
" def internal_policy(self, boards: np.ndarray) -> np.ndarray:\n",
" random_values = np.random.rand(*boards.shape)\n",
" return random_values\n",
" # return np.argmax(random_values, (1, 2))\n",
"\n",
"\n",
"rnd_policy = RandomPolicy()\n",
"assert rnd_policy.policy_name == \"random\"\n",
"rnd_policy_result = rnd_policy.get_policy(get_new_games(1))\n",
"assert np.any((5 >= rnd_policy_result) & (rnd_policy_result >= 3))"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"123 ms ± 4.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n"
]
},
{
"data": {
"text/plain": "array([[[0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n ...,\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0]],\n\n [[0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n ...,\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0]],\n\n [[0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n ...,\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0]],\n\n ...,\n\n [[0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n ...,\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0]],\n\n [[0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n ...,\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0]],\n\n [[0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n ...,\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0],\n [0, 0, 0, ..., 0, 0, 0]]])"
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def single_turn(\n",
" current_boards: np, policy: GamePolicy\n",
") -> tuple[np.ndarray, np.ndarray]:\n",
" policy_results = policy.get_policy(current_boards)\n",
"\n",
" assert np.all(moves_possible(current_boards, policy_results)), (\n",
" current_boards[(moves_possible(current_boards, policy_results) == False)],\n",
" policy_results[(moves_possible(current_boards, policy_results) == False)],\n",
" np.where(moves_possible(current_boards, policy_results) == False),\n",
" )\n",
"\n",
" return do_moves(current_boards, policy_results), policy_results\n",
"\n",
"\n",
"%timeit single_turn(get_new_games(100), RandomPolicy())\n",
"single_turn(get_new_games(100), RandomPolicy())[0]"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"10.8 s ± 339 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
]
},
{
"data": {
"text/plain": "(array([[[[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n ...,\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]]],\n \n \n [[[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n ...,\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]]],\n \n \n [[[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 1., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n ...,\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 1., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]],\n \n [[ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 1., 0., 0.],\n ...,\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.],\n [ 0., 0., 0., ..., 0., 0., 0.]]],\n \n \n ...,\n \n \n [[[-1., -1., -1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., 1., 1., ..., 1., 1., -1.],\n ...,\n [ 1., 1., 1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.]],\n \n [[ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [-1., -1., -1., ..., -1., -1., 1.],\n ...,\n [-1., 1., 1., ..., 1., 1., 1.],\n [-1., -1., 1., ..., 1., 1., 1.],\n [-1., -1., -1., ..., -1., 1., 1.]],\n \n [[ 0., -1., -1., ..., -1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.],\n ...,\n [ 1., -1., 1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.]],\n \n ...,\n \n [[ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., -1., 1., ..., 1., 1., 1.],\n ...,\n [ 1., -1., 1., ..., 1., -1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.]],\n \n [[ 1., 1., 1., ..., -1., -1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., -1., 1., ..., 1., -1., -1.],\n ...,\n [ 1., -1., 1., ..., -1., 1., -1.],\n [ 1., 1., -1., ..., -1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.]],\n \n [[ 1., -1., -1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n ...,\n [ 1., -1., 1., ..., 1., 1., 1.],\n [ 1., -1., -1., ..., 1., 1., 1.],\n [ 1., 1., 1., ..., 1., 1., -1.]]],\n \n \n [[[-1., -1., -1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., 1., 1., ..., 1., 1., -1.],\n ...,\n [ 1., 1., 1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.]],\n \n [[ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [-1., -1., -1., ..., -1., -1., 1.],\n ...,\n [-1., 1., 1., ..., 1., 1., 1.],\n [-1., -1., 1., ..., 1., 1., 1.],\n [-1., -1., -1., ..., -1., 1., 1.]],\n \n [[ 0., -1., -1., ..., -1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.],\n ...,\n [ 1., -1., 1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.]],\n \n ...,\n \n [[ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., -1., 1., ..., 1., 1., 1.],\n ...,\n [ 1., -1., 1., ..., 1., -1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.]],\n \n [[ 1., 1., 1., ..., -1., -1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., -1., 1., ..., 1., -1., -1.],\n ...,\n [ 1., -1., 1., ..., -1., 1., -1.],\n [ 1., 1., -1., ..., -1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.]],\n \n [[ 1., -1., -1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n ...,\n [ 1., -1., 1., ..., 1., 1., 1.],\n [ 1., -1., -1., ..., 1., 1., 1.],\n [ 1., 1., 1., ..., 1., 1., -1.]]],\n \n \n [[[-1., -1., -1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., 1., 1., ..., 1., 1., -1.],\n ...,\n [ 1., 1., 1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.]],\n \n [[ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [-1., -1., -1., ..., -1., -1., 1.],\n ...,\n [-1., 1., 1., ..., 1., 1., 1.],\n [-1., -1., 1., ..., 1., 1., 1.],\n [-1., -1., -1., ..., -1., 1., 1.]],\n \n [[ 0., -1., -1., ..., -1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.],\n ...,\n [ 1., -1., 1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.]],\n \n ...,\n \n [[ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., -1., 1., ..., 1., 1., 1.],\n ...,\n [ 1., -1., 1., ..., 1., -1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.]],\n \n [[ 1., 1., 1., ..., -1., -1., 1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n [ 1., -1., 1., ..., 1., -1., -1.],\n ...,\n [ 1., -1., 1., ..., -1., 1., -1.],\n [ 1., 1., -1., ..., -1., -1., -1.],\n [ 1., -1., -1., ..., -1., -1., -1.]],\n \n [[ 1., -1., -1., ..., -1., -1., -1.],\n [ 1., 1., 1., ..., 1., -1., -1.],\n [ 1., 1., 1., ..., 1., 1., 1.],\n ...,\n [ 1., -1., 1., ..., 1., 1., 1.],\n [ 1., -1., -1., ..., 1., 1., 1.],\n [ 1., 1., 1., ..., 1., 1., -1.]]]]),\n array([[[ 4., 2.],\n [ 4., 2.],\n [ 3., 5.],\n ...,\n [ 5., 3.],\n [ 4., 2.],\n [ 3., 5.]],\n \n [[ 5., 4.],\n [ 3., 2.],\n [ 2., 5.],\n ...,\n [ 5., 2.],\n [ 3., 2.],\n [ 2., 5.]],\n \n [[ 4., 5.],\n [ 2., 4.],\n [ 5., 3.],\n ...,\n [ 5., 1.],\n [ 2., 2.],\n [ 5., 3.]],\n \n ...,\n \n [[-1., -1.],\n [-1., -1.],\n [-1., -1.],\n ...,\n [-1., -1.],\n [-1., -1.],\n [-1., -1.]],\n \n [[-1., -1.],\n [-1., -1.],\n [-1., -1.],\n ...,\n [-1., -1.],\n [-1., -1.],\n [-1., -1.]],\n \n [[-1., -1.],\n [-1., -1.],\n [-1., -1.],\n ...,\n [-1., -1.],\n [-1., -1.],\n [-1., -1.]]]))"
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"SIMULATE_TURNS = 70\n",
"\n",
"\n",
"def simulate_game(\n",
" nr_of_games: int,\n",
" policies: tuple[GamePolicy, GamePolicy],\n",
") -> tuple[np.ndarray, np.ndarray]:\n",
"\n",
" board_history_stack = np.zeros((SIMULATE_TURNS, nr_of_games, 8, 8))\n",
" action_history_stack = np.zeros((SIMULATE_TURNS, nr_of_games, 2))\n",
" current_boards = get_new_games(nr_of_games)\n",
" for turn_index in range(SIMULATE_TURNS):\n",
" policy_index = turn_index % 2\n",
" policy = policies[policy_index]\n",
" board_history_stack[turn_index] = current_boards\n",
" if policy_index == 0:\n",
" current_boards = current_boards * -1\n",
" current_boards, action_taken = single_turn(current_boards, policy)\n",
" action_history_stack[turn_index] = action_taken\n",
"\n",
" if policy_index == 0:\n",
" current_boards = current_boards * -1\n",
"\n",
" return board_history_stack, action_history_stack\n",
"\n",
"\n",
"%timeit simulate_game(100, (RandomPolicy(), RandomPolicy()))\n",
"simulate_game(10, (RandomPolicy(), RandomPolicy()))"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"\n",
"def create_test_game():\n",
" test_array = [\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 2, 0, 0, 0],\n",
" [0, 0, 0, 2, 1, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 2, 0, 0, 0],\n",
" [0, 0, 0, 2, 1, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 2, 0, 0, 0],\n",
" [0, 0, 1, 1, 1, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 2, 0, 0, 0],\n",
" [0, 0, 2, 1, 1, 0, 0, 0],\n",
" [0, 2, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 2, 0, 0, 0],\n",
" [0, 0, 2, 1, 1, 0, 0, 0],\n",
" [0, 2, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 2, 0, 0, 0],\n",
" [0, 0, 2, 1, 1, 0, 0, 0],\n",
" [0, 2, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 2, 0, 0, 0],\n",
" [0, 0, 2, 2, 2, 2, 0, 0],\n",
" [0, 2, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 0, 0, 0],\n",
" [0, 0, 0, 1, 1, 1, 0, 0],\n",
" [0, 0, 2, 2, 2, 2, 0, 0],\n",
" [0, 2, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 2, 0, 0],\n",
" [0, 0, 0, 1, 2, 2, 0, 0],\n",
" [0, 0, 2, 2, 2, 2, 0, 0],\n",
" [0, 2, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 2, 0, 0],\n",
" [0, 0, 0, 1, 2, 2, 0, 0],\n",
" [0, 0, 2, 2, 1, 2, 0, 0],\n",
" [0, 2, 0, 0, 0, 1, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 2, 0, 0],\n",
" [0, 0, 0, 1, 2, 2, 0, 0],\n",
" [0, 0, 2, 2, 1, 2, 0, 0],\n",
" [0, 2, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 2, 0, 0],\n",
" [0, 0, 0, 1, 2, 2, 0, 0],\n",
" [0, 1, 1, 1, 1, 2, 0, 0],\n",
" [0, 2, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 2, 0, 0],\n",
" [0, 0, 0, 1, 2, 2, 0, 0],\n",
" [2, 2, 2, 2, 2, 2, 0, 0],\n",
" [0, 2, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 2, 0, 0],\n",
" [0, 0, 0, 1, 1, 1, 1, 0],\n",
" [2, 2, 2, 2, 2, 2, 0, 0],\n",
" [0, 2, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 0, 2, 0, 0],\n",
" [0, 0, 0, 1, 1, 1, 1, 0],\n",
" [2, 2, 2, 1, 2, 2, 0, 0],\n",
" [0, 2, 0, 1, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 0, 0, 0],\n",
" [0, 0, 0, 2, 2, 2, 0, 0],\n",
" [0, 0, 0, 2, 2, 1, 1, 0],\n",
" [2, 2, 2, 1, 2, 2, 0, 0],\n",
" [0, 2, 0, 1, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 1, 0, 0],\n",
" [0, 0, 0, 2, 2, 1, 0, 0],\n",
" [0, 0, 0, 2, 2, 1, 1, 0],\n",
" [2, 2, 2, 1, 2, 2, 0, 0],\n",
" [0, 2, 0, 1, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 1, 0, 0],\n",
" [0, 0, 0, 2, 2, 2, 2, 0],\n",
" [0, 0, 0, 2, 2, 2, 1, 0],\n",
" [2, 2, 2, 1, 2, 2, 0, 0],\n",
" [0, 2, 0, 1, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 1, 0, 0],\n",
" [0, 0, 0, 2, 1, 2, 2, 0],\n",
" [0, 0, 0, 2, 2, 1, 1, 0],\n",
" [2, 2, 2, 1, 1, 1, 1, 0],\n",
" [0, 2, 0, 1, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 1, 0, 0],\n",
" [0, 0, 0, 2, 1, 2, 2, 0],\n",
" [0, 0, 0, 2, 2, 1, 2, 0],\n",
" [2, 2, 2, 2, 2, 2, 2, 2],\n",
" [0, 2, 0, 1, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 2, 1, 0, 1, 0, 0],\n",
" [0, 0, 0, 2, 1, 2, 2, 0],\n",
" [0, 0, 0, 2, 1, 1, 2, 0],\n",
" [2, 2, 2, 2, 1, 2, 2, 2],\n",
" [0, 2, 0, 1, 1, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" [\n",
" [0, 0, 0, 0, 2, 0, 0, 0],\n",
" [0, 0, 2, 2, 0, 2, 0, 0],\n",
" [0, 0, 0, 2, 1, 2, 2, 0],\n",
" [0, 0, 0, 2, 1, 1, 2, 0],\n",
" [2, 2, 2, 2, 1, 2, 2, 2],\n",
" [0, 2, 0, 1, 1, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 2, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0],\n",
" ],\n",
" ]\n",
" test_array = np.array(test_array)\n",
"\n",
" # swapp 2 by one. 2 was only there for homogenous formating and easier readability while coading.\n",
" test_array[test_array == 2] = -1\n",
" assert np.all(\n",
" np.count_nonzero(test_array, axis=(1, 2))\n",
" == np.arange(4, 4 + test_array.shape[0])\n",
" )\n",
"\n",
" # validated that only one stone is added per turn\n",
" zero_array = test_array == 0\n",
" diff = zero_array != np.roll(zero_array, 1, axis=0)\n",
" turns = np.where(diff[1:])\n",
" arr = np.array(turns)[0]\n",
" assert len(arr) == len(set(arr))\n",
"\n",
" return test_array"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "<Figure size 1200x300 with 3 Axes>",
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA30AAAEiCAYAAABNzbuyAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/P9b71AAAACXBIWXMAAA9hAAAPYQGoP6dpAAA+dElEQVR4nO3dfXBcV33G8WdXErLBkrBNRGwkhySS7JJImQQwTTxWeZMbJ3JCh4lbMAUZSlswENDWAXcGGuoSQ7A0aZvgtqmR0iEhmA5hHHsIdnixRUxwTACLArFUmkaunarTOFopjlVJe/vHrmT5RdLu3Zfzu3e/n5mdrKXdPQ/n3vuwZ3d1N+J5nicAAAAAQChFXQcAAAAAAOQPiz4AAAAACDEWfQAAAAAQYiz6AAAAACDEWPQBAAAAQIix6AMAAACAEGPRBwAAAAAhxqIPAAAAAEKstNADJhIJnThxQhUVFYpEIoUeHoBBnudpeHhYS5cuVTTq7rUo+gnAxVjoKPoJwMWk208FX/SdOHFCtbW1hR4WQAAMDAyopqbG2fj0E4DZuOwo+gnAbObqp4Iv+ioqKs7+Y36hR5f08rTrLsYnAxmsZXA9/rQM5/SDA877STK1PZxlcD0+GcgwQwaXHUU/kcHM+GQwmWGufir4om/qIwnzJW0o9OiSHpJ0WtIrJb3XwfhkIIO1DK7Hl6QHJb0s5x9Zct5Pko3t4TqD6/HJQIbzGego+okMZsYng60MafYTJ3IBAAAAgBBj0QcAAAAAIcaiDwAAAABCrOB/0wcAAACg8GoX1arthjbVV9erYl6Fhs8Mq2+wT92HujXwwoDreMgjFn0AAABAiDU3NCvWElNrU6sSXkKSFI1GlUgkr9+57k49evRRdezrUE9fj8uoyBM+3gkAAACEVGxNTAc2H9DaxrWKRqMqLSlVaUmpopFp16NR3dR4kw7ecVDtLe2uIyMPWPQBAAAAIdTe0q7tt22XJJWVlM1628nfd6zvYOEXQiz6AAAAgJBpbmhWx/oOX/ftWN+h1fWrc5wILmW86Dt48KDWrVunpUuXKhKJ6Nvf/nYeYgFA5ugnAFbRTyi0WEtMYxNjvu47NjGm9jW82xcmGS/6XnrpJV1zzTW677778pEHAHyjnwBYRT+hkGoX1aq1qXXOj3TOpKykTLc03aKahTU5TgZXMj5759q1a7V27dp8ZAGArNBPAKyin1BIbTe0KeElFM3iL7kSXkIbV23U1j1bc5gMruT9KxtGR0c1Ojo69e94PJ7vIQEgLfQTAKvoJ2Sjvro+68fw5Kmuui4HaWBB3k/ksm3bNlVVVU1damtr8z0kAKSFfgJgFf2EbFTMq1A0mt3T/JJoiSrnVeYoEVzL+6Jvy5YtGhoamroMDAzke0gASAv9BMAq+gnZGD4zPPXF635NJCYUP8M7zGGR9493lpeXq7y8PN/DAEDG6CcAVtFPyEbfYF/WjxFRRP2D/TlIAwv4nj4AAAAgRLoPdSsaye5pfjQSVdcTXTlKBNcyfqdvZGRE/f1nV/3/8R//oZ///OdatGiRli1bltNwAJAJ+gmAVfQTCmnghQHtObpHaxvX+vrahrGJMe3t3avjp47nIR1cyPglgCNHjujaa6/VtddeK0lqb2/Xtddeq8997nM5DwcAmaCfAFhFP6HQtu/b7vt7+kqiJerc15njRHAp43f63vrWt8rzvHxkAYCs0E8ArKKfUGg9fT2K7YqpY31Hxvfd/M3N6unryUMquMLf9AEAAAAh1Lm/U7FdMUnJj2zOZvL3sV0xde7nXb6wYdEHAAAAhFTn/k41392svb17lUgkND4xrvGJcSW8hMYmxpLXEwnt7d2r5rubWfCFVN6/sgEAAACAOz19Perp61HNwhptXLVRddV1qpxXqfiZuPoH+9X1RBcnbQk5Fn0AAABAETh+6ri27tnqOgYc4OOdAAAAABBiLPoAAAAAIMRY9AEAAABAiLHoAwAAAIAQi3gF/qbQeDyuqqqq5D9eWciRU16W5EmKSJrvYHwykMFaBtfjS9Lp5H+GhoZUWVnpKISBfpJsbA/XGVyPTwYynM9AR9FPZDAzPhlsZUizn9yevfO0w7E9x+OTgQzWMrge3xrXc2Fhe7jO4Hp8MpDBKtfzYGFbkMH9+GSwlWEObhd9vNNHBjK4z+B6fMlmUfJKenHvk2Qgw3TWOop+Ku4Mrscng60MafaTu0XffEnvdTDuQ0pOjqvxyUAGaxlcjy9JDypZnFYU8/5gIYPr8clAhvNZ6qhi3xZkcD8+GWxlSLOfOJELAAAAAIQYiz4AAAAACDEWfQAAAAAQYm5P5IKM1S6qVdsNbaqvrlfFvAoNnxlW32Cfug91a+CFgcKEGJF0TFJc0pikMkmVkhokLShMBOeYA+AC9JMRzAFwAQv9ZCGDBcyDGyz6AqK5oVmxlpham1qV8BKSpGg0qkQief3OdXfq0aOPqmNfh3r6evIT4qSkXknPKXmWIunsGYsk6WlJyyQ1SlqSnwjOMQfABegnI5gD4AIW+slCBguYB7f4eGcAxNbEdGDzAa1tXKtoNKrSklKVlpQqGpl2PRrVTY036eAdB9Xe0p7bAJ6ko5L2ShqY9jPvItcHUrc7Ou1nYcAcABdFPxnAHAAX5byfjGSwgHlwj0Wfce0t7dp+23ZJUllJ2ay3nfx9x/qO3B4svZIOp67P9SRh8veHU/cLC+YAuAD9ZARzAFzAQj9ZyGAB82ADiz7Dmhua1bG+w9d9O9Z3aHX96uxDnNTZJxOZOpy6f9AxB8AF6CcjmAPgAhb6yUIGC5gHOzJa9G3btk1vfvObVVFRoerqar3rXe/SM888k69sRS/WEtPYxJiv+45NjKl9TQ5eIenV2b8HyVRE4XglmTkIBPqpsOgnI5iDwKCjCsdCP1nIYAHzYEdGi74DBw5o06ZNevLJJ7V//36NjY1pzZo1eumll/KVr2jVLqpVa1PrnG+Dz6SspEy3NN2imoU1/kOMKHlCAL9/9+Gl7j/iP4JzzEFg0E+FQz8ZwRwECh1VGBb6yUIGC5gHWzJa9D322GNqa2vTVVddpWuuuUbd3d167rnn9NOf/jRf+YpW2w1tU2c28ivhJbRx1Ub/D3BM/l9BnhRJPU5QMQeBQT8VDv1kBHMQKHRUYVjoJwsZLGAebMnqKxuGhoYkSYsWLZrxNqOjoxodHZ36dzwez2bIolFfXZ/1Y3jyVFdd5/8BcrWpgrzJmYPAop/yh34ygjkItLk6in7yx0I/WchgAfNgi+8TuSQSCX3yk5/UqlWrdPXVV894u23btqmqqmrqUltb63fIolIxr0LRaHbn2SmJlqhyXqX/BxhT9qf09lKPE1TMQSDRT/lFPxnBHARWOh1FP/ljoZ8sZLCAebDF95bYtGmTfvnLX+rhhx+e9XZbtmzR0NDQ1GVgYGDW2yNp+Mzw1JdV+jWRmFD8TBavDJYpNx8d8vdRbhuYg0Cin/KLfjKCOQisdDqKfvLHQj9ZyGAB82CLr493fuxjH9OePXt08OBB1dTM/seV5eXlKi8v9xWumPUN9mX9GBFF1D/Y7/8BcvXCSpBfoGEOAod+yj/6yQjmIJDS7Sj6yR8L/WQhgwXMgy0ZvdPneZ4+9rGP6ZFHHtH3v/99XX755fnKVfS6D3UrGsnuLfFoJKquJ7r8P0CDcvPRoYYsH8Ml5iAw6KfCoZ+MYA4ChY4qDAv9ZCGDBcyDLRltiU2bNulrX/uaHnroIVVUVOj555/X888/r5dffjlf+YrWwAsD2nN0T1bfbbL76G4dP3Xcf4gFkpYpu++AWpZ6nKBiDgKDfioc+skI5iBQ6KjCsNBPFjJYwDzYktGib8eOHRoaGtJb3/pWLVmyZOryjW98I1/5itr2fdt9f7dJSbREnfs6sw/RqOy+A6ox+wjOMQeBQD8VFv1kBHMQGHRU4VjoJwsZLGAe7Mj4450Xu7S1teUpXnHr6etRbFfM1303f3Ozevp6sg+xRNJKn/ddmbp/0DEHgUA/FRb9ZARzEBh0VOFY6CcLGSxgHuzI7oO2yLvO/Z1TB8tcb49P/j62K6bO/Tl8ZaRRZ59UzPUxosnfr1S4XkFmDoAL0E9GMAfABSz0k4UMFjAPNrDoC4DO/Z1qvrtZe3v3KpFIaHxiXOMT40p4CY1NjCWvJxLa27tXzXc35/4giUhqknSzpNppP4tc5Hpt6nZNyv5U4pYwB8BF0U8GMAfARTnvJyMZLGAe3PP1lQ0ovJ6+HvX09ahmYY02rtqouuo6Vc6rVPxMXP2D/ep6oiv/f+i6JHUZkXRMUlzJL/UtU/KU3w0K/wkBmAPgAvSTEcwBcAEL/WQhgwXMg1ss+gLm+Knj2rpnq9sQCyRd5zaCc8wBcAH6yQjmALiAhX6ykMEC5sENPt4JAAAAACHGog8AAAAAQoxFHwAAAACEGIs+AAAAAAixiOd5XiEHjMfjqqqqSv7jlYUcOeVlSZ6Sp6qe72B8MpDBWgbX40vS6eR/hoaGVFlZ6SiEgX6SbGwP1xlcj08GMpzPQEfRT2QwMz4ZbGVIs5/cnr3ztMOxPcfjk4EM1jK4Ht8a13NhYXu4zuB6fDKQwSrX82BhW5DB/fhksJVhDm4XfbzTRwYyuM/genzJZlHySnpx75NkIMN01jqKfiruDK7HJ4OtDGn2k7tF33xJ73Uw7kNKTo6r8clABmsZXI8vSQ8qWZxWFPP+YCGD6/HJQIbzWeqoYt8WZHA/PhlsZUiznziRCwAAAACEGIs+AAAAAAgxFn0AAAAAEGJuT+SCQKpdVKu2G9pUX12vinkVGj4zrL7BPnUf6tbACwP5DzAi6ZikuKQxSWWSKiU1SFqQ/+ElA3NgJANgjYnjwnFHWZgDCxkAa0wcFzyHMpOh0Fj0IW3NDc2KtcTU2tSqhJeQJEWjUSUSyet3rrtTjx59VB37OtTT15P7ACcl9Up6TsmzJElnz5gkSU9LWiapUdKS3A8vGZgDIxkAa0wcF447ysIcWMgAWGPiuOA5lJkMrvDxTqQltiamA5sPaG3jWkWjUZWWlKq0pFTRyLTr0ahuarxJB+84qPaW9twN7kk6KmmvpIFpP/Mucn0gdbuj036WI07nwFAGwBrnx4WBjnI+B0YyANY4Py4M9JNkYB6MZHCJRR/m1N7Sru23bZcklZWUzXrbyd93rO/I3cHSK+lw6vpcJTT5+8Op++WI8zkwkgGwxsRx4bijLMyBhQyANSaOC55DmcngWkaLvh07dqipqUmVlZWqrKzU9ddfr+985zv5ygYDmhua1bG+w9d9O9Z3aHX96uwCnNTZssrU4dT9s+R8DoxksI5+Kj4mjgvHHWVhDixkCAI6qriYOC54DmUmgwUZLfpqamr0xS9+UT/96U915MgRvf3tb9ett96qf/u3f8tXPjgWa4lpbGLM133HJsbUvibLV0h6dfbz5pmKKCevVDmfAyMZrKOfio+J48JxR1mYAwsZgoCOKi4mjgueQ5nJYEFGi75169bppptuUn19vRoaGvSFL3xBCxYs0JNPPpmvfHCodlGtWpta53wbfCZlJWW6pekW1Sys8RdgRMk/OPb7uXIvdf8Rn/eXgTkwkiEI6KfiYuK4cNxRFubAQoagoKOKh4njgudQZjJY4ftv+iYmJvTwww/rpZde0vXXX5/LTDCi7Ya2qTMb+ZXwEtq4aqO/Ox+T/1eoJkVSj+OT8zkwkiFo6KfwM3FcOO4oC3NgIUMQ0VHhZuK44DmUmQxWZPyVDb29vbr++ut15swZLViwQI888oje8IY3zHj70dFRjY6OTv07Ho/7S4qCq6+uz/oxPHmqq67zd+dc7SpZPI7zOTCSISjop+Jh4rhw3FEW5sBChiDJpKPop+AycVzwHMpMBisyfqdv+fLl+vnPf66f/OQn+shHPqIPfOAD+tWvfjXj7bdt26aqqqqpS21tbVaBUTgV8yoUjWZ3gteSaIkq51X6u/OYsj9lsJd6HJ+cz4GRDEFBPxUPE8eF446yMAcWMgRJJh1FPwWXieOC51BmMliR8Sy84hWvUF1dnd74xjdq27Ztuuaaa/S3f/u3M95+y5YtGhoamroMDITzW+7DaPjM8NSXVfo1kZhQ/IzPl4nKlJuPJvj7GLckA3NgJENQ0E/Fw8Rx4bijLMyBhQxBkklH0U/BZeK44DmUmQxWZPzxzvMlEolzPn5wvvLycpWXl2c7DBzoG+zL+jEiiqh/sN/fnXP1okoWj+N8DoxkCCr6KbxMHBeOO8rCHFjIEGSzdRT9FFwmjgueQ5nJYEVG7/Rt2bJFBw8e1LPPPqve3l5t2bJFP/zhD7Vhw4Z85YND3Ye6FY1k95Z4NBJV1xNd/u7coNx8NKHB/92dz4GRDEFAPxUXE8eF446yMAcWMgQFHVU8TBwXPIcyk8GKjGZhcHBQ73//+7V8+XK94x3v0FNPPaXvfve7amlpyVc+ODTwwoD2HN2T1Xeb7D66W8dPHfcXYIGkZcruO2aWpR7HJ+dzYCRDENBPxcXEceG4oyzMgYUMQUFHFQ8TxwXPocxksCKjRd/OnTv17LPPanR0VIODg3r88ccpq5Dbvm+77+82KYmWqHNfZ3YBGpXdd8w0Zje8ZGAOjGSwjn4qPiaOC8cdZWEOLGQIAjqquJg4LngOZSaDBdm934nQ6+nrUWxXzNd9N39zs3r6erILsETSSp/3XZm6f5acz4GRDIA1Jo4Lxx1lYQ4sZACsMXFc8BzKTAYLWPRhTp37O6cOlrneHp/8fWxXTJ37c/TKSKPOltZcH1OY/P1K5eQVqknO58BIBsAaE8eF446yMAcWMgDWmDgueA5lJoNrLPqQls79nWq+u1l7e/cqkUhofGJc4xPjSngJjU2MJa8nEtrbu1fNdzfn9iCJSGqSdLOk2mk/i1zkem3qdk3K/lTF53E6B4YyANY4Py4MdJTzOTCSAbDG+XFhoJ8kA/NgJINLWX9lA4pHT1+Pevp6VLOwRhtXbVRddZ0q51Uqfiau/sF+dT3Rld8/dF2SuoxIOiYpruSXhpYpeUrhBmX1B8fpcD4HRjIA1pg4Lhx3lIU5sJABsMbEccFzKDMZXGHRh4wdP3VcW/dsdRdggaTr3A0vGZgDIxkAa0wcF447ysIcWMgAWGPiuOA5lJkMhcbHOwEAAAAgxFj0AQAAAECIsegDAAAAgBCLeJ7n92sbfYnH46qqqkr+45WFHDnlZSW/cDIiab6D8clABmsZXI8vSaeT/xkaGlJlZaWjEAb6SbKxPVxncD0+GchwPgMdRT+Rwcz4ZLCVIc1+cnsil9MOx/Ycj08GMljL4Hp8a1zPhYXt4TqD6/HJQAarXM+DhW1BBvfjk8FWhjm4XfTxTh8ZyOA+g+vxJZtFySvpxb1PkoEM01nrKPqpuDO4Hp8MtjKk2U/uFn3zJb3XwbgPKTk5rsYnAxmsZXA9viQ9qGRxWlHM+4OFDK7HJwMZzmepo4p9W5DB/fhksJUhzX7iRC4AAAAAEGIs+gAAAAAgxFj0AQAAAECIsegDAAAAgBBze/ZOBFLtolq13dCm+up6Vcyr0PCZYfUN9qn7ULcGXhgI/fiSpBFJxyTFJY1JKpNUKalB0oLCRDAxD4AxJo4L1/3genwZ2Q6AMSaOCwP9YCGDiW1RYCz6kLbmhmbFWmJqbWpVwktIkqLRqBKJ5PU7192pR48+qo59Herp6wnd+JKkk5J6JT2n5Ol5pbOn6pWkpyUtk9QoaUl+IpiYB8AYE8eF635wPb6MbAfAGBPHhYF+sJDBxLZwhI93Ii2xNTEd2HxAaxvXKhqNqrSkVKUlpYpGpl2PRnVT4006eMdBtbe0h2p8eZKOStoraWDaz7yLXB9I3e7otJ/liPN5AAxyfly47gfX46c43w6AQc6PCwv9YCGDDGwLx1j0YU7tLe3aftt2SVJZSdmst538fcf6jpwdLK7Hl5R8Zepw6vpcJTT5+8Op++WIiXkAjDFxXLjuB9fjy8h2AIwxcVwY6AcLGUxsC8eyWvR98YtfVCQS0Sc/+ckcxYE1zQ3N6ljf4eu+Hes7tLp+daDHl5T8OMLhOW91cYdT98+SiXkIGPop/EwcF677wfX4MrIdAoZ+Cj8Tx4WBfrCQwcS2MMD3ou+pp57SP/7jP6qpqSmXeWBMrCWmsYkxX/cdmxhT+5rsXiFxPb6k5CtNkTlvdXER5eSVKhPzECD0U3EwcVy47gfX48vIdggQ+qk4mDguDPSDhQwmtoUBvhZ9IyMj2rBhg+6//34tXLgw15lgRO2iWrU2tc75NvhMykrKdEvTLapZWBPI8SUlzzD1nPx/rtxL3X/EfwQT8xAg9FNxMHFcuO4H1+PLyHYIEPqpOJg4Lgz0g4UMJraFEb4WfZs2bdLNN9+sd77znbnOA0PabmibOrORXwkvoY2rNgZyfEnJUwr7fYVqUiT1OD6ZmIcAoZ+Kg4njwnU/uB5fRrZDgNBPxcHEcWGgHyxkMLEtjMj4KxsefvhhPf3003rqqafSuv3o6KhGR0en/h2PxzMdEo7UV9dn/RiePNVV1wVyfEnJ75DJhSwex8Q8BAT9VDxMHBeu+8H1+DKyHQKCfioeJo4LA/1gIYOJbWFERu/0DQwM6Pbbb9eDDz6oefPmpXWfbdu2qaqqaupSW1vrKygKr2JehaLR7E7wWhItUeW8ykCOLyn5paHZnjLYSz2OTybmIQDop+Ji4rhw3Q+ux5eR7RAA9FNxMXFcGOgHCxlMbAsjMpqFn/70pxocHNR1112n0tJSlZaW6sCBA/q7v/s7lZaWamJi4oL7bNmyRUNDQ1OXgYFwfst9GA2fGZ76skq/JhITip/x9xKN6/ElSWXKzUcT/H2UXJKReQgA+qm4mDguXPeD6/FlZDsEAP1UXEwcFwb6wUIGE9vCiIw+3vmOd7xDvb3nnkZn48aNWrFihT796U+rpKTkgvuUl5ervLw8u5Rwom+wL+vHiCii/sH+QI4vScrVCztZPI6JeQgA+qm4mDguXPeD6/FlZDsEAP1UXEwcFwb6wUIGE9vCiIze6auoqNDVV199zuVVr3qVFi9erKuvvjpfGeFI96FuRSPZvSUejUTV9URXIMeXJDUoNx9NaPB/dxPzEAD0U3ExcVy47gfX48vIdggA+qm4mDguDPSDhQwmtoUR2c0CQm3ghQHtObonq+822X10t46fOh7I8SVJCyQtU3bfMbMs9Tg+mZgHwBgTx4XrfnA9voxsB8AYE8eFgX6wkMHEtjAi60XfD3/4Q91zzz05iAKLtu/b7vu7TUqiJerc1xno8SVJjcruO2Yas49gYh4CiH4KNxPHhet+cD2+jGyHAKKfws3EcWGgHyxkMLEtDOCdPsyqp69HsV0xX/fd/M3N6unrCfT4kqQlklb6vO/K1P2zZGIeAGNMHBeu+8H1+DKyHQBjTBwXBvrBQgYT28IAFn2YU+f+zqmDZa63xyd/H9sVU+f+3Lwy4np8SclXmiZLa66PKUz+fqVy8ypZiol5AIwxcVy47gfX48vIdgCMMXFcGOgHCxlMbAvHWPQhLZ37O9V8d7P29u5VIpHQ+MS4xifGlfASGpsYS15PJLS3d6+a727O+UHienxFJDVJullS7bSfRS5yvTZ1uyb5/xz7DJzPA2CQ8+PCdT+4Hj/F+XYADHJ+XFjoBwsZZGBbOJbRVzaguPX09ainr0c1C2u0cdVG1VXXqXJepeJn4uof7FfXE115/UNX1+NLSn7MYImkEUnHJMWV/NLQMiVPKdyg7P7oOQ0m5gEwxsRx4bofXI8vI9sBMMbEcWGgHyxkMLEtHGHRh4wdP3VcW/dsLdrxJSVL6Tq3EUzMA2CMiePCdT+4Hl9GtgNgjInjwkA/WMhgYlsUGB/vBAAAAIAQY9EHAAAAACHGog8AAAAAQizieZ7fr0z0JR6Pq6qqKvmPVxZy5JSXlfyyx4ik+Q7GJwMZrGVwPb4knU7+Z2hoSJWVlY5CGOgnycb2cJ3B9fhkIMP5DHQU/UQGM+OTwVaGNPvJ7YlcTjsc23M8PhnIYC2D6/GtcT0XFraH6wyuxycDGaxyPQ8WtgUZ3I9PBlsZ5uB20cc7fWQgg/sMrseXbBYlr6QX9z5JBjJMZ62j6KfizuB6fDLYypBmP7lb9M2X9F4H4z6k5OS4Gp8MZLCWwfX4kvSgksVpRTHvDxYyuB6fDGQ4n6WOKvZtQQb345PBVoY0+4kTuQAAAABAiLHoAwAAAIAQY9EHAAAAACHGog8AAAAAQszt2TszULuoVm03tKm+ul4V8yo0fGZYfYN96j7UrYEXBshQRBlcj08GWxkssDAPZLCTQSOSjkmKSxqTVCapUlKDpAX5H97CHJDBFtdz4Xp8MkzjuJ8kG/PgOoOL8d19Oft8SRvmvn1zQ7NiLTG1NrUq4SUkSdFoVIlE6nokqkePPqqOfR3q6euZ+wEnz7LzSqV9lh0y2MiQ8/HJ4Gv8vGRInXnKzJezu+onyf3+YCGDhX3SRwadlNQr6TklT98tnT2V9+T1ZZIaJS1J4/FcbwcynGWgozLtJ8n9cRHa/cF1hgD2k2RjHlxncNlPphd9sTUxbb9tu8YmxlRWUjbj7SZ/H9sVU+f+ztkfNMONQwYbGfIyPhkyHj9vGQw8oZIM9JPkfn+wkMHCPplJBk/JJ1OHlXwCNdv/q07+fqWST64is9zW9XYgw1kGOirTRZ/r4yLU+4PrDAHrJ8nAPBjI4LqfzP5NX3tLu7bftl2SZp2Y6b/vWN+h9pZ2MoQsg+vxyWArgwUW5oEMdjJMPaGSZn9CNf33h1P3ywELc0AGW1zPhevxyTCN436SbMyD6wyux5cyXPTdeeedikQi51xWrFiRszCTmhua1bG+w9d9O9Z3aHX9ajKEJIPr8clgK8Ns6CcyuMigkzr7hCpTh1P3z4KFOSBDeoqlo1yPT4ZpHPeTZGMeXGdwPf6kjN/pu+qqq3Ty5Mmpy49+9KOcBJku1hLT2MSYr/uOTYypfU32q2Iy2Mjgenwy2MowF/qJDIXOoF7N/hGo2USU9avpFuaADOkrho5yPT4ZpnHcT5KNeXCdwfX4kzJe9JWWlurSSy+durzmNa/JSZBJtYtq1drUOudbnzMpKynTLU23qGZhDRkCnsH1+GSwlSEd9BMZCplBI0qeFMHvX8Z7qfuP+Lu7hTkgQ2bC3lGuxyfDNI77SbIxD64zuB5/uowXfX19fVq6dKmuuOIKbdiwQc8991zWIaZru6Ft6mw2fiW8hDau2kiGgGdwPT4ZbGVIB/1EhkJm0DH5fxV9UiT1OD5YmAMyZCbsHeV6fDJM47ifJBvz4DqD6/Gny+h7+t7ylreou7tby5cv18mTJ/X5z39eq1ev1i9/+UtVVFRc9D6jo6MaHR2d+nc8Hp91jPrq+kwiXZQnT3XVdb7vTwYbGVyPTwZbGeZCP5Gh0Bk0++6S98exMAdkSF+mHZVpP0nu58L1+GSYxnE/STbmwXUG1+NPl9Gib+3atVPXm5qa9Ja3vEWXXXaZdu3apQ996EMXvc+2bdv0+c9/Pu0xKuZVKBrN7qSiJdESVc7zf0plMtjI4Hp8MtjKMBf6iQyFzqAx+f/o1CQv9Tg+WJgDMqQv047KtJ8k93PhenwyTOO4nyQb8+A6g+vxp8sqxatf/Wo1NDSov79/xtts2bJFQ0NDU5eBgdm/ZX74zPDUFxT6NZGYUPyM/5cmyGAjg+vxyWArQ6boJzLkO4PKlJuPT/n7Uw8Tc0AG/+bqqEz7SXI/F67HJ8M0jvtJsjEPrjO4Hn+6rBZ9IyMj+vd//3ctWbJkxtuUl5ersrLynMts+gb7sokkSYooov7BmZ/ozYUMNjK4Hp8MtjJkin4iQ74zKFdvDPl8HAtzQAb/5uqoTPtJcj8XrscnwzSO+0myMQ+uM7gef7qMFn1/8Rd/oQMHDujZZ5/VoUOH9Ad/8AcqKSnRe97znqyDTOo+1K1oJLu3QaORqLqe6CJDwDO4Hp8MtjLMhX4iQ6EzqEG5+fhUg7+7WpgDMqSvGDrK9fhkmMZxP0k25sF1Btfjn/M4mdz4+PHjes973qPly5dr/fr1Wrx4sZ588kldcsklWQeZNPDCgPYc3ZPV91nsPrpbx08dJ0PAM7genwy2MsyFfiJDoTNogaRlyu57sJalHscHC3NAhvQVQ0e5Hp8M0zjuJ8nGPLjO4Hr86TJa9D388MM6ceKERkdHdfz4cT388MO68sorsw5xvu37tvv+PouSaIk693WSISQZXI9PBlsZZkM/kcFFBjUqu+/BasxueAtzQIb0FEtHuR6fDNM47ifJxjy4zuB6/EnZvd+YJz19PYrtivm67+ZvblZPXw8ZQpLB9fhksJXBAgvzQAY7GbRE0kqf912Zun8WLMwBGWxxPReuxyfDNI77SbIxD64zuB5/kslFnyR17u+cmqC53hKd/H1sV0yd+3P3ah0ZbGRwPT4ZbGWwwMI8kMFOBjXq7BOruT5KNfn7lcrJq+iSjTkggy2u58L1+GSYxnE/STbmwXUG1+NLhhd9UnKCmu9u1t7evUokEhqfGNf4xLgSXkJjE2PJ64mE9vbuVfPdzXkpbjLYyOB6fDLYymCBhXkgg5EMEUlNkm6WVDvtZ5GLXK9N3a5J/v/W5iKczwEZzHE9F67HJ0OKgX6SDMyDgQyux494npftuX0yEo/HVVVVJc2XtCH9+9UsrNHGVRtVV12nynmVip+Jq3+wX11PdGX2x40PSTot6ZWS3ptZdjLYyJCz8cmQ1fg5zfCgpJeloaGhtE5Lni/O+0lyvz9YyGBhn8wig0YkHZMUV/KLjcuUPO15gzI7KYLr7UCGswx0lN9+ktwfF6HbH1xnCHA/STbmwXUGF/0UmEVfzmRzoJCBDGHM4Hp8ycQTKslAP0k2tofrDK7HJwMZzmego+gnMpgZnwy2MqTZT6Y/3gkAAAAAyA6LPgAAAAAIMRZ9AAAAABBi7v6mT0p+/rXQXlbyCycjSn4u3gUykMFSBtfjS8nPw8vQ3/RJbvpJsrE9XGdwPT4ZyHA+Ax1FP5HBzPhksJUhzX4qLVCcizvtcGzP8fhkIIO1DK7Ht8b1XFjYHq4zuB6fDGSwyvU8WNgWZHA/PhlsZZiD20Uf7/SRgQzuM7geX7JZlLySXtz7JBnIMJ21jqKfijuD6/HJYCtDmv3kbtE3X25Pc+tqfDKQwVoG1+NLU6cbNqOY9wcLGVyPTwYynM9SRxX7tiCD+/HJYCtDmv3EiVwAAAAAIMRY9AEAAABAiLHoAwAAAIAQc3sil4CpXVSrthvaVF9dr4p5FRo+M6y+wT51H+rWwAsDruMVzoikY5LiksYklUmqlNQgaYHDXAXEvgBr2CdTLPST4wzsC7CGfTKFfpLE/uAKi740NDc0K9YSU2tTqxJeQpIUjUaVSCSv37nuTj169FF17OtQT1+Py6j5dVJSr6TnlDxLkXT2jEWS9LSkZZIaJS0peLqCYF+ANeyTKRb6yXEG9gVYwz6ZQj9JYn9wjY93ziG2JqYDmw9obeNaRaNRlZaUqrSkVNHItOvRqG5qvEkH7zio9pZ215Fzz5N0VNJeSQPTfuZd5PpA6nZHp/0sJNgXYA37pGz0k4EM7Auwhn1SJrrBRAaxP1jAom8W7S3t2n7bdklSWUnZrLed/H3H+o7w7ai9kg6nrs9VApO/P5y6X0iwL8Aa9skUC/3kOAP7Aqxhn0yhnySxP1jBom8GzQ3N6ljf4eu+Hes7tLp+dY4TOXJSZ8siU4dT9w849gVYwz6ZYqGfHGdgX4A17JMp9JMk9gdLMl70/dd//Zfe9773afHixZo/f74aGxt15MiRfGRzKtYS09jEmK/7jk2MqX1NSF6d6NXZz3tnKqJQvNvHvhAc9NPcQrVPWugnxxnYF4KlGDqKfTKFfpLE/mBJRou+U6dOadWqVSorK9N3vvMd/epXv1JHR4cWLlyYr3xO1C6qVWtT65xvQc+krKRMtzTdopqFNTlOVmAjSv7Br9/PdXup+4/kLFHBsS8EB/2UntDskxb6yXEG9oVgKYaOYp9MoZ8ksT9Yk9Gi70tf+pJqa2vV1dWllStX6vLLL9eaNWt05ZVX5iufE203tE2dVcivhJfQxlUbc5TIkWPy/wrRpEjqcQKKfSE46Kf0hWKftNBPjjOwLwRLMXQU+2QK/SSJ/cGajBZ9u3fv1pve9Cbddtttqq6u1rXXXqv7779/1vuMjo4qHo+fc7Guvro+68fw5Kmuui4HaRzK1aayv8lnxL4QHPRT+kKxT1roJ8cZ2BeCJdOOop8CjH6SxP5gTUaLvt/+9rfasWOH6uvr9d3vflcf+chH9IlPfEIPPPDAjPfZtm2bqqqqpi61tbVZh863inkVikazO8dNSbRElfMqc5TIkTFlf8peL/U4AcW+EBz0U/pCsU9a6CfHGdgXgiXTjqKfAox+ksT+YE1GWyKRSOi6667TXXfdpWuvvVZ/+qd/qg9/+MP6h3/4hxnvs2XLFg0NDU1dBgYGZrytFcNnhqe+KNKvicSE4mfsvyo3qzLl5qMB/j7KbQL7QnDQT+kLxT5poZ8cZ2BfCJZMO4p+CjD6SRL7gzUZLfqWLFmiN7zhDef87Hd+53f03HPPzXif8vJyVVZWnnOxrm+wL+vHiCii/sH+HKRxKFebyv4mnxH7QnDQT+kLxT5poZ8cZ2BfCJZMO4p+CjD6SRL7gzUZLfpWrVqlZ5555pyfHTt2TJdddllOQ7nWfahb0Uh2b0dHI1F1PdGVo0SONCg3Hw1oyEEWR9gXgoN+Sl8o9kkL/eQ4A/tCsBRDR7FPptBPktgfrMloS3zqU5/Sk08+qbvuukv9/f166KGH9E//9E/atGlTvvI5MfDCgPYc3ZPV94rsPrpbx08dz3GyAlsgaZmy+46XZanHCSj2heCgn9ITmn3SQj85zsC+ECzF0FHskyn0kyT2B2syWvS9+c1v1iOPPKKvf/3ruvrqq7V161bdc8892rBhQ77yObN933bf3ytSEi1R577OHCdypFHZfcdLYw6zOMK+EAz0U3pCtU9a6CfHGdgXgqNYOop9MoV+ksT+YEnG77m2traqt7dXZ86c0a9//Wt9+MMfzkcu53r6ehTbFfN1383f3Kyevp4cJ3JkiaSVPu+7MnX/gGNfCA76aW6h2ict9JPjDOwLwVIMHcU+mUI/SWJ/sCS7D9qGXOf+zqkdda63pid/H9sVU+f+kL0q0aizpTHXxwQmf79SoXiXbxL7Aqxhn0yx0E+OM7AvwBr2yRT6SRL7gxUs+ubQub9TzXc3a2/vXiUSCY1PjGt8YlwJL6GxibHk9URCe3v3qvnu5nDuoBFJTZJullQ77WeRi1yvTd2uSf4/R24U+wKsYZ+UjX4ykIF9AdawT8pEN5jIIPYHC0pdBwiCnr4e9fT1qGZhjTau2qi66jpVzqtU/Exc/YP96nqiqzj+yHRJ6jIi6ZikuJJf2lmm5Cl9GxTok7akg30B1rBPpljoJ8cZ2BdgDftkCv0kif3BNRZ9GTh+6ri27tnqOoZ7CyRd5zqEW+wLsIZ9MsVCPznOwL4Aa9gnU+gnSewPrvDxTgAAAAAIMRZ9AAAAABBiLPoAAAAAIMRY9AEAAABAiEU8z/MKOWA8HldVVVXyH68s5MgpL0vylDwV7XwH45OBDNYyuB5fkk4n/zM0NKTKykpHIQz0k2Rje7jO4Hp8MpDhfAY6in4ig5nxyWArQ5r95Pbsnacdju05Hp8MZLCWwfX41rieCwvbw3UG1+OTgQxWuZ4HC9uCDO7HJ4OtDHNwu+jjnT4ykMF9BtfjSzaLklfSi3ufJAMZprPWUfRTcWdwPT4ZbGVIs5/cLfrmS3qvg3EfUnJyXI1PBjJYy+B6fEl6UMnitKKY9wcLGVyPTwYynM9SRxX7tiCD+/HJYCtDmv3EiVwAAAAAIMRY9AEAAABAiLHoAwAAAIAQc3siFyCgahfVqu2GNtVX16tiXoWGzwyrb7BP3Ye6NfDCgOt4AIoY/QTAMjrKDRZ9QAaaG5oVa4mptalVCS8hSYpGo0okktfvXHenHj36qDr2dainr8dlVABFhn4CYBkd5RYf7wTSFFsT04HNB7S2ca2i0ahKS0pVWlKqaGTa9WhUNzXepIN3HFR7S7vryACKBP0EwDI6yj0WfUAa2lvatf227ZKkspKyWW87+fuO9R2UFoC8o58AWEZH2cCiD5hDc0OzOtZ3+Lpvx/oOra5fneNEAJBEPwGwjI6yI6NF3+tf/3pFIpELLps2bcpXPsC5WEtMYxNjvu47NjGm9jW8UlUodBSKDf0UHPQTihEdZUdGJ3J56qmnNDExMfXvX/7yl2ppadFtt92W82CABbWLatXa1Kpo1N+b4mUlZbql6RbVLKzR8VPHc5wO56OjUEzop2Chn1Bs6ChbMtoKl1xyiS699NKpy549e3TllVfq937v9/KVD3Cq7Ya2qTNM+ZXwEtq4amOOEmE2dBSKCf0ULPQTig0dZYvvr2z4v//7P33ta19Te3u7IpHIjLcbHR3V6Ojo1L/j8bjfIYGCq6+uz/oxPHmqq67LQRpkIp2Oop8QZPRTcNFPKAZ0lC2+T+Ty7W9/Wy+++KLa2tpmvd22bdtUVVU1damtrfU7JFBwFfMqfH8sYVJJtESV8ypzlAjpSqej6CcEGf0UXPQTigEdZYvvLbFz506tXbtWS5cunfV2W7Zs0dDQ0NRlYGDA75BAwQ2fGZ760lC/JhITip/hFdpCS6ej6CcEGf0UXPQTigEdZYuvj3f+53/+px5//HF961vfmvO25eXlKi8v9zMM4FzfYF/WjxFRRP2D/TlIg3Sl21H0E4KMfgom+gnFgo6yxdc7fV1dXaqurtbNN9+c6zyAKd2HuhWNZPfRhGgkqq4nunKUCOmgo1AM6Kdgop9QLOgoWzLeEolEQl1dXfrABz6g0lLf54EBAmHghQHtObonq++Y2X10N6caLiA6CsWCfgoe+gnFhI6yJeNF3+OPP67nnntOH/zgB/ORBzBn+77tKisp83XfkmiJOvd15jgRZkNHoZjQT8FCP6HY0FF2ZLzoW7NmjTzPU0NDQz7yAOb09PUotivm676bv7lZPX09OU6E2dBRKCb0U7DQTyg2dJQd2X3QFigSnfs7p0prro8pTP4+tiumzv28QgUgv+gnAJbRUTaw6APS1Lm/U813N2tv714lEgmNT4xrfGJcCS+hsYmx5PVEQnt796r57mbKCkDB0E8ALKOj3OOviIEM9PT1qKevRzULa7Rx1UbVVdepcl6l4mfi6h/sV9cTXfzBMQAn6CcAltFRbrHoA3w4fuq4tu7Z6joGAFyAfgJgGR3lBh/vBAAAAIAQY9EHAAAAACHGog8AAAAAQizieZ5XyAHj8biqqqqS/3hlIUdOeVmSJykiab6D8clABmsZXI8vSaeT/xkaGlJlZaWjEAb6SbKxPVxncD0+GchwPgMdRT+Rwcz4ZLCVIc1+cnsil9MOx/Ycj08GMljL4Hp8a1zPhYXt4TqD6/HJQAarXM+DhW1BBvfjk8FWhjm4XfTxTh8ZyOA+g+vxJZtFySvpxb1PkoEM01nrKPqpuDO4Hp8MtjKk2U/uFn3zJb3XwbgPKTk5rsYnAxmsZXA9viQ9qGRxWlHM+4OFDK7HJwMZzmepo4p9W5DB/fhksJUhzX7iRC4AAAAAEGIs+gAAAAAgxFj0AQAAAECIsegDAAAAgBBj0QcAAAAAIcaiDwAAAABCjEUfAAAAAIQYiz4AAAAACLGMFn0TExP67Gc/q8svv1zz58/XlVdeqa1bt8rzvHzlA4C00E8ALKOjALhUmsmNv/SlL2nHjh164IEHdNVVV+nIkSPauHGjqqqq9IlPfCJfGQFgTvQTAMvoKAAuZbToO3TokG699VbdfPPNkqTXv/71+vrXv67Dhw/nJRwApIt+AmAZHQXApYw+3nnDDTfoe9/7no4dOyZJ+sUvfqEf/ehHWrt2bV7CAUC66CcAltFRAFzK6J2+z3zmM4rH41qxYoVKSko0MTGhL3zhC9qwYcOM9xkdHdXo6OjUv+PxuP+0ADAD+gmAZZl2FP0EIJcyeqdv165devDBB/XQQw/p6aef1gMPPKDt27frgQcemPE+27ZtU1VV1dSltrY269AAcD76CYBlmXYU/QQglzJa9G3evFmf+cxn9Ed/9EdqbGzUH//xH+tTn/qUtm3bNuN9tmzZoqGhoanLwMBA1qEB4Hz0EwDLMu0o+glALmX08c7Tp08rGj13nVhSUqJEIjHjfcrLy1VeXu4vHQCkiX4CYFmmHUU/AciljBZ969at0xe+8AUtW7ZMV111lX72s5+ps7NTH/zgB/OVDwDSQj8BsIyOAuBSRou+v//7v9dnP/tZffSjH9Xg4KCWLl2qP/uzP9PnPve5fOUDgLTQTwAso6MAuJTRoq+iokL33HOP7rnnnjzFAQB/6CcAltFRAFzK6EQuAAAAAIBgYdEHAAAAACHGog8AAAAAQoxFHwAAAACEGIs+AAAAAAgxFn0AAAAAEGIs+gAAAAAgxFj0AQAAAECIRTzP8wo54NDQkF796lcn/zG/kCOnvDztuovxyUAGaxlcjz8tw4svvqiqqipHIQz0k2RqezjL4Hp8MpBhhgwuO4p+IoOZ8clgMsNc/VRaoDhThoeHz/7j5ZlvVxCux5fIMIkMSa4zOB5/eHjY6aLPVD9JZLAwvkSGSWRw2lH000WQwf34EhkmGe+ngr/Tl0gkdOLECVVUVCgSiWR8/3g8rtraWg0MDKiysjIPCckQlAyuxydD7jJ4nqfh4WEtXbpU0ai7T53TT2QIUwbX44cpg4WOyrafJPfbw/X4ZCCDtQyF7KeCv9MXjUZVU1OT9eNUVlY620HIYCuD6/HJkJsMLt/hm0Q/kSGMGVyPH5YMrjsqV/0kud8erscnAxmsZShEP3EiFwAAAAAIMRZ9AAAAABBigVv0lZeX66/+6q9UXl5OhiLP4Hp8MtjKYIGFeSADGayMTwZ7XM+F6/HJQAZrGQo5fsFP5AIAAAAAKJzAvdMHAAAAAEgfiz4AAAAACDEWfQAAAAAQYiz6AAAAACDEArXo+/GPf6ySkhLdfPPNBR+7ra1NkUhk6rJ48WLdeOONOnr0aMGzPP/88/r4xz+uK664QuXl5aqtrdW6dev0ve99L+9jT5+HsrIyvfa1r1VLS4u++tWvKpFI5H388zNMv9x4440FGX+uHP39/QUZ//nnn9ftt9+uuro6zZs3T6997Wu1atUq7dixQ6dPn877+G1tbXrXu951wc9/+MMfKhKJ6MUXX8x7BmvoKPrp/ByuOsp1P0luO4p+uhD9RD+dn4N+Kq7nUIFa9O3cuVMf//jHdfDgQZ04caLg49944406efKkTp48qe9973sqLS1Va2trQTM8++yzeuMb36jvf//7+vKXv6ze3l499thjetvb3qZNmzYVJMPkPDz77LP6zne+o7e97W26/fbb1draqvHx8YJmmH75+te/XpCx58px+eWX533c3/72t7r22mu1b98+3XXXXfrZz36mH//4x7rjjju0Z88ePf7443nPgAsVe0fRTxfmcNlRrvpJoqMsop/op/Nz0E/F1U+lrgOka2RkRN/4xjd05MgRPf/88+ru7tZf/uVfFjRDeXm5Lr30UknSpZdeqs985jNavXq1/ud//keXXHJJQTJ89KMfVSQS0eHDh/WqV71q6udXXXWVPvjBDxYkw/R5eN3rXqfrrrtOv/u7v6t3vOMd6u7u1p/8yZ8UNINLrnJ89KMfVWlpqY4cOXLOfnDFFVfo1ltvFd/EUnh0FP00Uw5XXGago2yhn+inmXK4Qj8VXmDe6du1a5dWrFih5cuX633ve5+++tWvOt0oIyMj+trXvqa6ujotXry4IGO+8MILeuyxx7Rp06ZzdtJJr371qwuS42Le/va365prrtG3vvUtZxmKxf/+7/9q3759M+4HkhSJRAqcCsXeUfQTJtFR9tBP9BOSirmfArPo27lzp973vvdJSr4lPDQ0pAMHDhQ0w549e7RgwQItWLBAFRUV2r17t77xjW8oGi3MNPb398vzPK1YsaIg42VqxYoVevbZZwsy1vRtMXm56667CjL2bDluu+22vI85uR8sX778nJ+/5jWvmcrx6U9/Ou85pItvh7Vr1xZkbGuKvaPop3NZ6CgX/STZ6Sj66Sz6iX6ajn5y309S4TsqEB/vfOaZZ3T48GE98sgjkqTS0lL94R/+oXbu3Km3vvWtBcvxtre9TTt27JAknTp1Sl/5yle0du1aHT58WJdddlnex7f+drPneQV7dWT6tpi0aNGigow9W46ZXjUqhMOHDyuRSGjDhg0aHR0tyJgX2w4/+clPpp5cFAs6in46n4WOstRPUuE7in5Kop/op/PRTxcqhudQgVj07dy5U+Pj41q6dOnUzzzPU3l5ue69915VVVUVJMerXvUq1dXVTf37n//5n1VVVaX7779ff/M3f5P38evr6xWJRPSb3/wm72P58etf/7pgf4R7/rZwxUWOuro6RSIRPfPMM+f8/IorrpAkzZ8/v2BZLva///jx4wUb3wo6in46n4WOcpXBSkfRT0n0E/10PvrJfT9Jhe8o8x/vHB8f17/8y7+oo6NDP//5z6cuv/jFL7R06VInZ2ycFIlEFI1G9fLLLxdkvEWLFun3f//3dd999+mll1664PcuT0H9/e9/X729vXr3u9/tLEOxWLx4sVpaWnTvvfdedD9AYdFRSfQTJtFRdtBPSfQTJhVzP5l/p2/Pnj06deqUPvShD13watS73/1u7dy5U3/+539ekCyjo6N6/vnnJSU/mnDvvfdqZGRE69atK8j4knTfffdp1apVWrlypf76r/9aTU1NGh8f1/79+7Vjxw79+te/znuGyXmYmJjQf//3f+uxxx7Ttm3b1Nraqve///15H396hulKS0v1mte8piDju/aVr3xFq1at0pve9CbdeeedampqUjQa1VNPPaXf/OY3euMb3+g6YtGgo86iny7MMR0dRUcVGv10Fv10YY7p6Kci6CfPuNbWVu+mm2666O9+8pOfeJK8X/ziF3nP8YEPfMCTNHWpqKjw3vzmN3v/+q//mvexz3fixAlv06ZN3mWXXea94hWv8F73utd5t9xyi/eDH/wg72NPn4fS0lLvkksu8d75znd6X/3qV72JiYm8j39+humX5cuXF2T86TluvfXWgo453YkTJ7yPfexj3uWXX+6VlZV5CxYs8FauXOl9+ctf9l566aW8jz/T//4f/OAHniTv1KlTec9gAR11rmLvp/NzuOoo1/3keW47in5Kop/ORT/RT5OK8TlUxPOM/3UrAAAAAMA383/TBwAAAADwj0UfAAAAAIQYiz4AAAAACDEWfQAAAAAQYiz6AAAAACDEWPQBAAAAQIix6AMAAACAEGPRBwAAAAAhxqIPAAAAAEKMRR8AAAAAhBiLPgAAAAAIMRZ9AAAAABBi/w8Pls5r3UNbbAAAAABJRU5ErkJggg==\n"
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plot_othello_boards(create_test_game()[-3:])"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"array = create_test_game()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Sources\n",
"\n",
"* Game rules and example board images [https://en.wikipedia.org/wiki/Reversi](https://en.wikipedia.org/wiki/Reversi)\n",
"* Game rules and example game images [https://de.wikipedia.org/wiki/Othello_(Spiel)](https://de.wikipedia.org/wiki/Othello_(Spiel))\n",
"* Game strategy examples [https://de.wikipedia.org/wiki/Computer-Othello](https://de.wikipedia.org/wiki/Computer-Othello)\n",
"* Image for 8 directions [https://www.researchgate.net/journal/EURASIP-Journal-on-Image-and-Video-Processing-1687-5281](https://www.researchgate.net/journal/EURASIP-Journal-on-Image-and-Video-Processing-1687-5281)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.8"
}
},
"nbformat": 4,
"nbformat_minor": 4
}