diff --git a/main.ipynb b/main.ipynb index 629e1fc..9fa3e38 100644 --- a/main.ipynb +++ b/main.ipynb @@ -4,9 +4,46 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Deep Reversi AI\n", + "# Deep Otello AI\n", "\n", - "The game is not" + "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.\n", + "\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 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" ] }, { @@ -35,7 +72,6 @@ "import abc\n", "from typing import Final\n", "from scipy.ndimage import binary_dilation\n", - "from tqdm.auto import tqdm\n", "import matplotlib.pyplot as plt\n", "from abc import ABC" ] @@ -49,79 +85,117 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ - "ENEMY: Final[int] = -1\n", - "PLAYER: Final[int] = 1\n", - "BOARD_SIZE: Final[int] = 8" + "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": 4, + "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]])" - ] + "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": 4, + "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], dtype=int\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", - "metadata": {}, "source": [ - "## Creating new boards" - ] + "Another constant needed is the initial start square at the center of the board." + ], + "metadata": { + "collapsed": false + } }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, + "execution_count": 23, "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]])" - ] + "text/plain": "array([[-1, 1],\n [ 1, -1]])" }, - "execution_count": 5, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "def get_new_games(number_of_games: int):\n", + "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] = np.array([[-1, 1], [1, -1]])\n", + " empty[:, 3:5, 3:5] = START_SQUARE\n", " return empty\n", "\n", "\n", @@ -130,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -162,17 +236,27 @@ "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": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAASIAAAEiCAYAAABdvt+2AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/P9b71AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAdeElEQVR4nO3de1BU58EG8GdhyerAsgpKlIgiYEUxOCjGWsZUTCSuQkwnY9uMNhpp64UAubURO2nTprpmoh1bL2gVLx3jrZ2YJowg8f5lEkWMWpMQIxAqKRqTirtA2u3Cnu+PA6sIC5xlz3mF8/xmztiFs/u8L9AnZ8+ei0GSJAlERAIFiB4AERGLiIiEYxERkXAsIiISjkVERMKxiIhIOBYREQnHIiIi4YxaB7rdbtTW1sJsNsNgMGgdT0QakSQJ9fX1iIyMREBA59s8mhdRbW0toqKitI4lIkFqamowbNiwTtfRvIjMZvPtByEahzfc8b/1lC06n3MXky06vyW7zf/nvdC8iDxvx0IAvKRx+FoA9QDMAF7UUbbofM5dn3NfA6AB3doFw53VRCQci4iIhGMREZFwLCIiEo5FRETCsYiISDgWEREJxyIiIuEUF9GpU6eQkZGByMhIGAwGvP322yoMi4j0RHERNTY2Yvz48di4caMa4yEiHVJ8iofVaoXValVjLESkU6qfa+Z0OuF0Oj2PHQ6H2pFE1MuovrPaZrPBYrF4Fl4ChIjupnoR5eXlwW63e5aamhq1I4mol1H9rZnJZILJZFI7hoh6MR5HRETCKd4iamhoQEVFhefxF198gQsXLiAsLAzDhw/36+CISB8UF1FZWRlSU1M9j1944QUAwIIFC7Bz506/DYyI9ENxEU2bNg2SJKkxFiLSKe4jIiLhWEREJByLiIiEYxERkXAsIiISjkVERMKxiIhIOBYREQlnkDQ+OtHhcMBiscgPzFomA2gAIAEwAAjRUbbofM5dn3Ovl/+x2+0IDQ3tdFXVz77vVL2gXEmn2aLzOXdxROd3QWwRcYtIH/mcuz7nrqD4xBVRCIAXNc5cC/mHo7ds0fmcuz7nvgZyEXYDd1YTkXAsIiISjkVERMKxiIhIOBYREQnHIiIi4VhERCQci4iIhFNURDabDZMmTYLZbEZERASeeOIJXL58Wa2xEZFOKCqikydPIisrC6dPn8Z7770Hl8uFtLQ0NDY2qjU+ItIBRad4FBcXt3m8c+dORERE4Ny5c3j44Yf9OjAi0o8enWtmt9sBAGFhYV7XcTqdcDqdnscOh6MnkUTUB/m8s9rtduO5555DSkoKxo0b53U9m80Gi8XiWaKionyNJKI+yuciysrKwscff4x9+/Z1ul5eXh7sdrtnqamp8TWSiPoon96aPfvssygsLMSpU6cwbNiwTtc1mUwwmUw+DY6I9EFREUmShOzsbBw8eBAnTpzAyJEj1RoXEemIoiLKysrCnj178Pe//x1msxnXr18HAFgsFvTv31+VARJR36doH1F+fj7sdjumTZuGoUOHepb9+/erNT4i0gHFb82IiPyN55oRkXAsIiISjkVERMKxiIhIOBYREQnHIiIi4VhERCQci4iIhDNIGh+l6HA4YLFY5AdmLZMh34dbAmCAfC9wvWSLzufc9Tn3evkfu92O0NDQTlft0YXReqxeUK6k02zR+Zy7OKLzuyC2iLhFpI98zl2fc1dQfOKKKATAixpnroX8w9Fbtuh8zl2fc18DuQi7gTuriUg4FhERCcciIiLhWEREJByLiIiEYxERkXAsIiISTvHF8xMTExEaGorQ0FBMmTIFRUVFao2NiHRCURENGzYMq1evxrlz51BWVobp06djzpw5+OSTT9QaHxHpgKIjqzMyMto8XrlyJfLz83H69GkkJCT4dWBEpB8+n+LR3NyMv/71r2hsbMSUKVP8OSYi0hnFRXTp0iVMmTIF//3vfxESEoKDBw9i7NixXtd3Op1wOp2exw6Hw7eRElGfpfhTs9GjR+PChQs4c+YMli5digULFuDTTz/1ur7NZoPFYvEsUVFRPRowEfU9iovovvvuQ1xcHCZOnAibzYbx48fjj3/8o9f18/LyYLfbPUtNTU2PBkxEfU+PLwPidrvbvPW6m8lkgslk6mkMEfVhioooLy8PVqsVw4cPR319Pfbs2YMTJ07g8OHDao2PiHRAURHduHEDTz/9NK5duwaLxYLExEQcPnwYM2bMUGt8RKQDioqooKBArXEQkY7xXDMiEo5FRETCsYiISDgWEREJxyIiIuFYREQkHIuIiIRjERGRcAZJkiQtAx0OBywWi/zArGUyeA90zp1z11K9/I/dbkdoaGinq/b4pNceqReUK+k0W3Q+5y6O6PwuiC0ibhHpI59z1+fcFRSfuCIKAfCixplr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\n", - "text/plain": [ - "
" - ] + "text/plain": "
", + "image/png": "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\n" }, "metadata": {}, "output_type": "display_data" @@ -180,13 +264,24 @@ ], "source": [ "def plot_othello_board(board, ax=None):\n", - " size = 3\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=(size, size))\n", + " fig, ax = plt.subplots(figsize=(fig_size, fig_size))\n", "\n", - " ax.set_facecolor(\"green\")\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", @@ -215,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -235,20 +330,16 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": { "tags": [] }, "outputs": [ { "data": { - "text/plain": [ - "array([[[1, 1, 1],\n", - " [1, 0, 1],\n", - " [1, 1, 1]]])" - ] + "text/plain": "array([[[1, 1, 1],\n [1, 0, 1],\n [1, 1, 1]]])" }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -260,23 +351,14 @@ }, { "cell_type": "code", - "execution_count": 10, + "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]]])" - ] + "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": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -324,31 +406,42 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "(array([2, 2, 2]), array([2, 2, 2]))" - ] + "text/plain": "(array([2, 2, 2]), array([2, 2, 2]))" }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "def evaluate_boards(array: np.ndarray):\n", - " return np.sum(array == 1, axis=(1, 2)), np.sum(array == -1, axis=(1, 2))\n", + "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", - "evaluate_boards(get_new_games(3))" + "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": 12, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -371,7 +464,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -379,13 +472,9 @@ " 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", - " try:\n", - " arr_moves_possible[game] = not np.any(\n", - " get_possible_turns(np.reshape(boards[game], (1, 8, 8)))\n", - " )\n", - " except Exception as err:\n", - " print(test)\n", - " raise err\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", @@ -412,36 +501,21 @@ " np.array([True] * 3),\n", ")\n", "np.testing.assert_array_equal(\n", - " moves_possible(np.zeros((3, 8, 8)), np.array([[-1, -1]] * 3)), np.array([True] * 3)\n", + " moves_possible(np.zeros((3, 8, 8)), np.array([[-1, -1]] * 3)),\n", + " np.array([True] * 3),\n", ")" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 14, + "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]])" - ] + "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": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -488,13 +562,12 @@ " return boards\n", "\n", "\n", - "boards = get_new_games(10)\n", - "do_moves(boards, np.array([[2, 3]] * 10))[0]" + "do_moves(get_new_games(10), np.array([[2, 3]] * 10))[0]" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -502,7 +575,8 @@ "\n", " IMPOSSIBLE: np.ndarray = np.array([-1, -1], dtype=int)\n", "\n", - " @abc.abstractproperty\n", + " @property\n", + " @abc.abstractmethod\n", " def policy_name(self) -> str:\n", " raise NotImplementedError()\n", "\n", @@ -513,20 +587,13 @@ " def get_policy(self, boards: np.ndarray) -> np.ndarray:\n", " policies = self.internal_policy(boards)\n", " possible_turns = get_possible_turns(boards)\n", - " poss_turns_debug = possible_turns[0]\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", - " # todo check if no turn is possible and return [-1, -1]\n", - " a1 = np.all(policy_vector[:] == 0, 1)\n", - " a2 = policies[:, 0, 0] == -1.0\n", + "\n", " no_turn_possible = np.all(policy_vector == 0, 1) & (policies[:, 0, 0] == -1.0)\n", - " if np.any(no_turn_possible):\n", - " cases = np.where(no_turn_possible)\n", - " print(cases)\n", - " print(\"Test\")\n", "\n", " policy_vector[no_turn_possible] = GamePolicy.IMPOSSIBLE\n", " return policy_vector" @@ -534,7 +601,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -549,56 +616,10 @@ " # return np.argmax(random_values, (1, 2))\n", "\n", "\n", - "rndpolicy = RandomPolicy()\n", - "assert rndpolicy.policy_name == \"random\"" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "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, 1, 0, 0, 0],\n", - " [ 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [ 0, 0, 0, 0, 0, 0, 0, 0]])" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def single_turn(current_boards: np, policy: GamePolicy) -> np.ndarray:\n", - " policy_results = policy.get_policy(current_boards)\n", - " poss = moves_possible(current_boards, policy_results)\n", - " if not np.all(poss):\n", - " false_values = np.where(poss == False)\n", - " bad_boards = current_boards[false_values]\n", - " bad_policy = policy_results[false_values]\n", - " print(\"test\")\n", - "\n", - " try:\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", - " except AssertionError as err:\n", - " raise err\n", - "\n", - " return do_moves(current_boards, policy_results)\n", - "\n", - "\n", - "single_turn(get_new_games(10), RandomPolicy())[0]" + "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))" ] }, { @@ -610,325 +631,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "(array([1], dtype=int64),)\n", - "Test\n", - "(array([1], dtype=int64),)\n", - "Test\n", - "(array([0, 4, 5, 7, 8], dtype=int64),)\n", - "Test\n" + "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.]]],\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., ..., 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", - " [[ 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., 1., ..., 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., ..., -1., 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", - " [[ 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., ..., -1., 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., ..., 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", - "\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", - "\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., ..., 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.]]]])" - ] + "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": {}, @@ -936,63 +644,39 @@ } ], "source": [ - "def simulate_game(\n", - " nr_of_games: int,\n", - " policies: tuple[GamePolicy, GamePolicy],\n", - ") -> np.ndarray:\n", - " history_stack = np.zeros((70, nr_of_games, 8, 8))\n", - " current_boards = get_new_games(nr_of_games)\n", - " index_counter = 0\n", - " for i in range(60):\n", - " policy_index = i % 2\n", - " policy = policies[policy_index]\n", - " if policy_index == 0:\n", - " current_boards = current_boards * -1\n", - " try:\n", - " current_boards = single_turn(current_boards, policy)\n", - " except RuntimeError as err:\n", - " print(\"Err\")\n", - " print(history_stack)\n", - " raise err\n", - " if policy_index == 0:\n", - " current_boards = current_boards * -1\n", + "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", - " history_stack[index_counter] = current_boards\n", - " index_counter += 1\n", - " return history_stack\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", - "simulate_game(10, (RandomPolicy(), RandomPolicy()))" + "%timeit single_turn(get_new_games(100), RandomPolicy())\n", + "single_turn(get_new_games(100), RandomPolicy())[0]" ] }, { "cell_type": "code", "execution_count": 19, - "metadata": { - "tags": [] - }, + "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, 0, 0],\n", - " [ 0, 0, 0, -1, 0, -1, 0, 0],\n", - " [ 0, 0, 1, 1, 1, 1, 0, 0],\n", - " [ 0, 0, 0, -1, 1, 0, 0, 0],\n", - " [ 0, 0, -1, -1, -1, 0, 0, 0],\n", - " [ 0, 0, 1, -1, 0, 0, 0, 0],\n", - " [ 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [ 0, 0, 0, 0, 0, 0, 0, 0]],\n", - "\n", - " [[ 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [ 0, 0, 0, 0, -1, -1, -1, 0],\n", - " [ 0, 0, 1, 0, -1, 0, 0, 0],\n", - " [ 0, 0, 0, 1, -1, 0, 0, 0],\n", - " [ 0, 0, 0, -1, 1, -1, -1, 0],\n", - " [ 0, 0, -1, 0, 0, 1, 0, 0],\n", - " [ 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [ 0, 0, 0, 0, 0, 0, 0, 0]]])" - ] + "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": {}, @@ -1000,119 +684,46 @@ } ], "source": [ - "arr = np.array(\n", - " [\n", - " [\n", - " [0, 0, 0, 0, 0, 0, 0, 0],\n", - " [0, 0, 0, -1, 0, -1, 0, 0],\n", - " [0, 0, 1, 1, 1, 1, 0, 0],\n", - " [0, 0, 0, -1, 1, 0, 0, 0],\n", - " [0, 0, -1, -1, -1, 0, 0, 0],\n", - " [0, 0, 1, -1, 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, -1, -1, -1, 0],\n", - " [0, 0, 1, 0, -1, 0, 0, 0],\n", - " [0, 0, 0, 1, -1, 0, 0, 0],\n", - " [0, 0, 0, -1, 1, -1, -1, 0],\n", - " [0, 0, -1, 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", - ")\n", - "arr" + "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": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[[False, False, True, True, True, True, True, False],\n", - " [False, False, False, False, False, False, False, False],\n", - " [False, False, False, False, False, False, False, False],\n", - " [False, False, True, False, False, False, False, False],\n", - " [False, False, False, False, False, False, False, False],\n", - " [False, True, False, False, True, True, False, False],\n", - " [False, False, False, True, False, False, False, False],\n", - " [False, False, False, False, False, False, False, False]],\n", - "\n", - " [[False, False, False, False, True, False, True, False],\n", - " [False, False, False, False, False, False, False, False],\n", - " [False, False, False, False, False, False, False, False],\n", - " [False, False, False, False, False, True, False, True],\n", - " [False, False, True, False, False, False, False, True],\n", - " [False, False, False, True, False, False, False, False],\n", - " [False, False, False, False, False, False, False, False],\n", - " [False, False, False, False, False, False, False, False]]])" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "get_possible_turns(arr)" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ True, True])" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "moves_possible(arr, RandomPolicy().get_policy(arr))" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[0, 4],\n", - " [4, 7]], dtype=int64)" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "RandomPolicy().get_policy(arr)" - ] - }, - { - "cell_type": "code", - "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1363,25 +974,46 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": "
", + "image/png": "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8zNMv9x4440FGX+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": null, + "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": null, + "execution_count": 22, "metadata": {}, "outputs": [], "source": []