aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorInigoGutierrez <inigogf.95@gmail.com>2023-06-12 19:43:40 +0200
committerInigoGutierrez <inigogf.95@gmail.com>2023-06-12 19:43:40 +0200
commit65ac3a6b050dcb88688cdc2654b1ed6693e9a160 (patch)
tree19797a3d1a2f897628d0413482117c27c9cfe6b9
parenta005228a986b17732ae7cccbedde450533cfe1f1 (diff)
downloadimago-65ac3a6b050dcb88688cdc2654b1ed6693e9a160.tar.gz
imago-65ac3a6b050dcb88688cdc2654b1ed6693e9a160.zip
Submitted version.
-rw-r--r--doc/Makefile2
-rw-r--r--doc/listings/testOutput.txt53
-rw-r--r--doc/tex/appendixes.tex48
-rw-r--r--doc/tex/imago.tex25
-rw-r--r--doc/tex/implementation.tex83
-rw-r--r--imago/engine/core.py1
-rw-r--r--imago/engine/imagoIO.py22
-rw-r--r--imago/engine/keras/convNeuralNetwork.py2
-rw-r--r--imago/engine/keras/denseNeuralNetwork.py2
-rw-r--r--imago/engine/keras/initialDenseNeuralNetwork.py28
-rw-r--r--imago/engine/keras/keras.py4
-rw-r--r--imago/engine/keras/neuralNetwork.py15
-rw-r--r--imago/gameLogic/gameBoard.py9
-rw-r--r--imago/gameLogic/gameData.old52
-rw-r--r--imago/gameLogic/gameData.py51
-rw-r--r--imago/sgfParser/astNode.py119
-rw-r--r--tests/test_gameBoard.py28
-rw-r--r--tests/test_gameState.py2
-rw-r--r--tests/test_imagoIO.py9
-rw-r--r--tests/test_monteCarlo.py2
-rw-r--r--tests/test_neuralNetwork.py68
-rw-r--r--tests/test_sgf.py27
22 files changed, 423 insertions, 229 deletions
diff --git a/doc/Makefile b/doc/Makefile
index cf6c166..472de8e 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -9,7 +9,7 @@ diagramImgs = diagrams/planningWorkPlanEngine.png diagrams/planningWorkPlanGame.
imgs = img/imago.jpg img/models/denseModel.png img/models/convModel.png
-listings = listings/denseModel.txt listings/convModel.txt listings/denseTraining.txt listings/convTraining.txt listings/trainCommand.sh
+listings = listings/denseModel.txt listings/convModel.txt listings/denseTraining.txt listings/convTraining.txt listings/trainCommand.sh listings/testOutput.txt
all: $(docName).pdf
diff --git a/doc/listings/testOutput.txt b/doc/listings/testOutput.txt
index 7d1cfda..e6fb2d6 100644
--- a/doc/listings/testOutput.txt
+++ b/doc/listings/testOutput.txt
@@ -1,29 +1,24 @@
-Invalid Move! Move outside board bounds.
-Name Stmts Miss Cover Missing
--------------------------------------------------------------------------------
-imago/data/enums.py 17 0 100%
-imago/engine/core.py 39 8 79% 43, 50-52, 60-62, 68
-imago/engine/createDecisionAlgorithm.py 11 5 55% 13-21
-imago/engine/decisionAlgorithm.py 9 4 56% 6, 10, 14, 18
-imago/engine/imagoIO.py 103 10 90% 75-76, 190-198, 205
-imago/engine/keras/convNeuralNetwork.py 12 12 0% 3-54
-imago/engine/keras/denseNeuralNetwork.py 12 12 0% 3-40
-imago/engine/keras/initialDenseNeuralNetwork.py 11 11 0% 3-28
-imago/engine/keras/keras.py 28 15 46% 16-27, 34-37, 41, 48-49
-imago/engine/keras/neuralNetwork.py 137 112 18% 23-31, 34, 37-46, 58-61, 66-69, 72-80, 85-95, 100-112, 117-139, 142-145, 151-186, 195-203, 206
-imago/engine/monteCarlo.py 110 8 93% 128, 181-190, 194-195
-imago/engine/parseHelpers.py 48 0 100%
-imago/gameLogic/gameBoard.py 199 26 87% 115, 177, 202, 269, 278-303, 311
-imago/gameLogic/gameData.py 24 24 0% 3-51
-imago/gameLogic/gameMove.py 95 13 86% 21, 27, 34, 131-134, 138-142, 146
-imago/gameLogic/gameState.py 42 0 100%
-imago/scripts/monteCarloSimulation.py 17 17 0% 3-25
-imago/sgfParser/astNode.py 125 125 0% 1-156
-imago/sgfParser/parsetab.py 18 18 0% 5-28
-imago/sgfParser/sgf.py 6 6 0% 3-13
-imago/sgfParser/sgflex.py 31 31 0% 5-71
-imago/sgfParser/sgfyacc.py 41 41 0% 5-71
--------------------------------------------------------------------------------
-TOTAL 1135 498 56%
-
-8 empty files skipped.
+Name Stmts Miss Cover Missing
+------------------------------------------------------------------------
+imago/data/enums.py 17 0 100%
+imago/engine/core.py 39 7 82% 43, 50-52, 60-62
+imago/engine/createDecisionAlgorithm.py 11 5 55% 13-21
+imago/engine/decisionAlgorithm.py 9 4 56% 6, 10, 14, 18
+imago/engine/imagoIO.py 107 9 92% 76-77, 189-196, 208
+imago/engine/keras/convNeuralNetwork.py 11 0 100%
+imago/engine/keras/denseNeuralNetwork.py 11 0 100%
+imago/engine/keras/keras.py 30 3 90% 48-49, 53
+imago/engine/keras/neuralNetwork.py 137 1 99% 141
+imago/engine/monteCarlo.py 110 7 94% 128, 181-188, 194-195
+imago/engine/parseHelpers.py 48 0 100%
+imago/gameLogic/gameBoard.py 205 7 97% 116, 180, 205, 272, 284, 306, 312
+imago/gameLogic/gameMove.py 95 9 91% 21, 27, 34, 138-142, 146
+imago/gameLogic/gameState.py 41 0 100%
+imago/scripts/monteCarloSimulation.py 17 17 0% 3-25
+imago/sgfParser/astNode.py 70 7 90% 10, 14, 22, 45, 59, 67, 157
+imago/sgfParser/parsetab.py 18 0 100%
+imago/sgfParser/sgf.py 6 0 100%
+imago/sgfParser/sgflex.py 31 9 71% 37-38, 42-43, 47-48, 64-65, 71
+imago/sgfParser/sgfyacc.py 41 14 66% 35-36, 51, 59-68, 71
+------------------------------------------------------------------------
+TOTAL 1054 99 91%
diff --git a/doc/tex/appendixes.tex b/doc/tex/appendixes.tex
index f9f189e..b4533d6 100644
--- a/doc/tex/appendixes.tex
+++ b/doc/tex/appendixes.tex
@@ -76,7 +76,7 @@ input or because of the \gls{ko} rule.
\subsubsection{The engine: the \texttt{imagocli.py} interface}
-\texttt{imagocli.py} is a text interface which follows the \acrshort{gtp}
+\texttt{imagocli.py} is a text interface which follows the \acrfull{gtp}
specification. It can be executed in a shell as:
{
@@ -87,8 +87,8 @@ specification. It can be executed in a shell as:
\par
}
-The \acrshort{ai} to be run can be passes as an argument to the \texttt{-e}
-option. The available arguments are:
+If desired, the \acrshort{ai} to be run can be passed as an argument to the
+\texttt{-e} option, but it is not necessary. The available arguments are:
\begin{itemize}
@@ -111,11 +111,11 @@ be executed as:
\par
}
-If no arguments are provided, the default configuration is to use the Monte
-Carlo Tree Search algorithm.
+If no arguments are provided the default configuration is to use the Monte Carlo
+Tree Search algorithm.
When executed interactively and before any input is provided it just waits for
-input, with no prompt whatsoever. This is in compliance with the \acrshort{gtp}
+input with no prompt whatsoever. This is in compliance with the \acrshort{gtp}
specification.
These are the commands that the program knows and a short description of what
@@ -125,8 +125,8 @@ each does:
\item \texttt{list\_commands}: Shows a list of the commands the engine
knows.
- \item \texttt{known\_command}: Receives an argument and tells wether it is a
- known command or not.
+ \item \texttt{known\_command}: Receives an argument and tells whether it is
+ a known command or not.
\item \texttt{name}: Shows the name of the program.
\item \texttt{version}: Shows the version of the program.
\item \texttt{protocol\_version}: Shows the implemented \acrshort{gtp}
@@ -320,13 +320,23 @@ The costs are calculated based on a programmer salary of 20€/hour.
\midrule
Game preliminary research & 15 & 300 \\
\midrule
- Game implementation & 55 & 1100 \\
+ Game implementation & 95 & 1900 \\
\midrule
- Game unit testing & 50 & 1000 \\
+ Game unit testing & 90 & 1800 \\
\midrule
- Game system testing & 5 & 100 \\
+ Game system testing & 15 & 300 \\
\midrule
- \textbf{Total} & \textbf{125} & \textbf{2500} \\
+ Engine preliminary research & 15 & 300 \\
+ \midrule
+ Engine implementation & 75 & 1500 \\
+ \midrule
+ Algorithms implementations & 135 & 2700 \\
+ \midrule
+ Engine testing & 75 & 1500 \\
+ \midrule
+ Results analysis & 30 & 600 \\
+ \midrule
+ \textbf{Total} & \textbf{545} & \textbf{10900} \\
\bottomrule
\end{tabular}
}
@@ -354,11 +364,11 @@ The costs are calculated based on a programmer salary of 20€/hour.
\toprule
\textbf{Category} & \textbf{Cost (€)} \\
\midrule
- Work & 2500 \\
+ Work & 10900 \\
\midrule
Materials & 600 \\
\midrule
- \textbf{Total} & \textbf{3100} \\
+ \textbf{Total} & \textbf{11500} \\
\bottomrule
\end{tabular}
}
@@ -372,17 +382,17 @@ The costs are calculated based on a programmer salary of 20€/hour.
\toprule
\textbf{Task} & \textbf{Cost (€)} \\
\midrule
- Game preliminary research & 300 \\
+ Game system development & 2200 \\
\midrule
- Game implementation & 1100 \\
+ Engine development & 4500 \\
\midrule
- Game unit testing & 1000 \\
+ Testing & 3600 \\
\midrule
- Game system testing & 100 \\
+ Result analysis & 600 \\
\midrule
Materials & 600 \\
\midrule
- \textbf{Total} & \textbf{3100} \\
+ \textbf{Total} & \textbf{11500} \\
\bottomrule
\end{tabular}
}
diff --git a/doc/tex/imago.tex b/doc/tex/imago.tex
index 4287330..ca72c7a 100644
--- a/doc/tex/imago.tex
+++ b/doc/tex/imago.tex
@@ -51,12 +51,13 @@
\includegraphics[width=0.3\textwidth]{img/logoEII.png}
\end{center}~\\[10pt]
\program\\
- \large An AI player of the game of Go
+ \large An AI player of the game of Go\\
+ \large (Juego Go basado en inteligencia artificial)\\
}
\author{Íñigo Gutiérrez Fernández}
-\date{}
+\date{Oviedo, June 2023}
\maketitle
@@ -71,12 +72,20 @@
\clearpage
\begin{abstract}
- The game of Go presents a complex problem for machine learning by virtue of
- containing a very wide and deep decision tree. This project has tried to
- tackle the problem by using different decision algorithms and also provides
- a full implementation of the game. These tools could be used by players and
- developers as a foundation for other machine learning projects or to simply
- keep studying the game.
+ With a history of more than 3000 years, the game of Go presents a complex
+ problem for machine learning by virtue of containing a very wide and deep
+ decision tree. Finally, in 2016, computer scientists from DeepMind were able
+ to create an artificial intelligence capable of defeating profesional
+ players of the game with a combination of old and new strategies. This
+ project has tried to follow their steps and tackle the problem by using
+ different decision algorithms, such as Monte Carlo Tree Search and neural
+ networks, and also provides a full implementation of the game, playable on
+ its own or available as a library for the engine developed for this project
+ and potentially others to come. The resulting strength of the developed
+ algorithms is no match to that of a profesional player, but it shows the
+ possibilities achievable just with the limited resources employed on this
+ project. These tools could be used by players and developers as a foundation
+ for other machine learning projects or to simply keep studying the game.
\end{abstract}
\clearpage
diff --git a/doc/tex/implementation.tex b/doc/tex/implementation.tex
index 4970c14..d3cd0c3 100644
--- a/doc/tex/implementation.tex
+++ b/doc/tex/implementation.tex
@@ -98,6 +98,9 @@ A version control tool widely used in software development environments
allows to store and manage snapshots of a project, navigate through them and
diverge into different development branches, among other useful features.
+The source code of this document and of the rest of the project is publicly
+available at \url{https://git.taamas.xyz/Taamas/imago}.
+
\subsubsection{Documentation Tools}
\paragraph{\LaTeX}
@@ -260,11 +263,13 @@ The script used to run the tests is shown on \lref{lst:test} and its output on
\subsubsection{Usability Testing}
-A human user was asked to interact with the interfaces of \program{} and
-presented with a questionary. The profile of this user is of someone who has
-played some Go matches and knows the fundamentals of the game but is a beginner,
-and who has little experience with computers outside of their usage as office
-tools and internet browsers. Here are their answers.
+Two human users were asked to interact with the interfaces of \program{} and
+presented with a questionary.
+
+The profile of the first user is of someone who has played some Go matches and
+knows the fundamentals of the game but is a beginner, and who has little
+experience with computers outside of their usage as office tools and internet
+browsers. Here are their answers.
\vspace{\interclassSpace}
@@ -330,3 +335,71 @@ tools and internet browsers. Here are their answers.
Yes.\\
\bottomrule
\end{tabular}
+
+The profile of the second user is of someone who has experience with computers
+and works as a software developer, but who has just the bare minimum knowledge
+of the game of Go. Here are their answers.
+
+\vspace{\interclassSpace}
+
+\begin{tabular}{p{0.4\linewidth}p{0.6\linewidth}}
+ \toprule
+ \multicolumn{2}{c}{\textbf{Playing against a human}} \\
+ \midrule
+ \textbf{Question} & \textbf{Answer} \\
+ \midrule
+ Were you able to start the interface? &
+ Yes, I was able to. \\
+ \midrule
+ How hard was the interface of the game to understand? &
+ I think six out of ten. Some people won't understand what a command is and
+ without a visual interface they could feel confused about it.\\
+ \bottomrule
+\end{tabular}
+
+\vspace{\interclassSpace}
+
+\begin{tabular}{p{0.4\linewidth}p{0.6\linewidth}}
+ \toprule
+ \multicolumn{2}{c}{\textbf{Playing against the engine}} \\
+ \midrule
+ \textbf{Question} & \textbf{Answer} \\
+ \midrule
+ Were you able to start the interface? &
+ Yes, I was. \\
+ \midrule
+ How hard was the interface of the game to understand? &
+ I think seven out of ten. I was expecting to follow some steps so I could
+ execute it at the same time I was reading the manual, but in the end the
+ most practical thing was read everything before execute commands.\\
+ \midrule
+ How strong did you find the engine? &
+ It followed good paths to win the game, I didn't feel random moves during
+ the game by its side.\\
+ \bottomrule
+\end{tabular}
+
+\vspace{\interclassSpace}
+
+\begin{tabular}{p{0.4\linewidth}p{0.6\linewidth}}
+ \toprule
+ \multicolumn{2}{c}{\textbf{Playing against the interface through a
+ third-party}} \\
+ \midrule
+ \textbf{Question} & \textbf{Answer} \\
+ \midrule
+ Were you able to start the interface? &
+ Yes, I was. \\
+ \midrule
+ Did you find any problems when setting up the engine? &
+ No, I didn't.\\
+ \midrule
+ Do you think this tool has value for studying Go? &
+ I think it is a good tool for a group of players, so they can practise and
+ even train the AI if they want to.\\
+ \bottomrule
+\end{tabular}
+
+The results of these usability tests were useful mostly to update the manual and
+make it easier to understand and follow and also to address some problems with
+the interfaces that raised up during the testing.
diff --git a/imago/engine/core.py b/imago/engine/core.py
index 47c8f16..0f88dfd 100644
--- a/imago/engine/core.py
+++ b/imago/engine/core.py
@@ -4,6 +4,7 @@ from imago.data.enums import DecisionAlgorithms
from imago.engine.createDecisionAlgorithm import create as createDA
from imago.gameLogic.gameState import GameState
+
DEF_SIZE = 9
DEF_KOMI = 5.5
DEF_ALGORITHM = DecisionAlgorithms.KERAS
diff --git a/imago/engine/imagoIO.py b/imago/engine/imagoIO.py
index ae1210b..75a1a99 100644
--- a/imago/engine/imagoIO.py
+++ b/imago/engine/imagoIO.py
@@ -6,7 +6,6 @@ from imago.engine import parseHelpers
from imago.engine.core import GameEngine
-
def getCoordsText(row, col):
"""Returns a string representation of row and col.
In GTP A1 is bottom left corner.
@@ -91,33 +90,33 @@ class ImagoIO:
def list_commands(self, _):
- """List of commands, one per row"""
+ """List of commands, one per row."""
output = ""
for c in self.commands_set:
output += ("%s - %s\n" % (c.__name__, c.__doc__))
+ output = output[:-1] # Remove last newline character
self._response(output)
def protocol_version(self, _):
- """Version of the GTP Protocol"""
+ """Version of the GTP Protocol."""
self._response("2")
def name(self, _):
- """Name of the engine"""
+ """Name of the engine."""
self._response("Imago")
def version(self, _):
- """Version of the engine"""
+ """Version of the engine."""
self._response("0.0.0")
def boardsize(self, args):
"""Changes the size of the board.
Board state, number of stones and move history become arbitrary.
- It is wise to call clear_board after this command.
- """
+ It is wise to call clear_board after this command."""
if len(args) != 1:
self._responseError("Wrong number of arguments\n" +
"Usage: boardsize <newSize>")
@@ -129,8 +128,7 @@ class ImagoIO:
def clear_board(self, _):
"""The board is cleared, the number of captured stones reset to zero and the move
- history reset to empty.
- """
+ history reset to empty."""
self.gameEngine.clearBoard()
self._response()
@@ -148,8 +146,7 @@ class ImagoIO:
def fixed_handicap(self, args):
"""Handicap stones are placed on the board on standard vertices.
- These vertices follow the GTP specification.
- """
+ These vertices follow the GTP specification."""
if len(args) != 1:
self._responseError("Wrong number of arguments\n" +
"Usage: fixed_handicap <count>")
@@ -200,8 +197,7 @@ class ImagoIO:
def undo(self, _):
"""The board configuration and number of captured stones are reset to the state
- before the last move, which is removed from the move history.
- """
+ before the last move, which is removed from the move history."""
self.gameEngine.undo()
self._response()
diff --git a/imago/engine/keras/convNeuralNetwork.py b/imago/engine/keras/convNeuralNetwork.py
index 638e2fe..7e6e63c 100644
--- a/imago/engine/keras/convNeuralNetwork.py
+++ b/imago/engine/keras/convNeuralNetwork.py
@@ -43,7 +43,7 @@ class ConvNeuralNetwork(NeuralNetwork):
),
])
- model.summary()
+ #model.summary()
model.compile(
optimizer=Adam(learning_rate=0.0001),
diff --git a/imago/engine/keras/denseNeuralNetwork.py b/imago/engine/keras/denseNeuralNetwork.py
index 6a350f7..4b4c0e0 100644
--- a/imago/engine/keras/denseNeuralNetwork.py
+++ b/imago/engine/keras/denseNeuralNetwork.py
@@ -29,7 +29,7 @@ class DenseNeuralNetwork(NeuralNetwork):
),
])
- model.summary()
+ #model.summary()
model.compile(
optimizer=Adam(learning_rate=0.0001),
diff --git a/imago/engine/keras/initialDenseNeuralNetwork.py b/imago/engine/keras/initialDenseNeuralNetwork.py
deleted file mode 100644
index dfe8379..0000000
--- a/imago/engine/keras/initialDenseNeuralNetwork.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""Dense neural network."""
-
-from tensorflow.keras.models import Sequential
-from tensorflow.keras.layers import Dense
-from tensorflow.keras.optimizers import Adam
-
-from imago.engine.keras.neuralNetwork import NeuralNetwork
-
-defaultModelFile = 'models/imagoDenseKerasModel.h5'
-
-class DenseNeuralNetwork(NeuralNetwork):
-
- def _initModel(self, boardSize=NeuralNetwork.DEF_BOARD_SIZE):
- model = Sequential([
- Dense(units=16, activation='relu', input_shape=(boardSize,boardSize)),
- Dense(units=32, activation='relu'),
- Dense(units=boardSize, activation='softmax')
- ])
-
- model.summary()
-
- model.compile(
- optimizer=Adam(learning_rate=0.0001),
- loss='categorical_crossentropy',
- metrics=['accuracy']
- )
-
- return model
diff --git a/imago/engine/keras/keras.py b/imago/engine/keras/keras.py
index 3d60b8e..b3061e8 100644
--- a/imago/engine/keras/keras.py
+++ b/imago/engine/keras/keras.py
@@ -47,3 +47,7 @@ class Keras(DecisionAlgorithm):
"""Empties move history."""
boardSize = self.currentMove.board.getBoardHeight()
self.currentMove = GameMove(GameBoard(boardSize, boardSize))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/imago/engine/keras/neuralNetwork.py b/imago/engine/keras/neuralNetwork.py
index c414a78..9d0b853 100644
--- a/imago/engine/keras/neuralNetwork.py
+++ b/imago/engine/keras/neuralNetwork.py
@@ -7,6 +7,9 @@ import os.path
import numpy
from matplotlib import pyplot
+# Disable TensorFlow importing warnings
+os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
+
from tensorflow.keras.models import load_model
from tensorflow.keras.utils import plot_model
@@ -28,12 +31,12 @@ class NeuralNetwork:
self.model = self._loadModel(self.path)
except FileNotFoundError:
self.model = self._initModel(boardSize)
- self.saveModelPlot("model.png")
+ #self.saveModelPlot("model.png")
def _initModel(self, boardSize=DEF_BOARD_SIZE):
raise NotImplementedError("Tried to directly use NeuralNetwork class. Use one of the subclasses instead.")
- def trainModel(self, games):
+ def trainModel(self, games, epochs=20, verbose=2):
trainMoves = []
targets = []
for game in games:
@@ -48,14 +51,14 @@ class NeuralNetwork:
y=targets,
validation_split=0.1,
batch_size=1,
- epochs=20,
+ epochs=epochs,
shuffle=False,
- verbose=2
+ verbose=verbose
)
def _loadModel(self, modelPath):
# Load model
- if os.path.isfile(modelPath):
+ if os.path.isfile(modelPath) or os.path.isdir(modelPath):
return load_model(modelPath)
else:
raise FileNotFoundError("Keras neural network model file not found at %s"
@@ -145,7 +148,7 @@ class NeuralNetwork:
return self.model.predict(
x = sampleBoards,
batch_size = 1,
- verbose = 2)
+ verbose = 0)
def _saveHeatmap(self, data, passChance):
rows = len(data)
diff --git a/imago/gameLogic/gameBoard.py b/imago/gameLogic/gameBoard.py
index 170a7cb..d0e22a6 100644
--- a/imago/gameLogic/gameBoard.py
+++ b/imago/gameLogic/gameBoard.py
@@ -110,9 +110,12 @@ class GameBoard:
containing the vertices where stones were captured.
"""
- if (row < 0 or row >= self.getBoardHeight()
- or col < 0 or col >= self.getBoardWidth()):
- raise RuntimeError("[ERROR] Move and capture: out of bounds (%d, %d)" % (row, col))
+ try:
+ if (row < 0 or row >= self.getBoardHeight()
+ or col < 0 or col >= self.getBoardWidth()):
+ raise RuntimeError("[ERROR] Move and capture: out of bounds (%d, %d)" % (row, col))
+ except Exception as err:
+ raise RuntimeError("[ERROR] Move and capture: Wrong input: %s" % err)
self.board[row][col] = player
diff --git a/imago/gameLogic/gameData.old b/imago/gameLogic/gameData.old
new file mode 100644
index 0000000..f23723f
--- /dev/null
+++ b/imago/gameLogic/gameData.old
@@ -0,0 +1,52 @@
+"""Invariable data pertaining a match."""
+
+#class GameData:
+ """Invariable data pertaining a match."""
+
+# Obsolete method of storing properties
+# def __init__(self,
+# name = None,
+# size = 19,
+# annotator = None,
+# date = None,
+# blackRank = "?",
+# whiteRank = "?",
+# blackName = None,
+# whiteName = None,
+# blackTeam = None,
+# whiteTeam = None,
+# copyrightInfo = None,
+# event = None,
+# gameComment = None,
+# openingInfo = None,
+# timeInfo = None,
+# overtimeInfo = None,
+# placeInfo = None,
+# result = "?",
+# roundInfo = None,
+# rules = None,
+# source = None,
+# user = None
+# ):
+# self.name = name
+# self.size = size
+# self.annotator = annotator
+# self.date = date
+# self.blackRank = blackRank
+# self.whiteRank = whiteRank
+# self.blackName = blackName
+# self.whiteName = whiteName
+# self.blackTeam = blackTeam
+# self.whiteTeam = whiteTeam
+# self.copyrightInfo = copyrightInfo
+# self.event = event
+# self.gameComment = gameComment
+# self.openingInfo = openingInfo
+# self.timeInfo = timeInfo
+# self.overtimeInfo = overtimeInfo
+# self.placeInfo = placeInfo
+# self.result = result
+# self.roundInfo = roundInfo
+# self.rules = rules
+# self.source = source
+# self.user = user
diff --git a/imago/gameLogic/gameData.py b/imago/gameLogic/gameData.py
deleted file mode 100644
index 6cc4d7e..0000000
--- a/imago/gameLogic/gameData.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Invariable data pertaining a match."""
-
-class GameData:
- """Invariable data pertaining a match."""
-
- def __init__(self,
- name = None,
- size = 19,
- annotator = None,
- date = None,
- blackRank = "?",
- whiteRank = "?",
- blackName = None,
- whiteName = None,
- blackTeam = None,
- whiteTeam = None,
- copyrightInfo = None,
- event = None,
- gameComment = None,
- openingInfo = None,
- timeInfo = None,
- overtimeInfo = None,
- placeInfo = None,
- result = "?",
- roundInfo = None,
- rules = None,
- source = None,
- user = None
- ):
- self.name = name
- self.size = size
- self.annotator = annotator
- self.date = date
- self.blackRank = blackRank
- self.whiteRank = whiteRank
- self.blackName = blackName
- self.whiteName = whiteName
- self.blackTeam = blackTeam
- self.whiteTeam = whiteTeam
- self.copyrightInfo = copyrightInfo
- self.event = event
- self.gameComment = gameComment
- self.openingInfo = openingInfo
- self.timeInfo = timeInfo
- self.overtimeInfo = overtimeInfo
- self.placeInfo = placeInfo
- self.result = result
- self.roundInfo = roundInfo
- self.rules = rules
- self.source = source
- self.user = user
diff --git a/imago/sgfParser/astNode.py b/imago/sgfParser/astNode.py
index 41629ce..5071483 100644
--- a/imago/sgfParser/astNode.py
+++ b/imago/sgfParser/astNode.py
@@ -1,4 +1,4 @@
-from imago.gameLogic.gameData import GameData
+#from imago.gameLogic.gameData import GameData
from imago.gameLogic.gameMove import GameMove
from imago.gameLogic.gameBoard import GameBoard
from imago.data.enums import Player
@@ -22,64 +22,6 @@ class ASTNode:
children = children[0].children
children.append(move)
- def toGameTree(self):
- """Converts this node and its subtree into a GameTree"""
- gameData = GameData()
- for prop in self.props:
- if prop.name == "GM": # Type of game, 1 is Go
- if prop.value != 1:
- print("ERROR") # TODO: Error handling
- if prop.name == "SZ": # Size of board. [19] for squared, [10:12] for rect.
- gameData.size = prop.value
- if prop.name == "AN": # Annotator
- gameData.annotator = prop.value
- if prop.name == "BR": # Rank of black player
- gameData.blackRank = prop.value
- if prop.name == "WR": # Rank of white player
- gameData.whiteRank = prop.value
- if prop.name == "PB": # Name of black player
- gameData.blackName = prop.value
- if prop.name == "PW": # Name of white player
- gameData.whiteName = prop.value
- if prop.name == "BT": # Name of black team
- gameData.blackTeam = prop.value
- if prop.name == "WT": # Name of white team
- gameData.whiteTeam = prop.value
- if prop.name == "CP": # Copyright information
- gameData.copyright = prop.value
- if prop.name == "DT": # Date
- gameData.date = prop.value
- if prop.name == "EV": # Event information
- gameData.event = prop.value
- if prop.name == "GN": # Game nae
- gameData.name = prop.value
- if prop.name == "GC": # Extra game comment
- gameData.gameComment = prop.value
- if prop.name == "ON": # Description of opening played
- gameData.openingInfo = prop.value
- if prop.name == "OT": # Overtime method
- gameData.overtimeInfo = prop.value
- if prop.name == "PC": # Place where the game took place
- gameData.place = prop.value
- if prop.name == "RE": # Result of the game
- gameData.result = prop.value
- if prop.name == "RO": # Round number and type
- gameData.roundInfo = prop.value
- if prop.name == "RU": # Rules used for the game
- gameData.rules = prop.value
- if prop.name == "SO": # Source of the game
- gameData.source = prop.source
- if prop.name == "TM": # Time limit in seconds
- gameData.timeInfo = prop.source
- if prop.name == "US": # User or program which entered the game
- gameData.user = prop.source
-
- firstMoves = []
- for child in self.children:
- firstMoves.append(child.toGameMoveTree(size))
-
- return GameTree(firstMoves, gameData)
-
def toGameMoveTree(self, previousMove=None):
if previousMove is None:
# Game root node
@@ -138,6 +80,65 @@ def textToCoords(text): # Poner en PropertyMove, subclase de Property
row = ord(text[1]) - ord('a')
return [row, col]
+ # Obsolete method of storing properties
+ #def toGameTree(self):
+ # """Converts this node and its subtree into a GameTree"""
+ # gameData = GameData()
+ # for prop in self.props:
+ # if prop.name == "GM": # Type of game, 1 is Go
+ # if prop.value != 1:
+ # print("ERROR") # TODO: Error handling
+ # if prop.name == "SZ": # Size of board, [19] for squared, [10:12] for rect.
+ # gameData.size = prop.value
+ # if prop.name == "AN": # Annotator
+ # gameData.annotator = prop.value
+ # if prop.name == "BR": # Rank of black player
+ # gameData.blackRank = prop.value
+ # if prop.name == "WR": # Rank of white player
+ # gameData.whiteRank = prop.value
+ # if prop.name == "PB": # Name of black player
+ # gameData.blackName = prop.value
+ # if prop.name == "PW": # Name of white player
+ # gameData.whiteName = prop.value
+ # if prop.name == "BT": # Name of black team
+ # gameData.blackTeam = prop.value
+ # if prop.name == "WT": # Name of white team
+ # gameData.whiteTeam = prop.value
+ # if prop.name == "CP": # Copyright information
+ # gameData.copyright = prop.value
+ # if prop.name == "DT": # Date
+ # gameData.date = prop.value
+ # if prop.name == "EV": # Event information
+ # gameData.event = prop.value
+ # if prop.name == "GN": # Game nae
+ # gameData.name = prop.value
+ # if prop.name == "GC": # Extra game comment
+ # gameData.gameComment = prop.value
+ # if prop.name == "ON": # Description of opening played
+ # gameData.openingInfo = prop.value
+ # if prop.name == "OT": # Overtime method
+ # gameData.overtimeInfo = prop.value
+ # if prop.name == "PC": # Place where the game took place
+ # gameData.place = prop.value
+ # if prop.name == "RE": # Result of the game
+ # gameData.result = prop.value
+ # if prop.name == "RO": # Round number and type
+ # gameData.roundInfo = prop.value
+ # if prop.name == "RU": # Rules used for the game
+ # gameData.rules = prop.value
+ # if prop.name == "SO": # Source of the game
+ # gameData.source = prop.source
+ # if prop.name == "TM": # Time limit in seconds
+ # gameData.timeInfo = prop.source
+ # if prop.name == "US": # User or program which entered the game
+ # gameData.user = prop.source
+ #
+ # firstMoves = []
+ # for child in self.children:
+ # firstMoves.append(child.toGameMoveTree())
+ #
+ # return GameTree(firstMoves, gameData)
+
class Property:
"""Property of a Node"""
diff --git a/tests/test_gameBoard.py b/tests/test_gameBoard.py
index 8a7b127..c7808ac 100644
--- a/tests/test_gameBoard.py
+++ b/tests/test_gameBoard.py
@@ -114,5 +114,33 @@ class TestGameBoard(unittest.TestCase):
board.board[9][0] = Player.WHITE
self.assertEqual((9, 21), board.score())
+ def testToString(self):
+ """Test formatting of the board as a string."""
+
+ board = GameBoard(9, 9)
+ self.assertEqual(' A B C D E F G H J \n\
+9 · · · · · · · · · \n\
+8 · · · · · · · · · \n\
+7 · · · · · · · · · \n\
+6 · · · · · · · · · \n\
+5 · · · · · · · · · \n\
+4 · · · · · · · · · \n\
+3 · · · · · · · · · \n\
+2 · · · · · · · · · \n\
+1 · · · · · · · · · ', board.toString())
+
+ board.moveAndCapture(2, 6, Player.BLACK)
+ board.moveAndCapture(5, 4, Player.WHITE)
+ self.assertEqual(' A B C D E F G H J \n\
+9 · · · · · · · · · \n\
+8 · · · · · · · · · \n\
+7 · · · · · · B · · \n\
+6 · · · · · · · · · \n\
+5 · · · · · · · · · \n\
+4 · · · · W · · · · \n\
+3 · · · · · · · · · \n\
+2 · · · · · · · · · \n\
+1 · · · · · · · · · ', board.toString())
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_gameState.py b/tests/test_gameState.py
index 638e269..1c6b997 100644
--- a/tests/test_gameState.py
+++ b/tests/test_gameState.py
@@ -73,7 +73,7 @@ class TestGameState(unittest.TestCase):
])
- self.assertFalse(state.playMove(-1, -1))
+ self.assertRaises(Exception, state.playMove, -1, -1)
self.assertEqual(state.getBoard().getBoard(),
[
[Player.EMPTY, Player.EMPTY, Player.EMPTY],
diff --git a/tests/test_imagoIO.py b/tests/test_imagoIO.py
index 4f2d7bd..499fdb2 100644
--- a/tests/test_imagoIO.py
+++ b/tests/test_imagoIO.py
@@ -81,7 +81,8 @@ class TestImagoIO(unittest.TestCase):
'= \n\n' +
'= \n\n' +
'= \n\n' +
- '? unknown command\n\n',
+ '? unknown command\n\n' +
+ '= \n\n',
value
)
@@ -112,7 +113,8 @@ class TestImagoIO(unittest.TestCase):
self.assertEqual(
'= ' +
commandsString +
- '\n\n\n',
+ '\n\n' +
+ '= \n\n',
value
)
@@ -140,7 +142,8 @@ class TestImagoIO(unittest.TestCase):
self.assertEqual(
'? Wrong number of arguments\n' +
'? Usage: fixed_handicap <count>\n\n' +
- '= A1 A2\n\n',
+ '= A1 A2\n\n' +
+ '= \n\n',
value
)
diff --git a/tests/test_monteCarlo.py b/tests/test_monteCarlo.py
index 496c073..9d1fcfc 100644
--- a/tests/test_monteCarlo.py
+++ b/tests/test_monteCarlo.py
@@ -66,7 +66,7 @@ class TestMonteCarlo(unittest.TestCase):
self.assertEqual(thirdMoveCoords, nextNode.move.coords)
#def testSimulation(self):
- # """Test calculation of group liberties."""
+ # """Test Monte Carlo simulation."""
# board = GameBoard(TEST_BOARD_SIZE, TEST_BOARD_SIZE)
# move = GameMove(board)
# node = MCTSNode(move, None)
diff --git a/tests/test_neuralNetwork.py b/tests/test_neuralNetwork.py
index dfcbd7a..42ba4a1 100644
--- a/tests/test_neuralNetwork.py
+++ b/tests/test_neuralNetwork.py
@@ -1,8 +1,16 @@
"""Tests for neural network module."""
+import os
+import shutil
import unittest
+from imago.data.enums import DecisionAlgorithms
+from imago.sgfParser.sgf import loadGameTree
+from imago.gameLogic.gameState import GameState
from imago.engine.keras.neuralNetwork import NeuralNetwork
+from imago.engine.keras.denseNeuralNetwork import DenseNeuralNetwork
+from imago.engine.keras.convNeuralNetwork import ConvNeuralNetwork
+from imago.engine.keras.keras import Keras
class TestNeuralNetwork(unittest.TestCase):
"""Test neural network module."""
@@ -13,3 +21,63 @@ class TestNeuralNetwork(unittest.TestCase):
self.assertRaises(NotImplementedError,
NeuralNetwork,
"non/existing/file")
+
+ def testNetworks(self):
+ """Test creation of initial model for dense neural network"""
+
+ testModel = 'testModel'
+ testModelPlot = 'testModelPlot'
+
+ games = []
+ for file in [
+ '../collections/minigo/matches/1.sgf',
+ '../collections/minigo/matches/2.sgf',
+ '../collections/minigo/matches/3.sgf'
+ ]:
+ games.append(loadGameTree(file))
+ matches = [game.getMainLineOfPlay() for game in games]
+
+ nn = DenseNeuralNetwork(modelPath=testModel, boardSize=9)
+ nn.trainModel(matches, epochs=1, verbose=0)
+
+ game = GameState(9)
+ nn.pickMove(game.lastMove, game.getCurrentPlayer())
+
+ nn.saveModel(testModel)
+ self.assertTrue(os.path.isdir(testModel))
+ shutil.rmtree(testModel, ignore_errors=True)
+
+ nn.saveModel()
+ self.assertTrue(os.path.isdir(testModel))
+ nn = DenseNeuralNetwork(modelPath=testModel, boardSize=9)
+
+ nn.saveModelPlot(testModelPlot)
+ self.assertTrue(os.path.isfile(testModelPlot))
+
+ shutil.rmtree(testModel, ignore_errors=True)
+ os.remove(testModelPlot)
+
+ nn = ConvNeuralNetwork(testModel, boardSize=9)
+
+ def testKeras(self):
+ """Test keras model loading."""
+
+ gameState = GameState(9)
+ move = gameState.lastMove
+
+ keras = Keras(move)
+ keras.forceNextMove("pass")
+
+ keras = Keras(move, DecisionAlgorithms.DENSE)
+ keras.forceNextMove((3,3))
+
+ keras = Keras(move, DecisionAlgorithms.CONV)
+ self.assertRaises(RuntimeError, keras.forceNextMove, "wrongmove")
+ pickedCoords = keras.pickMove()
+ self.assertTrue(len(pickedCoords) == 2 or pickedCoords == "pass")
+
+ self.assertRaises(RuntimeError, Keras, move, DecisionAlgorithms.MONTECARLO)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_sgf.py b/tests/test_sgf.py
new file mode 100644
index 0000000..1266429
--- /dev/null
+++ b/tests/test_sgf.py
@@ -0,0 +1,27 @@
+"""Tests for processing of SGF files."""
+
+import unittest
+
+from imago.sgfParser.sgfyacc import parser
+
+TESTING_SGF = 'tests/testingSGF.sgf'
+
+class TestSGF(unittest.TestCase):
+ """Test processing SGF files."""
+
+ def testToGameTree(self):
+ """Test converting file to GameTree"""
+
+ file = open(TESTING_SGF, "r")
+ text = file.read()
+ file.close()
+
+ astNode = parser.parse(text)
+
+ astNode.toGameMoveTree()
+
+ astNode.toString()
+
+
+if __name__ == '__main__':
+ unittest.main()