aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorInigoGutierrez <inigogf.95@gmail.com>2023-01-11 19:20:06 +0100
committerInigoGutierrez <inigogf.95@gmail.com>2023-01-11 19:20:06 +0100
commit80c4cca827ff80c0508c27cd9b6a37ffa2ea17e5 (patch)
tree95bb1bbad5fb44f4025d8a9f69ed8163ce42fa46
parent824d5e3b6954407a694f5739cbeb40f66324c8d7 (diff)
downloadimago-80c4cca827ff80c0508c27cd9b6a37ffa2ea17e5.tar.gz
imago-80c4cca827ff80c0508c27cd9b6a37ffa2ea17e5.zip
100% coverage on imagoIO module.
-rw-r--r--imago/engine/imagoIO.py50
-rw-r--r--imago/engine/monteCarlo.py2
-rw-r--r--imago/engine/parseHelpers.py14
-rw-r--r--tests/test_imagoIO.py164
-rw-r--r--tests/test_parseHelpers.py36
5 files changed, 221 insertions, 45 deletions
diff --git a/imago/engine/imagoIO.py b/imago/engine/imagoIO.py
index c983949..1b63f18 100644
--- a/imago/engine/imagoIO.py
+++ b/imago/engine/imagoIO.py
@@ -17,6 +17,7 @@ def getCoordsText(row, col):
class ImagoIO:
"""Recieves and handles commands."""
+
def __init__(self, decisionAlgorithmId=None, outputStream=sys.stdin):
self.commands_set = {
self.protocol_version,
@@ -37,13 +38,14 @@ class ImagoIO:
self.gameEngine = GameEngine(decisionAlgorithmId)
self.outputStream = outputStream
+
def _response(self, text=""):
print("= %s" % text, file=self.outputStream)
print(file=self.outputStream)
def _responseError(self, text=""):
- print("? %s" % text, file=self.outputStream)
+ print("? %s" % text.replace('\n', '\n? '), file=self.outputStream)
print(file=self.outputStream)
@@ -71,14 +73,14 @@ class ImagoIO:
else:
self._responseError("unknown command")
except Exception as err:
- self._responseError("An uncontrolled error ocurred. The error was: %s" % err)
+ self._responseError("Error: %s" % err)
def known_command(self, args):
- """True if command is known, false otherwise"""
+ """True if command is known, false otherwise."""
if len(args) != 1:
- self._responseError("Wrong number of arguments.")
- self._responseError("Usage: known_command COMMAND_NAME")
+ self._responseError("Wrong number of arguments\n" +
+ "Usage: known_command COMMAND_NAME")
return
out = "false"
for c in self.commands_set:
@@ -116,13 +118,14 @@ class ImagoIO:
It is wise to call clear_board after this command.
"""
if len(args) != 1:
- self._responseError("Wrong number of arguments")
- self._responseError("Usag. boardsize <newSize>")
+ self._responseError("Wrong number of arguments\n" +
+ "Usage: boardsize <newSize>")
return
size = int(args[0])
self.gameEngine.setBoardsize(size)
self._response()
+
def clear_board(self, _):
"""The board is cleared, the number of captured stones reset to zero and the move
history reset to empty.
@@ -130,23 +133,25 @@ class ImagoIO:
self.gameEngine.clearBoard()
self._response()
+
def komi(self, args):
"""Sets a new value of komi."""
if len(args) != 1:
- self._responseError("Wrong number of arguments")
- self._responseError("Usage: komi <newKomi>")
+ self._responseError("Wrong number of arguments\n" +
+ "Usage: komi <newKomi>")
return
komi = float(args[0])
self.gameEngine.setKomi(komi)
self._response()
+
def fixed_handicap(self, args):
"""Handicap stones are placed on the board on standard vertices.
These vertices follow the GTP specification.
"""
if len(args) != 1:
- self._responseError("Wrong number of arguments")
- self._responseError("Usage: fixed_handicap <count>")
+ self._responseError("Wrong number of arguments\n" +
+ "Usage: fixed_handicap <count>")
return
stones = float(args[0])
vertices = self.gameEngine.setFixedHandicap(stones)
@@ -155,29 +160,36 @@ class ImagoIO:
out += " " + getCoordsText(vertex[0], vertex[1])
self._response(out)
+
def place_free_handicap(self, args):
"""Handicap stones are placed on the board by the AI criteria."""
#TODO
+
def set_free_handicap(self, args):
"""Handicap stones are placed on the board as requested."""
#TODO
+
def play(self, args):
"""A stone of the requested color is played at the requested vertex."""
if len(args) != 2:
- self._responseError("Wrong number of arguments.")
- self._responseError("Usage: play <color> <vertex>")
+ self._responseError("Wrong number of arguments\n" +
+ "Usage: play <color> <vertex>")
return
- move = parseHelpers.parseMove(args, self.gameEngine.gameState.size)
- self.gameEngine.play(move.color, move.vertex)
- self._response()
+ try:
+ move = parseHelpers.parseMove(args, self.gameEngine.gameState.size)
+ self.gameEngine.play(move.color, move.vertex)
+ self._response()
+ except Exception as err:
+ self._responseError("Invalid move: %s" % err)
+
def genmove(self, args):
"""A stone of the requested color is played where the engine chooses."""
if len(args) != 1:
- self._responseError("Wrong number of arguments.")
- self._responseError("Usage: genmove <color>")
+ self._responseError("Wrong number of arguments\n" +
+ "Usage: genmove <color>")
return
color = parseHelpers.parseColor(args[0])
output = parseHelpers.vertexToString(self.gameEngine.genmove(color),
@@ -185,8 +197,10 @@ class ImagoIO:
self._response(output)
self.gameEngine.gameState.getBoard().printBoard()
+
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.
"""
self.gameEngine.undo()
+ self._response()
diff --git a/imago/engine/monteCarlo.py b/imago/engine/monteCarlo.py
index f4712e6..ee8380a 100644
--- a/imago/engine/monteCarlo.py
+++ b/imago/engine/monteCarlo.py
@@ -15,7 +15,7 @@ class MCTS(DecisionAlgorithm):
self.simulationsPerExpansion = 10
self.debug = False
- def forceNextMove(self, coords):
+ def forceNextMove(self, coords: tuple):
"""Selects given move as next move."""
for node in self.root.children:
if (node.move.isPass and coords == "pass" or
diff --git a/imago/engine/parseHelpers.py b/imago/engine/parseHelpers.py
index fc42845..64342d3 100644
--- a/imago/engine/parseHelpers.py
+++ b/imago/engine/parseHelpers.py
@@ -19,7 +19,7 @@ def parseMove(args, boardsize):
"""Converts the textual representation of a move to a move instance."""
if len(args) != 2:
raise RuntimeError(
- "Unable to transform string %s to move: Wrong format."
+ "Unable to transform string [%s] to move: Wrong format."
% args)
color = parseColor(args[0])
vertex = parseVertex(args[1], boardsize)
@@ -32,7 +32,7 @@ def parseColor(text):
return Player.WHITE
if textUpper in VALID_BLACK_STRINGS:
return Player.BLACK
- raise RuntimeError("Unknown color %s." % text)
+ raise RuntimeError("Unknown color [%s]." % text)
def parseVertex(text, boardSize):
"""Returns row and column of a vertex given its input string. A vertex can also be the
@@ -47,7 +47,7 @@ def parseVertex(text, boardSize):
if text == "PASS":
return "pass"
raise RuntimeError(
- "Unable to transform string %s to vertex. Wrong format."
+ "Unable to transform string [%s] to vertex. Wrong format."
% text)
vertexCol = ord(text[0])
@@ -61,10 +61,10 @@ def parseVertex(text, boardSize):
if (vertexCol < 0 or vertexRow < 0
or vertexCol >= boardSize or vertexRow >= boardSize):
raise RuntimeError(
- "Unable to transform string %s to vertex. Maps to [%d, %d], which is out of board bounds (size %d)"
+ "Unable to transform string [%s] to vertex. Maps to [%d, %d], which is out of board bounds (size [%d])"
% (text, vertexCol, vertexRow, boardSize))
- return [vertexRow, vertexCol]
+ return (vertexRow, vertexCol)
def vertexToString(vertex, boardSize):
"""Returns a string representing the vertex.
@@ -76,11 +76,11 @@ def vertexToString(vertex, boardSize):
return "pass"
if len(vertex) != 2:
raise RuntimeError(
- "Unable to transform vertex %s to string. Too many elements."
+ "Unable to transform vertex [%s] to string. Too many elements."
% str(vertex))
if vertex[0] >= boardSize or vertex[1] >= boardSize or vertex[0] < 0 or vertex[1] < 0:
raise RuntimeError(
- "Unable to transform vertex %s to string. Vertex out of board bounds (size %d)"
+ "Unable to transform vertex [%s] to string. Vertex out of board bounds (size [%d])"
% (str(vertex), boardSize))
vertexRow = boardSize - vertex[0]
diff --git a/tests/test_imagoIO.py b/tests/test_imagoIO.py
index c3c6fda..4f2d7bd 100644
--- a/tests/test_imagoIO.py
+++ b/tests/test_imagoIO.py
@@ -5,36 +5,194 @@ import unittest
import io
import sys
+from imago.data.enums import DecisionAlgorithms
from imago.engine.imagoIO import ImagoIO
+from imago.engine.parseHelpers import parseVertex
class TestImagoIO(unittest.TestCase):
"""Test ImagoIO component."""
@unittest.mock.patch('imago.engine.imagoIO.input', create=True)
-
def testSimpleCommands(self, mocked_input):
"""Test simple commands."""
+ self.maxDiff = None
+
mocked_input.side_effect = [
+ '\n',
'name\n',
'version\n',
'protocol_version\n',
+ 'clear_board\n',
+ '\n',
+ 'known_command\n',
+ 'known_command name\n',
+ 'known_command version\n',
+ 'known_command wrongcommand\n',
+ '\n',
+ 'boardsize\n',
+ 'boardsize 10\n',
+ '\n',
+ 'komi\n',
+ 'komi 5.5\n',
+ '\n',
+ 'play\n',
+ 'play 1\n',
+ 'play 1 2\n',
+ 'play b a1\n',
+ '\n',
+ 'undo\n',
+ 'undo\n',
+ '\n',
+ 'wrongcommand\n',
+ '\n',
'quit\n'
]
testout = io.StringIO()
- imagoIO = ImagoIO(outputStream=testout)
+ imagoIO = ImagoIO(
+ decisionAlgorithmId=DecisionAlgorithms.MONTECARLO,
+ outputStream=testout
+ )
imagoIO.start()
value = testout.getvalue()
self.assertEqual(
'= Imago\n\n' +
'= 0.0.0\n\n' +
- '= 2\n\n',
+ '= 2\n\n' +
+ '= \n\n' +
+ '? Wrong number of arguments\n' +
+ '? Usage: known_command COMMAND_NAME\n\n' +
+ '= true\n\n' +
+ '= true\n\n' +
+ '= false\n\n' +
+ '? Wrong number of arguments\n' +
+ '? Usage: boardsize <newSize>\n\n' +
+ '= \n\n' +
+ '? Wrong number of arguments\n' +
+ '? Usage: komi <newKomi>\n\n' +
+ '= \n\n' +
+ '? Wrong number of arguments\n' +
+ '? Usage: play <color> <vertex>\n\n' +
+ '? Wrong number of arguments\n' +
+ '? Usage: play <color> <vertex>\n\n' +
+ '? Invalid move: Unknown color [1].\n\n' +
+ '= \n\n' +
+ '= \n\n' +
+ '= \n\n' +
+ '? unknown command\n\n',
+ value
+ )
+
+ testout.close()
+
+
+ @unittest.mock.patch('imago.engine.imagoIO.input', create=True)
+ def testListsCommands(self, mocked_input):
+ """Test command for listing all commands."""
+
+ mocked_input.side_effect = [
+ 'list_commands\n',
+ 'quit\n'
+ ]
+
+ testout = io.StringIO()
+ imagoIO = ImagoIO(
+ decisionAlgorithmId=DecisionAlgorithms.MONTECARLO,
+ outputStream=testout
+ )
+
+ commandsString = "\n".join(list(map(
+ lambda cmd: "%s - %s" % (cmd.__name__, cmd.__doc__),
+ imagoIO.commands_set)))
+
+ imagoIO.start()
+ value = testout.getvalue()
+ self.assertEqual(
+ '= ' +
+ commandsString +
+ '\n\n\n',
value
)
testout.close()
+
+ @unittest.mock.patch('imago.engine.imagoIO.input', create=True)
+ def testFixedHandicap(self, mocked_input):
+ """Test command for setting fixed handicap stones."""
+
+ mocked_input.side_effect = [
+ 'fixed_handicap\n',
+ 'fixed_handicap 2\n',
+ 'quit\n'
+ ]
+
+ testout = io.StringIO()
+ imagoIO = ImagoIO(
+ decisionAlgorithmId=DecisionAlgorithms.MONTECARLO,
+ outputStream=testout
+ )
+
+ imagoIO.start()
+ value = testout.getvalue()
+ self.assertEqual(
+ '? Wrong number of arguments\n' +
+ '? Usage: fixed_handicap <count>\n\n' +
+ '= A1 A2\n\n',
+ value
+ )
+
+ testout.close()
+
+
+# @unittest.mock.patch('imago.engine.imagoIO.input', create=True)
+# def testGenmove(self, mocked_input):
+# """Test command for generating a move."""
+#
+# mocked_input.side_effect = [
+# 'genmove\n',
+# 'genmove w w\n',
+# 'genmove 2\n',
+# 'quit\n'
+# ]
+#
+# testout = io.StringIO()
+# imagoIO = ImagoIO(
+# decisionAlgorithmId=DecisionAlgorithms.MONTECARLO,
+# outputStream=testout
+# )
+#
+# imagoIO.start()
+# value = testout.getvalue()
+# self.assertEqual(
+# '? Wrong number of arguments\n' +
+# '? Usage: genmove <color>\n\n' +
+# '? Wrong number of arguments\n' +
+# '? Usage: genmove <color>\n\n' +
+# '? Error: Unknown color [2].\n\n',
+# value
+# )
+#
+# mocked_input.side_effect = [
+# 'genmove w\n',
+# 'quit\n']
+#
+# testout = io.StringIO()
+# imagoIO = ImagoIO(
+# decisionAlgorithmId=DecisionAlgorithms.MONTECARLO,
+# outputStream=testout
+# )
+# imagoIO.start()
+# value = testout.getvalue()
+# vertexValue = value.split(' ')[1].split('\n')[0]
+#
+# # Test parsing vertex does not raise an error
+# parseVertex(vertexValue, imagoIO.gameEngine.gameState.size)
+#
+# testout.close()
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_parseHelpers.py b/tests/test_parseHelpers.py
index 7bbf152..c1405fb 100644
--- a/tests/test_parseHelpers.py
+++ b/tests/test_parseHelpers.py
@@ -26,7 +26,7 @@ class TestParseHelpers(unittest.TestCase):
)
parsedMove = parseHelpers.parseMove(["B", "t1"], TEST_BOARD_SIZE)
- targetMove = parseHelpers.GtpMove(Player.BLACK, [18, 18])
+ targetMove = parseHelpers.GtpMove(Player.BLACK, (18, 18))
self.assertEqual(parsedMove.color, targetMove.color)
self.assertEqual(parsedMove.vertex, targetMove.vertex)
@@ -54,26 +54,26 @@ class TestParseHelpers(unittest.TestCase):
"""Test correct inputs and their resulting coordinates for parseVertex."""
self.assertEqual(parseHelpers.parseVertex(
"a1", TEST_BOARD_SIZE),
- [18,0])
+ (18,0))
self.assertEqual(parseHelpers.parseVertex(
"b1", TEST_BOARD_SIZE),
- [18,1])
+ (18,1))
self.assertEqual(parseHelpers.parseVertex(
"a2", TEST_BOARD_SIZE),
- [17,0])
+ (17,0))
self.assertEqual(parseHelpers.parseVertex(
"b2", TEST_BOARD_SIZE),
- [17,1])
+ (17,1))
self.assertEqual(parseHelpers.parseVertex(
"T1", TEST_BOARD_SIZE),
- [18,18])
+ (18,18))
self.assertEqual(parseHelpers.parseVertex(
"T19", TEST_BOARD_SIZE),
- [0,18])
+ (0,18))
self.assertEqual(parseHelpers.parseVertex(
"A19", TEST_BOARD_SIZE),
- [0,0])
+ (0,0))
self.assertEqual(parseHelpers.parseVertex(
"pass", TEST_BOARD_SIZE),
@@ -81,10 +81,14 @@ class TestParseHelpers(unittest.TestCase):
def testVertexToString(self):
"""Test converting vertices to strings."""
- self.assertEqual(parseHelpers.vertexToString([0,0], TEST_BOARD_SIZE), "A19")
- self.assertEqual(parseHelpers.vertexToString([1,0], TEST_BOARD_SIZE), "A18")
- self.assertEqual(parseHelpers.vertexToString([2,0], TEST_BOARD_SIZE), "A17")
- self.assertEqual(parseHelpers.vertexToString([0,1], TEST_BOARD_SIZE), "B19")
+
+ # Try with vertices as tuples
+ self.assertEqual(parseHelpers.vertexToString((0,0), TEST_BOARD_SIZE), "A19")
+ self.assertEqual(parseHelpers.vertexToString((1,0), TEST_BOARD_SIZE), "A18")
+ self.assertEqual(parseHelpers.vertexToString((2,0), TEST_BOARD_SIZE), "A17")
+ self.assertEqual(parseHelpers.vertexToString((0,1), TEST_BOARD_SIZE), "B19")
+
+ # Try with vertices as arrays
self.assertEqual(parseHelpers.vertexToString([0,2], TEST_BOARD_SIZE), "C19")
self.assertEqual(parseHelpers.vertexToString([0,18], TEST_BOARD_SIZE), "T19")
self.assertEqual(parseHelpers.vertexToString([18,0], TEST_BOARD_SIZE), "A1")
@@ -93,10 +97,10 @@ class TestParseHelpers(unittest.TestCase):
self.assertEqual(parseHelpers.vertexToString("pass", TEST_BOARD_SIZE), "pass")
wrongVertices = [
- [-1,0],
- [0,-1],
- [-1,-1],
- [19,0],
+ (-1,0),
+ (0,-1),
+ (-1,-1),
+ (19,0),
[0,19],
[19,19],
[0],