reversi/main.ipynb

1107 lines
85 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.\n",
"* [Initial design decisions](#initial-design-decisions) an explanation about some initial design decision and assumptions\n",
"* [Imports and dependencies](#imports-and-dependencies) explains what libraries where used"
],
"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": "markdown",
"source": [
"## Initial design decisions\n",
"\n",
"At the beginning of this project I made some design decisions.\n",
"The first onw was that I do not want to use a gym library because it limits the data formats accessible.\n",
"I choose to implement the hole game as entry in a stack in numpy arrays to be able to accommodate interfacing with a neural network easier and to use scipy pattern recognition tools to implement some game mechanics for a fast simulation cycle.\n",
"I chose to ignore player colors as far as I could instead a player perspective was used. Which allowed to change the perspective with a flipping of the sign. (multiplying with -1).\n",
"The array format should also allow for data multiplication or the breaking of strikt sequences by flipping the game along one the for axis, (horizontal, vertical, transpose along both diagonals).\n",
"\n",
"I wanted to implement different agents as classes that act on those game stacks.\n",
"\n",
"Since computation time is critical all computational have results are saved.\n",
"The analysis of those is then repeated in real time. If a recalculation of such a section is required the save file can be deleted and the code should be executed again."
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%load_ext blackcellmagic"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Imports and dependencies\n",
"\n",
"The following direct dependencies where used for this project:\n",
"```toml\n",
"jupyter = \"^1.0.0\"\n",
"matplotlib = \"^3.6.3\"\n",
"numpy = \"^1.24.1\"\n",
"pytest = \"^7.2.1\"\n",
"python = \"3.10.*\"\n",
"scipy = \"^1.10.0\"\n",
"tqdm = \"^4.64.1\"\n",
"jupyterlab = \"^3.6.1\"\n",
"torchvision = \"^0.14.1\"\n",
"torchaudio = \"^0.13.1\"\n",
"```\n",
"* `Jupyter` and `jupyterlab` on pycharm was used as a IDE / Ipython was used to implement this code.\n",
"* `matplotlib` was used for visualisation and statistics.\n",
"* `numpy` was used for array support and mathematical functions\n",
"* `tqdm` was used for progress bars\n",
"* `scipy` contains fast pattern recognition tools for images. It was used to make an initial estimation about where possible turns should be.\n",
"* `torch` supplied the ANN functionalities."
]
},
{
"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\n",
"\n",
"Some general constants needed to be defined. Such as board game size and Player and Enemy representations. Also, directional offsets and the initial placement of blocks."
]
},
{
"cell_type": "code",
"execution_count": 3,
"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 enemy 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,
"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": 4,
"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": 5,
"outputs": [
{
"data": {
"text/plain": "array([[-1, 1],\n [ 1, -1]])"
},
"execution_count": 5,
"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": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "<Figure size 300x300 with 1 Axes>",
"image/png": "iVBORw0KGgoAAAANSUhEUgAAASIAAAEiCAYAAABdvt+2AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/P9b71AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAdq0lEQVR4nO3de3BU5f0G8OckG1dIsivEYFizQEIsMcHwA0RLMyhBQQKh0HFocUIBRcslAuq0lWBtaRUWx7ZDBQwUwqVDuNkR6jByEeRSUS5BodyChktZSCKMhV0S6prsnt8fx90mkE32bPacN8l5PjNndHfP2e/7kvDw7tnzvkeSZVkGEZFAUaIbQETEICIi4RhERCQcg4iIhGMQEZFwDCIiEo5BRETCMYiISDiT3gV9Ph8qKioQHx8PSZL0Lk9EOpFlGTdv3oTNZkNUVNNjHt2DqKKiAna7Xe+yRCSI0+lEcnJyk/voHkTx8fGB/+/YVd/at6oAyAAkoGOScWqLrs++i6ktuv6tSuW/9f/OB6N7EPk/jnXsCoyv0Ld2STJQcwWItQH5l41TW3R99t2YfV9rU8IolFMwPFlNRMIxiIhIOAYREQnHICIi4RhERCQcg4iIhGMQEZFwDCIiEk51EO3fvx+jRo2CzWaDJEnYsmWLBs0iIiNRHUQ1NTXo06cPlixZokV7iMiAVE/xyM3NRW5urhZtISKD0nyumcfjgcfjCTx2u91alySiNkbzk9UOhwNWqzWwcQkQIrqd5kFUWFgIl8sV2JxOp9YliaiN0fyjmdlshtls1roMEbVhvI6IiIRTPSKqrq5GeXl54PGFCxdw7NgxdO7cGd26dYto44jIGFQHUWlpKXJycgKPX3nlFQDAxIkTsXr16og1jIiMQ3UQDR48GLIsa9EWIjIoniMiIuEYREQkHIOIiIRjEBGRcAwiIhKOQUREwjGIiEg4BhERCSfJOl+d6Ha7YbVaAUm5H7eeblUCsg+QooCOXY1TW3R99t2Yfa+pACADLpcLFoulyX3FBRERGUIoQaT5MiBBcURkmPrsuzH77h8RhUJYEHVMAvIv61uzJBmouaL8QIxUW3R99t2YfV9rU4IwFDxZTUTCMYiISDgGEREJxyAiIuEYREQkHIOIiIRjEBGRcAwiIhJOVRA5HA4MGDAA8fHx6NKlC8aMGYOzZ89q1TYiMghVQbRv3z4UFBTg4MGD+Oijj1BbW4thw4ahpqZGq/YRkQGomuKxffv2Bo9Xr16NLl264OjRo3jsscci2jAiMo4WzTVzuVwAgM6dOwfdx+PxwOPxBB673e6WlCSidijsk9U+nw8vvfQSsrOz0bt376D7ORwOWK3WwGa328MtSUTtVNhBVFBQgJMnT2LDhg1N7ldYWAiXyxXYnE5nuCWJqJ0K66PZiy++iK1bt2L//v1ITk5ucl+z2Qyz2RxW44jIGFQFkSzLmDFjBjZv3oy9e/ciJSVFq3YRkYGoCqKCggKsW7cO//jHPxAfH4+qqioAgNVqRYcOHTRpIBG1f6rOERUVFcHlcmHw4MHo2rVrYNu4caNW7SMiA1D90YyIKNI414yIhGMQEZFwDCIiEo5BRETCMYiISDgGEREJxyAiIuEYREQknCTrfJWi2+2G1WoFJCDWpmdl5T7csg+QopR7gRultuj67Lsx+15TAUBW1i2zWCxN7isuiIjIEEIJohat0NgiHBEZpj77bsy++0dEoRAWRB2TgPzL+tYsSQZqrig/ECPVFl2ffTdm39falCAMBU9WE5FwDCIiEo5BRETCMYiISDgGEREJxyAiIuEYREQknOrF87OysmCxWGCxWDBw4EBs27ZNq7YRkUGoCqLk5GQsWLAAR48eRWlpKYYMGYLRo0fj1KlTWrWPiAxA1ZXVo0aNavB43rx5KCoqwsGDB5GZmRnRhhGRcYQ9xcPr9eK9995DTU0NBg4cGMk2EZHBqA6iEydOYODAgfj2228RFxeHzZs3IyMjI+j+Ho8HHo8n8NjtdofXUiJqt1R/a9arVy8cO3YMhw4dwrRp0zBx4kScPn066P4OhwNWqzWw2e32FjWYiNof1UF01113IS0tDf3794fD4UCfPn3wl7/8Jej+hYWFcLlcgc3pdLaowUTU/rR4GRCfz9fgo9ftzGYzzGZzS8sQUTumKogKCwuRm5uLbt264ebNm1i3bh327t2LHTt2aNU+IjIAVUF09epVTJgwAZWVlbBarcjKysKOHTswdOhQrdpHRAagKoiKi4u1agcRGRjnmhGRcAwiIhKOQUREwjGIiEg4BhERCccgIiLhGEREJByDiIiEk2RZDvHu1JHhdrthtVoBCYi16VmZ90Bn39l3PdVUAJABl8sFi8XS5L7igoiIDCGUIGrx7PuwcURkmPrsuzH77h8RhUJYEHVMAvIv61uzJBmouaL8QIxUW3R99t2YfV9rU4IwFOJGRNRmmBGLRKTBBDPq4ME1lMODGl1q11YDrnLA5wGizIA1DYiJ06U06YhBRI3qigfxGKaiN0YgEamQ6n3BKsOHaziPk/gQ+7EUlTgT0drXTwOnlwLODwH3eTQc3kuAJRWwjwAypgKdgi+XTm0Ig4gaSEAP5GMZMjEMXtQiGjF37CMhCl2QhscxDUMwE6ewEyWYgm9wsUW13ReAf04BrnwESCZArmtkJxlwnwNOFwGnFgH3DwUGLQMsKS0qTYLxOiIKyMZkzMVppCMHABoNofr8r6cjB3NxCtmYHHbtshXAexlAxR7lcaMhVI//9Yo9ynFlK8IuTa0Ag4gAALmYgwlYgRjc3WwA3S4aMYhBB0zACuRijuran88D9r8AeL9tPoBuJ9cpx+1/QXkfapsYRIRsTMYYKH+LJUhhvYf/uDGYh2w8F/JxZSuA0t+EVfIOpb8ByriIaJvEIDK4BPTAOCyCHOoFH82QIWMcFiEBPZrd130BODAjImUDDryovC+1LQwig8vHMkTDFPZI6HYSJEQjBvlY1uy+/5wC+FR+FGuOr055X2pbWhRECxYsgCRJeOmllyLUHNJTVzyITAxTfU6oOdGIQSaGIQnpQfe5flr5dkztOaHmyHXK+16P7BUFpLGwg+jIkSNYtmwZsrKyItke0tFjmAovajV5by9q8TimBX399FLlK3otSCbl631qO8IKourqauTn52P58uXo1KlTpNtEOumNEREfDflFIwa9kRv0deeHkR8N+cl1gHObNu9N2ggriAoKCjBy5Eg8+eSTkW4P6cSMOCQiVdMaiegJM2LveP67m99fMa0h9zllegi1DaoHxxs2bMDnn3+OI0eOhLS/x+OBx+MJPHa73WpLkgYS0bPBtA0tSIhCItIAHG/wvPscQp6VHTZZmaN27/9pXIciQtVvotPpxKxZs1BSUoK77747pGMcDgesVmtgs9vtYTWUIssEs7A6Pk8jO2pArzrUcqqC6OjRo7h69Sr69esHk8kEk8mEffv24Z133oHJZILX673jmMLCQrhcrsDmdDoj1ngKXx30+VvaWJ0ofTJQtzrUcqo+mj3xxBM4ceJEg+eeffZZpKen49VXX0V0dPQdx5jNZpjN/I1oba6hHDJ8mn48U2bpl9/xvDUNgARtP55J39ehNkFVEMXHx6N3794NnouNjUVCQsIdz1Pr5kENruE8ukC7v63XcK7RdYti4pSlPNznNCsNS0+uW9SW8MpqAzuJDzW9jugkgn+Hbh+h7XVE9uBXDlAr1OJfhb1790agGSTCfizFEMzU5L2jEYN9CH5VYcZUZT0hLch1QEbwaympFeKIyMAqcQansDPioyIvanEKO1GFsqD7dMpQFjWL9KhIMinv2+nByL4vaYtBZHAlmAIvaiM6+96LWpSg+Zmng5YBUREOoiiT8r7UtjCIDO4bXMQGzIzo7PsNmBHSsrGWFCA7wh/Pshdz2di2iEFEOIBibMFrABD2yMh/3BbMwQGsDPm49OeBh98Mq+QdBswD0sNfrZYE4uL5BADYhvlw42uMwyJEw6RqMqwXtfCiFhswQ1UI+fV7Deh4n7JImq9O3WRYyaR8HMtezBBqyzgiooADKMZcZKAMygr2zZ3E9r9ehj2Yi8ywQsgv/Xlg7GnApqzb3+xJbP/rthzlOIZQ28YRETXwDS7iHTxV775muXdMkFWumD6Hk9iGfShq8tsxNSwpwMid9e5rtq2RCbKScrGiPVf5ip7fjrUPDCJqVCXOYCNmYSNm6X6n104ZQPY7yv/zTq/GIMmyrPWCDA243W5YrVZAAmJtelZW7sMt+wApSrkXuFFqi67Pvhuz7zUVUJZjcblgsVia3FdcEBGRIYQSROI+mnFEZJj67Lsx++4fEYVCWBB1TALyL+tbsyQZqLmi/ECMVFt0ffbdmH1fa1OCMBQ8WU3NEnnCWO8T5SQGg4gaFfgK/cPvF7q//Sv0VGUpj4ypyrdckfS/SwdGIBGpjVw6cB4n8SH2YykqwRuYtQcMImrAfUG5U+qVj5SLBhu9yllWru85XaQs5XH/UGWiaUvneCWgB/KxDJkYBi9qG726W0IUuiANj2MahmAmTmEnSjAlpLlt1HrxymoKKFsBvJcBVCgXVjc71cL/esUe5biyFeHXzsZkzMVppEO5tLq5KSb+19ORg7k4hWzw0uq2jEFEAIDP5wH7XwC836q/8aFcpxy3/wXlfdTKxRxMwArE4G7VN3yMRgxi0AETsAK5mKO+OLUKDCJC2Qqg9DeRea/S3wBlxaHvn43JGAMlvcJdisR/3BjMQzaeC+s9SCwGkcG5Lyiz3iPpwIvK+zYnAT0wDosiuijbOCxCAnpE5P1IPwwig/vnFGXpjUjy1Snv25x8LEM0TBFdlC0aMcgHl2hsa1QF0dy5cyFJUoMtPT1dq7aRxq6fVr4dU3tOqDlynfK+15v4Zr0rHkQmhqk+J9ScaMQgE8OQBP5etiWqR0SZmZmorKwMbJ988okW7SIdnF6q7S19Tge/iQcew1RNb2X0OHgbj7ZE9a+hyWRCUlKSFm0hnTk/jPxoyE+uU9YTCqY3RkR8NOQXjRj0Ri42YpYm70+Rp3pE9NVXX8FmsyE1NRX5+fm4dOmSFu0ijX138/srpjXkPqdMD7mdGXFIRKqmtRPRE2bEalqDIkdVED366KNYvXo1tm/fjqKiIly4cAGDBg3CzZs3gx7j8XjgdrsbbCTeHSsfakFW5qjd7vYVH7UgIQqJGt5OmyJL1Uez3Nz/3cc3KysLjz76KLp3745NmzZh8uTGr2x1OBz4/e9/37JWUsT5POLqmGDWpbZedajlWvTP0j333IMf/OAHKC9v5J+97xUWFsLlcgU2p9PZkpIUIVE6/R1trE4d9ElBvepQy7UoiKqrq3Hu3Dl07Rp8xSWz2QyLxdJgI/GsaUCELt8JTvq+zm2uoRwyfJqWVmbpB/8HkloXVUH0y1/+Evv27cPFixfx6aef4ic/+Qmio6PxzDPPaNU+0khMnLKUh5YsPRtft8iDGlyDtmfKr+Ec1y1qQ1QF0eXLl/HMM8+gV69e+OlPf4qEhAQcPHgQiYmJWrWPNGQfoe11RPbc4K+fxIeaXkd0Ek1cO0Ctjqpfww0bNmjVDhIgY6qynpAW5DrlvmPB7MdSDMFMTWpHIwb70MTVlNTqcK6ZgXXKUBY1i/SoSDIp79vUzQ8rcQansDPioyIvanEKOyN200fSB4PI4AYtU+4dH0lRJuV9m1OCKfCiNqKz772oRQlCmHFLrQqDyOAsKUB2hD+eZS8ObdnYb3ARGzAzorPvN2AGl41tgxhEhPTngYffjMx7DZgHpKtYtfUAirEFrwFA2CMj/3FbMAcHsDKs9yCxuHg+AQD6vQZ0vE9ZJM1Xp24yrGRSPo5lL1YXQn7bMB9ufI1xWIRomFRNhvWiFl7UYgNmMITaMI6IKCD9eWDsacCmrF/f7Els/+u2HOW4cELI7wCKMRcZKIOycn9zJ7H9r5dhD+YikyHUxnFERA1YUoCRO+vd12xbIxNkJeViRXuu8hV9U9+OqfENLuIdPFXvvma5d0yQVa6YPoeT2IZ9KOK3Y+0Eg4ga1SkDyH5H+X+97/RaiTPYiFnYiFm806tBSLIsa70YRANutxtWqxWQgFibnpWV+3DLPkCKUu4FbpTaouuz78bse00FlKVgXK5m55iKCyIiMoRQgkjcRzOOiAxTn303Zt/9I6JQCAuijklA/mV9a5YkAzVXlB+IkWqLrs++G7Pva21KEIaCX98TkXAMIiISjkFERMIxiIhIOAYREQnHICIi4RhERCQcg4iIhFMdRFeuXMH48eORkJCADh064KGHHkJpaakWbSMig1B1ZfX169eRnZ2NnJwcbNu2DYmJifjqq6/QqVMnrdpHRAagKojeeust2O12rFq1KvBcSkoIixMTETVB1UezDz74AA8//DDGjh2LLl26oG/fvli+fHmTx3g8Hrjd7gYbEVF9qoLo/PnzKCoqwgMPPIAdO3Zg2rRpmDlzJtasWRP0GIfDAavVGtjsdnuLG01E7YuqIPL5fOjXrx/mz5+Pvn374he/+AVeeOEFLF26NOgxhYWFcLlcgc3pdLa40UTUvqgKoq5duyIjI6PBcw8++CAuXboU9Biz2QyLxdJgIyKqT1UQZWdn4+zZsw2e+/LLL9G9e/eINoqIjEVVEL388ss4ePAg5s+fj/Lycqxbtw5//etfUVBQoFX7iMgAVAXRgAEDsHnzZqxfvx69e/fGG2+8gYULFyI/P1+r9hGRAaheKjYvLw95eXlatIWIDIpzzYhIOAYREQnHICIi4RhERCQcg4iIhGMQEZFwDCIiEo5BRETCSbIsy3oWdLvdsFqtgATE2vSsrNyHW/YBUpRyL3Cj1BZdn303Zt9rKgDIgMvlanayu7ggIiJDCCWIVE/xiBiOiAxTn303Zt/9I6JQCAuijklA/mV9a5YkAzVXlB+IkWqLrs++G7Pva21KEIaCJ6uJSDgGEREJxyAiIuEYREQkHIOIiIRjEBGRcAwiIhKOQUREwqkKoh49ekCSpDs23k6IiFpC1ZXVR44cgdfrDTw+efIkhg4dirFjx0a8YURkHKqCKDExscHjBQsWoGfPnnj88ccj2igiMpaw55p99913WLt2LV555RVIkhR0P4/HA4/HE3jsdrvDLUlE7VTYJ6u3bNmCGzduYNKkSU3u53A4YLVaA5vdbg+3JBG1U2EHUXFxMXJzc2GzNb2WR2FhIVwuV2BzOp3hliSidiqsj2b//ve/sWvXLrz//vvN7ms2m2E2m8MpQ0QGEdaIaNWqVejSpQtGjhwZ6fYQkQGpDiKfz4dVq1Zh4sSJMJnELfBIRO2H6iDatWsXLl26hOeee06L9hCRAake0gwbNgw6r7dPRO0c55oRkXAMIiISjkFERMIxiIhIOAYREQnHICIi4RhERCScJOt8UZDb7YbVagUkILbp+bIRx3ugs+/su35qKgDIgMvlgsViaXJfcUFERIYQShCJmyzGEZFh6rPvxuy7f0QUCmFB1DEJyL+sb82SZKDmivIDMVJt0fXZd2P2fa1NCcJQ8GQ1EQnHICIi4RhERCQcg4iIhGMQEZFwDCIiEo5BRETCMYiISDhVQeT1evH6668jJSUFHTp0QM+ePfHGG29wDWsiahFVV1a/9dZbKCoqwpo1a5CZmYnS0lI8++yzsFqtmDlzplZtJKJ2TlUQffrppxg9enTgxoo9evTA+vXrcfjwYU0aR0TGoOqj2Y9+9CPs3r0bX375JQDg+PHj+OSTT5Cbm6tJ44jIGFSNiGbPng2324309HRER0fD6/Vi3rx5yM/PD3qMx+OBx+MJPHa73eG3lojaJVUjok2bNqGkpATr1q3D559/jjVr1uCPf/wj1qxZE/QYh8MBq9Ua2Ox2e4sbTUTti6og+tWvfoXZs2dj3LhxeOihh/Dzn/8cL7/8MhwOR9BjCgsL4XK5ApvT6Wxxo4mofVH10ezWrVuIimqYXdHR0fD5fEGPMZvNMJvN4bWOiAxBVRCNGjUK8+bNQ7du3ZCZmYkvvvgCf/7zn/Hcc89p1T4iMgBVQbRo0SK8/vrrmD59Oq5evQqbzYYpU6bgt7/9rVbtIyIDUBVE8fHxWLhwIRYuXKhRc4jIiDjXjIiEYxARkXAMIiISjkFERMIxiIhIOAYREQnHICIi4RhERCScJOu8zqvL5cI999wDQLkft55uVQGQAUhAxyTj1BZdn30XU1t0ff9972/cuAGr1drkvroH0eXLl7kUCJGBOJ1OJCcnN7mP7kHk8/lQUVGB+Ph4SJKk6li32w273Q6n0wmLxaJRC1tnffbdeLVF129pbVmWcfPmTdhstjtW7bidqrlmkRAVFdVsOjbHYrEI+aVoDfXZd+PVFl2/JbWb+0jmx5PVRCQcg4iIhGtTQWQ2m/G73/1O2IqPIuuz78arLbq+nrV1P1lNRHS7NjUiIqL2iUFERMIxiIhIOAYREQnXpoLos88+Q3R0NEaOHKlbzUmTJkGSpMCWkJCA4cOH41//+pdubaiqqsKMGTOQmpoKs9kMu92OUaNGYffu3ZrWrd/3mJgY3HfffRg6dChWrlzZ5L3stKhffxs+fLjmtZuqX15ernntqqoqzJo1C2lpabj77rtx3333ITs7G0VFRbh165ZmdSdNmoQxY8bc8fzevXshSRJu3LihSd02FUTFxcWYMWMG9u/fj4qKCt3qDh8+HJWVlaisrMTu3bthMpmQl5enS+2LFy+if//++Pjjj/H222/jxIkT2L59O3JyclBQUKB5fX/fL168iG3btiEnJwezZs1CXl4e6urqdKtff1u/fr3mdZuqn5KSomnN8+fPo2/fvti5cyfmz5+PL774Ap999hl+/etfY+vWrdi1a5em9UXQfYpHuKqrq7Fx40aUlpaiqqoKq1evxpw5c3SpbTabkZSkTF1OSkrC7NmzMWjQIFy7dg2JiYma1p4+fTokScLhw4cRGxsbeD4zM1OXG1vW7/v999+Pfv364Yc//CGeeOIJrF69Gs8//7xu9UUQUX/69OkwmUwoLS1t8DNPTU3F6NGj0R6vuGkzI6JNmzYhPT0dvXr1wvjx47Fy5UohP5Dq6mqsXbsWaWlpSEhI0LTWf/7zH2zfvh0FBQUNfiH9/Mup6G3IkCHo06cP3n//fSH127NvvvkGO3fuDPozB6B6snhb0GaCqLi4GOPHjwegDJddLhf27dunS+2tW7ciLi4OcXFxiI+PxwcffICNGzc2O6O4pcrLyyHLMtLT0zWtE4709HRcvHhR8zr1/+z92/z58zWvG6z+2LFjNa3n/5n36tWrwfP33ntvoA2vvvqqpm1o7M88NzdX05pt4qPZ2bNncfjwYWzevBkAYDKZ8LOf/QzFxcUYPHiw5vVzcnJQVFQEALh+/Treffdd5Obm4vDhw+jevbtmdVvzEFyWZV3+Za7/Z+/XuXNnzesGqx9slKK1w4cPw+fzIT8/Hx6PR9Najf2ZHzp0KDAQ0EKbCKLi4mLU1dXBZrMFnpNlGWazGYsXLw55qYFwxcbGIi0tLfB4xYoVsFqtWL58Od58803N6j7wwAOQJAllZWWa1QjXmTNnND9pC9z5Z683veunpaVBkiScPXu2wfOpqakAgA4dOmjehsb6fPnyZU1rtvqPZnV1dfjb3/6GP/3pTzh27FhgO378OGw2m67foPhJkoSoqCj897//1bRO586d8dRTT2HJkiWoqam543Wtvkptzscff4wTJ07g6aefFlK/PUtISMDQoUOxePHiRn/m7VWrHxFt3boV169fx+TJk+8Y+Tz99NMoLi7G1KlTNW2Dx+NBVVUVAOWj2eLFi1FdXY1Ro0ZpWhcAlixZguzsbDzyyCP4wx/+gKysLNTV1eGjjz5CUVERzpw5o2l9f9+9Xi++/vprbN++HQ6HA3l5eZgwYYKmtevXr89kMuHee+/VvLYo7777LrKzs/Hwww9j7ty5yMrKQlRUFI4cOYKysjL0799fdBMjT27l8vLy5BEjRjT62qFDh2QA8vHjxzWrP3HiRBnK8uMyADk+Pl4eMGCA/Pe//12zmrerqKiQCwoK5O7du8t33XWXfP/998s//vGP5T179mhat37fTSaTnJiYKD/55JPyypUrZa/Xq2nt2+vX33r16qV5bX/90aNH61LrdhUVFfKLL74op6SkyDExMXJcXJz8yCOPyG+//bZcU1OjWd1gfd6zZ48MQL5+/bomdbkMCBEJ1+rPERFR+8cgIiLhGEREJByDiIiEYxARkXAMIiISjkFERMIxiIhIOAYREQnHICIi4RhERCQcg4iIhPt/kWo4zMTZT44AAAAASUVORK5CYII=\n"
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def plot_othello_board(board, ax=None) -> 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",
" The image generated will be shown directly.\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 a matplotlib axis object can be defined that is used to place the board as a sublot into a bigger context.\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(\"#66FF00\")\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": [
"110 ms ± 7.6 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": [
"9.34 s ± 430 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., ..., 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 ...,\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 \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 [[-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 \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 [[-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 \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([[[ 3., 5.],\n [ 5., 3.],\n [ 5., 3.],\n ...,\n [ 5., 3.],\n [ 5., 3.],\n [ 3., 5.]],\n \n [[ 2., 3.],\n [ 5., 4.],\n [ 5., 4.],\n ...,\n [ 5., 4.],\n [ 5., 4.],\n [ 4., 5.]],\n \n [[ 3., 2.],\n [ 6., 5.],\n [ 3., 5.],\n ...,\n [ 2., 5.],\n [ 5., 5.],\n [ 5., 6.]],\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": "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A2e+GT9RLrgenwxksJbB8fgDAwNOF32m+okMNsYnAxnGcdlR9BMZTI5PBjMZJuuntL/SF4lEdPToUYXDYXmeF/f2/f39qqioUFdXl4qKilKQkAyZksH1+GRIXgbf9zUwMKCysjKFQu7edU4/kSGbMrgeP5syWOioRPtJcr8/XI9PBjJYy5DOfkr7K32hUEjl5eUJP05RUZGzA4QMtjK4Hp8Mycng8hW+UfQTGbIxg+vxsyWD645KVj9J7veH6/HJQAZrGdLRT1zIBQAAAACyGIs+AAAAAMhiGbfoKygo0Oc+9zkVFBSQIcczuB6fDLYyWGBhHshABivjk8Ee13PhenwykMFahnSOn/YLuQAAAAAA0ifjXukDAAAAAMSORR8AAAAAZDEWfQAAAACQxVj0AQAAAEAWy6hFX2trq/Ly8nT77benfewVK1bI87yxrxkzZujd7363nnvuubRnefnll3XvvffquuuuU0FBgSoqKrRkyRI1NzenfOzx85Cfn68rr7xSt912m77xjW8oEomkfPxzM4z/eve7352W8SfL0dHRkZbxX375ZX384x/X7Nmzdemll+rKK6/UzTffrE2bNmlwcDDl469YsULvec97zvvvv/zlL+V5nl577bWUZ7CGjqKfzs3hqqNc95PktqPop/PRT/TTuTnop9z6GyqjFn2bN2/Wvffeq7179+ro0aNpH//d7363XnrpJb300ktqbm7WlClTtHjx4rRmeP7551VXV6ef//znWrdundra2vT444/rlltu0apVq9KSYXQenn/+ee3evVu33HKLPv7xj2vx4sV6/fXX05ph/Ne2bdvSMvZkOa699tqUj/uHP/xBN954o37605/qwQcf1DPPPKPW1lbdf//92rlzp/bs2ZPyDDhfrncU/XR+Dpcd5aqfJDrKIvqJfjo3B/2UW/00xXWAWB0/flzf/e53tX//fr388svaunWr/vEf/zGtGQoKCnTVVVdJkq666ip95jOf0YIFC/Tqq69q5syZacnw0Y9+VJ7n6amnntJll1029t+vv/56ffCDH0xLhvHzcPXVV+stb3mL/uzP/kwNDQ3aunWr/uZv/iatGVxyleOjH/2opkyZov379591HFx33XW68847xZ1Y0o+Oop8myuGKywx0lC30E/00UQ5X6Kf0y5hX+r73ve+pqqpKc+fO1fvf/3594xvfcLpTjh8/rm9961uaPXu2ZsyYkZYx/+///k+PP/64Vq1addZBOmr69OlpyXEh73znO1VbW6sf/OAHzjLkimPHjumnP/3phMeBJHmel+ZUyPWOop8wio6yh36inxCVy/2UMYu+zZs36/3vf7+k6EvCfX19euKJJ9KaYefOnZo2bZqmTZumcDisH/3oR/rud7+rUCg909jR0SHf91VVVZWW8eJVVVWl559/Pi1jjd8Xo18PPvhgWsa+WI73ve99KR9z9DiYO3fuWf/9iiuuGMvx6U9/OuU5pAvvh8bGxrSMbU2udxT9dDYLHeWinyQ7HUU/nUE/0U/j0U/u+0lKf0dlxNs7Dx8+rKeeekqPPfaYJGnKlCn6q7/6K23evFnveMc70pbjlltu0aZNmyRJvb29+trXvqbGxkY99dRTmjVrVsrHt/5ys+/7aXt2ZPy+GHX55ZenZeyL5ZjoWaN0eOqppxSJRHTXXXdpaGgoLWNeaD/85je/GfvjIlfQUfTTuSx0lKV+ktLfUfRTFP1EP52LfjpfLvwNlRGLvs2bN+v1119XWVnZ2H/zfV8FBQX6yle+ouLi4rTkuOyyyzR79uyx7//rv/5LxcXF+vrXv65/+Zd/Sfn4c+bMked5OnToUMrHCqK9vT1tH8I9d1+44iLH7Nmz5XmeDh8+fNZ/v+666yRJU6dOTVuWC/3v7+7uTtv4VtBR9NO5LHSUqwxWOop+iqKf6Kdz0U/u+0lKf0eZf3vn66+/rkcffVQbNmzQs88+O/Z18OBBlZWVObli4yjP8xQKhXTy5Mm0jHf55ZfrL/7iL/TVr35VJ06cOO/nLi9B/fOf/1xtbW1aunSpswy5YsaMGbrtttv0la985YLHAdKLjoqinzCKjrKDfoqinzAql/vJ/Ct9O3fuVG9vrz70oQ+d92zU0qVLtXnzZv393/99WrIMDQ3p5ZdflhR9a8JXvvIVHT9+XEuWLEnL+JL01a9+VTfffLPe9ra36Z//+Z9VU1Oj119/XT/72c+0adMmtbe3pzzD6DyMjIzolVde0eOPP661a9dq8eLF+sAHPpDy8cdnGG/KlCm64oor0jK+a1/72td0880366abbtLnP/951dTUKBQKad++fTp06JDq6upcR8wZdNQZ9NP5Ocajo+iodKOfzqCfzs8xHv2UA/3kG7d48WJ/0aJFF/zZb37zG1+Sf/DgwZTnuOeee3xJY1/hcNh/61vf6u/YsSPlY5/r6NGj/qpVq/xZs2b5l1xyiX/11Vf7d9xxh/+LX/wi5WOPn4cpU6b4M2fO9G+99Vb/G9/4hj8yMpLy8c/NMP5r7ty5aRl/fI4777wzrWOOd/ToUf9jH/uYf+211/r5+fn+tGnT/Le97W3+unXr/BMnTqR8/In+9//iF7/wJfm9vb0pz2ABHXW2XO+nc3O46ijX/eT7bjuKfoqin85GP9FPo3LxbyjP941/uhUAAAAAEJj5z/QBAAAAAIJj0QcAAAAAWYxFHwAAAABkMRZ9AAAAAJDFWPQBAAAAQBZj0QcAAAAAWYxFHwAAAABkMRZ9AAAAAJDFWPQBAAAAQBZj0QcAAAAAWYxFHwAAAABkMRZ9AAAAAJDF/j8/p2BkXTaztQAAAABJRU5ErkJggg==\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
}