aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--imago/engine/core.py10
-rw-r--r--imago/engine/decisionAlgorithm.py4
-rw-r--r--imago/engine/decisionAlgorithmFactory.py8
-rw-r--r--imago/engine/imagoIO.py4
-rw-r--r--imago/engine/keras.py16
-rw-r--r--imago/engine/keras/keras.py27
-rw-r--r--imago/engine/keras/neuralNetwork.py60
-rw-r--r--imago/engine/monteCarlo.py6
-rw-r--r--imago/gameLogic/gameMove.py2
-rwxr-xr-ximagocli.py18
-rw-r--r--models/testModel.h5bin0 -> 43120 bytes
11 files changed, 116 insertions, 39 deletions
diff --git a/imago/engine/core.py b/imago/engine/core.py
index 09ee51d..810b851 100644
--- a/imago/engine/core.py
+++ b/imago/engine/core.py
@@ -5,7 +5,7 @@ from imago.gameLogic.gameState import GameState
DEF_SIZE = 7
DEF_KOMI = 5.5
-DEF_ALGORITHM = DecisionAlgorithms.MONTECARLO
+DEF_ALGORITHM = DecisionAlgorithms.KERAS
class GameEngine:
"""Plays the game of Go."""
@@ -23,7 +23,7 @@ class GameEngine:
It is wise to call clear_board after this command.
"""
self.gameState = GameState(newSize)
- self.da = self.da.__init__(self.gameState.lastMove)
+ self.da = self.daFactory.create(self.daId, self.gameState.lastMove)
def clearBoard(self):
"""The board is cleared, the number of captured stones reset to zero and the move
@@ -55,10 +55,8 @@ class GameEngine:
def genmove(self, color):
"""Returns a list representing coordinates of the board in the form (row, col)."""
- coords = self.da.pickMove().coords
- #TODO: The move should NOT be played in its generation. This method is just for
- #suggesting a move.
- #self.gameState.playMoveForPlayer(coords[0], coords[1], color)
+ coords = self.da.pickMove()
+ self.play(color, [coords[0], coords[1]])
return coords
def undo(self):
diff --git a/imago/engine/decisionAlgorithm.py b/imago/engine/decisionAlgorithm.py
index e3f4b15..5727d25 100644
--- a/imago/engine/decisionAlgorithm.py
+++ b/imago/engine/decisionAlgorithm.py
@@ -12,3 +12,7 @@ class DecisionAlgorithm:
def pickMove(self):
"""Returns a move to play."""
raise NotImplementedError("Method pickMove not implemented on type %s" % type(self))
+
+ def clearBoard(self):
+ """Empties move history."""
+ raise NotImplementedError("Method clearBoard not implemented on type %s" % type(self))
diff --git a/imago/engine/decisionAlgorithmFactory.py b/imago/engine/decisionAlgorithmFactory.py
index c40c687..bd86864 100644
--- a/imago/engine/decisionAlgorithmFactory.py
+++ b/imago/engine/decisionAlgorithmFactory.py
@@ -2,6 +2,7 @@
from enum import Enum, auto as enumAuto
from imago.engine.monteCarlo import MCTS
+from imago.engine.keras.keras import Keras
class DecisionAlgorithms(Enum):
RANDOM = enumAuto()
@@ -19,5 +20,8 @@ class DecisionAlgorithmFactory:
if algorithm == DecisionAlgorithms.MONTECARLO:
return MCTS(move)
-# if algorithm == DecisionAlgorithms.KERAS:
-# --
+ if algorithm == DecisionAlgorithms.KERAS:
+ return Keras(move)
+
+ else:
+ return MCTS(move)
diff --git a/imago/engine/imagoIO.py b/imago/engine/imagoIO.py
index 371c447..6ada674 100644
--- a/imago/engine/imagoIO.py
+++ b/imago/engine/imagoIO.py
@@ -30,7 +30,7 @@ def getCoordsText(row, col):
class ImagoIO:
"""Recieves and handles commands."""
- def __init__(self):
+ def __init__(self, decisionAlgorithmId = None):
self.commands_set = {
protocol_version,
name,
@@ -47,7 +47,7 @@ class ImagoIO:
self.genmove,
self.undo
}
- self.gameEngine = GameEngine()
+ self.gameEngine = GameEngine(decisionAlgorithmId)
def start(self):
"""Starts reading commands interactively."""
diff --git a/imago/engine/keras.py b/imago/engine/keras.py
deleted file mode 100644
index 5445d92..0000000
--- a/imago/engine/keras.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""Keras neural network module."""
-
-from imago.engine.decisionAlgorithm import DecisionAlgorithm
-
-class Keras(DecisionAlgorithm):
-
- def __init__(self, move):
- pass
-
- def forceNextMove(self, coords):
- """Selects given move as next move."""
- pass
-
- def pickMove(self):
- """Returns a move to play."""
- pass
diff --git a/imago/engine/keras/keras.py b/imago/engine/keras/keras.py
new file mode 100644
index 0000000..a6aa913
--- /dev/null
+++ b/imago/engine/keras/keras.py
@@ -0,0 +1,27 @@
+"""Keras neural network module."""
+
+from imago.gameLogic.gameMove import GameMove
+from imago.gameLogic.gameBoard import GameBoard
+from imago.engine.decisionAlgorithm import DecisionAlgorithm
+from imago.engine.keras.neuralNetwork import NeuralNetwork
+
+MODEL_FILE = "models/testModel.h5"
+
+class Keras(DecisionAlgorithm):
+
+ def __init__(self, move):
+ self.currentMove = move
+ self.boardSize = move.board.getBoardHeight()
+ self.nn = NeuralNetwork(MODEL_FILE, self.boardSize)
+
+ def forceNextMove(self, coords):
+ """Selects given move as next move."""
+ self.currentMove = self.currentMove.addMoveByCoords(coords)
+
+ def pickMove(self):
+ """Returns a move to play."""
+ return self.nn.pickMove(self.currentMove)
+
+ def clearBoard(self):
+ """Empties move history."""
+ self.currentMove = GameMove(GameBoard(self.boardSize, self.boardSize))
diff --git a/imago/engine/keras/neuralNetwork.py b/imago/engine/keras/neuralNetwork.py
index 26cb19e..41b8b23 100644
--- a/imago/engine/keras/neuralNetwork.py
+++ b/imago/engine/keras/neuralNetwork.py
@@ -1,15 +1,14 @@
"""Keras neural network."""
+import sys
import os.path
+
import numpy
-from copy import deepcopy
+from matplotlib import pyplot
-import tensorflow as tf
-from tensorflow import keras
from tensorflow.keras.models import Sequential, load_model
-from tensorflow.keras.layers import Dense, Flatten
+from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
-from tensorflow.keras.metrics import categorical_crossentropy
from imago.data.enums import Player
@@ -20,6 +19,7 @@ OPPONENT_ID = -1
class NeuralNetwork:
def __init__(self, modelPath="", boardSize=DEF_BOARD_SIZE):
+ self.boardSize = boardSize
self.path = defaultModelFile
if modelPath != "":
self.path = modelPath
@@ -59,8 +59,8 @@ class NeuralNetwork:
x=trainMoves,
y=targets,
validation_split=0.1,
- batch_size=10,
- epochs=30,
+ batch_size=1,
+ epochs=20,
shuffle=False,
verbose=2
)
@@ -94,7 +94,7 @@ class NeuralNetwork:
OPPONENT_ID marks the stones of the other player."""
boardRows = len(board)
boardCols = len(board[0])
- contextBoard = numpy.zeros( (boardRows, boardCols), dtype = int)
+ contextBoard = numpy.zeros((boardRows, boardCols), dtype = int)
for row in range(boardRows):
for col in range(boardCols):
if board[row][col] != Player.EMPTY:
@@ -114,3 +114,47 @@ class NeuralNetwork:
target[move.getRow()][move.getCol()] = 1
targets.append(target.tolist())
return targets
+
+ def pickMove(self, gameMove):
+ prediction = self._predict(gameMove)[0]
+ #self.showHeatmap(prediction)
+ playableVertices = gameMove.getPlayableVertices()
+ highest = sys.float_info.min
+ hRow = -1
+ hCol = -1
+ for row in range(self.boardSize):
+ for col in range(self.boardSize):
+ #print("[%d, %d] %s" % (row, col, (row, col) in playableVertices))
+ if prediction[row][col] > highest and (row, col) in playableVertices:
+ hRow = row
+ hCol = col
+ highest = prediction[row][col]
+ return [hRow, hCol]
+
+ def _predict(self, gameMove):
+ board = gameMove.board.board
+ player = gameMove.getPlayer()
+ sampleBoard = self._boardToPlayerContext(board, player)
+ sampleBoard = numpy.array([sampleBoard])
+ return self.model.predict(
+ x = sampleBoard,
+ batch_size = 1,
+ verbose = 2)
+
+ def showHeatmap(self, data):
+ fig, ax = pyplot.subplots()
+ im = ax.imshow(data)
+
+ # Show all ticks and label them with the respective list entries
+ ax.set_xticks(numpy.arange(9))
+ ax.set_yticks(numpy.arange(9))
+
+ # Loop over data dimensions and create text annotations.
+ for i in range(9):
+ for j in range(9):
+ text = ax.text(j, i, data[i, j],
+ ha="center", va="center", color="w")
+
+ ax.set_title("Harvest of local farmers (in tons/year)")
+ fig.tight_layout()
+ pyplot.show()
diff --git a/imago/engine/monteCarlo.py b/imago/engine/monteCarlo.py
index 3f28d8d..2113fdd 100644
--- a/imago/engine/monteCarlo.py
+++ b/imago/engine/monteCarlo.py
@@ -29,8 +29,8 @@ class MCTS(DecisionAlgorithm):
# completely random
for _ in range(5):
self.root.selection().expansion().simulation(10, 20)
- self.root = self.selectBestNextNode()
- return self.root.move
+ selectedNode = self.selectBestNextNode()
+ return selectedNode.move.coords
def selectBestNextNode(self):
"""Returns best ucb node available for the current player."""
@@ -169,7 +169,7 @@ class MCTSNode:
selectedMove = random.choice(list(sensibleMoves))
currentMove = currentMove.addMoveByCoords(selectedMove)
score = currentMove.board.score()
- print("Current move: %s" % (currentMove.toString()))
+ print("Current move: %s" % (str(currentMove)))
print("Current move game length: ", currentMove.getGameLength())
print("Score of the board: %d, %d (%d)"
% (score[0],
diff --git a/imago/gameLogic/gameMove.py b/imago/gameLogic/gameMove.py
index cdd2fd6..c1c7a05 100644
--- a/imago/gameLogic/gameMove.py
+++ b/imago/gameLogic/gameMove.py
@@ -132,7 +132,7 @@ class GameMove:
return previousMoves
return self.nextMoves[0].getMainLineOfPlay(previousMoves)
- def toString(self):
+ def __str__(self):
"""Returns the coordinates of the move as a string."""
if self.isPass:
return "Pass"
diff --git a/imagocli.py b/imagocli.py
index e8e4580..150587e 100755
--- a/imagocli.py
+++ b/imagocli.py
@@ -2,8 +2,24 @@
"""Run the Imago engine."""
+import sys
+
from imago.engine.imagoIO import ImagoIO
+from imago.engine.decisionAlgorithmFactory import DecisionAlgorithms
if __name__ == "__main__":
- io = ImagoIO()
+
+ decisionAlgorithm = None
+ if len(sys.argv) >= 3:
+ if sys.argv[1] == "-e":
+ if sys.argv[2] == "montecarlo":
+ decisionAlgorithm = DecisionAlgorithms.MONTECARLO
+ if sys.argv[2] == "keras":
+ decisionAlgorithm = DecisionAlgorithms.KERAS
+
+ if decisionAlgorithm is None:
+ io = ImagoIO()
+ else:
+ io = ImagoIO(decisionAlgorithm)
+
io.start()
diff --git a/models/testModel.h5 b/models/testModel.h5
new file mode 100644
index 0000000..6bc5fe2
--- /dev/null
+++ b/models/testModel.h5
Binary files differ