Initial commit
authorNeil Smith <neil.github@njae.me.uk>
Fri, 25 Sep 2009 15:27:26 +0000 (16:27 +0100)
committerNeil Smith <neil.github@njae.me.uk>
Fri, 25 Sep 2009 15:27:26 +0000 (16:27 +0100)
158 files changed:
README [new file with mode: 0644]
Rakefile [new file with mode: 0644]
game-lines-11.txt [new file with mode: 0644]
game-lines-12.txt [new file with mode: 0644]
game-lines-13.txt [new file with mode: 0644]
game-lines-15.txt [new file with mode: 0644]
game-lines-16.txt [new file with mode: 0644]
game-lines-17.txt [new file with mode: 0644]
game-lines-18.txt [new file with mode: 0644]
game-lines-19.txt [new file with mode: 0644]
game-lines-20.txt [new file with mode: 0644]
game-lines-21.txt [new file with mode: 0644]
game-lines-8.txt [new file with mode: 0644]
lib/.LCKlibcartagena.rb~ [new file with mode: 0644]
lib/.svn/all-wcprops [new file with mode: 0644]
lib/.svn/entries [new file with mode: 0644]
lib/.svn/format [new file with mode: 0644]
lib/.svn/prop-base/first-move.rb.svn-base [new file with mode: 0644]
lib/.svn/prop-base/random-move.rb.svn-base [new file with mode: 0644]
lib/.svn/text-base/first-move.rb.svn-base [new file with mode: 0644]
lib/.svn/text-base/game_handler.rb.svn-base [new file with mode: 0644]
lib/.svn/text-base/libcartagena.rb.netbeans-base [new file with mode: 0644]
lib/.svn/text-base/libcartagena.rb.svn-base [new file with mode: 0644]
lib/.svn/text-base/main.rb.svn-base [new file with mode: 0644]
lib/.svn/text-base/random-move.rb.netbeans-base [new file with mode: 0644]
lib/.svn/text-base/random-move.rb.svn-base [new file with mode: 0644]
lib/.svn/tmp/tempfile.2.tmp [new file with mode: 0644]
lib/.svn/tmp/tempfile.3.tmp [new file with mode: 0644]
lib/.svn/tmp/tempfile.4.tmp [new file with mode: 0644]
lib/.svn/tmp/tempfile.5.tmp [new file with mode: 0644]
lib/.svn/tmp/tempfile.6.tmp [new file with mode: 0644]
lib/.svn/tmp/tempfile.tmp [new file with mode: 0644]
lib/cgi/.svn/all-wcprops [new file with mode: 0644]
lib/cgi/.svn/entries [new file with mode: 0644]
lib/cgi/.svn/format [new file with mode: 0644]
lib/cgi/.svn/text-base/cartagena-simple.rb.svn-base [new file with mode: 0644]
lib/cgi/cartagena-simple.rb [new file with mode: 0755]
lib/first-move.rb [new file with mode: 0755]
lib/game_handler.rb [new file with mode: 0644]
lib/html/.svn/all-wcprops [new file with mode: 0644]
lib/html/.svn/entries [new file with mode: 0644]
lib/html/.svn/format [new file with mode: 0644]
lib/html/.svn/prop-base/blue-pirate.gif.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/blue_pirate.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/boat.gif.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/boat.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/bottle.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/bottle.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/brown_pirate.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/cell.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/dagger.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/dagger.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/green_pirate.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/gun.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/gun.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/hat.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/hat.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/keys.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/keys.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/red_pirate.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/retreat.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/skull.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/skull.png.svn-base [new file with mode: 0644]
lib/html/.svn/prop-base/yellow_pirate.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/blue-pirate.gif.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/blue_pirate.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/boat.gif.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/boat.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/bottle.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/bottle.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/brown_pirate.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/cell.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/complex.html.netbeans-base [new file with mode: 0644]
lib/html/.svn/text-base/complex.html.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/dagger.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/dagger.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/green_pirate.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/gun.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/gun.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/hat.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/hat.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/index.html.netbeans-base [new file with mode: 0644]
lib/html/.svn/text-base/index.html.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/interface.html.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/keys.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/keys.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/red_pirate.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/retreat.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/rules.html.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/simple.html.netbeans-base [new file with mode: 0644]
lib/html/.svn/text-base/simple.html.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/skull.jpg.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/skull.png.svn-base [new file with mode: 0644]
lib/html/.svn/text-base/yellow_pirate.png.svn-base [new file with mode: 0644]
lib/html/blue-pirate.gif [new file with mode: 0644]
lib/html/blue_pirate.png [new file with mode: 0644]
lib/html/boat.gif [new file with mode: 0644]
lib/html/boat.png [new file with mode: 0644]
lib/html/bottle.jpg [new file with mode: 0644]
lib/html/bottle.png [new file with mode: 0644]
lib/html/brown_pirate.png [new file with mode: 0644]
lib/html/cell.png [new file with mode: 0644]
lib/html/complex.html [new file with mode: 0644]
lib/html/dagger.jpg [new file with mode: 0644]
lib/html/dagger.png [new file with mode: 0644]
lib/html/green_pirate.png [new file with mode: 0644]
lib/html/gun.jpg [new file with mode: 0644]
lib/html/gun.png [new file with mode: 0644]
lib/html/hat.jpg [new file with mode: 0644]
lib/html/hat.png [new file with mode: 0644]
lib/html/index.html [new file with mode: 0644]
lib/html/interface.html [new file with mode: 0644]
lib/html/keys.jpg [new file with mode: 0644]
lib/html/keys.png [new file with mode: 0644]
lib/html/red_pirate.png [new file with mode: 0644]
lib/html/retreat.png [new file with mode: 0644]
lib/html/robots/.svn/all-wcprops [new file with mode: 0644]
lib/html/robots/.svn/entries [new file with mode: 0644]
lib/html/robots/.svn/format [new file with mode: 0644]
lib/html/robots/1/.svn/all-wcprops [new file with mode: 0644]
lib/html/robots/1/.svn/entries [new file with mode: 0644]
lib/html/robots/1/.svn/format [new file with mode: 0644]
lib/html/robots/1/.svn/text-base/runme.rb.svn-base [new file with mode: 0644]
lib/html/robots/1/runme.rb [new file with mode: 0644]
lib/html/robots/2/.svn/all-wcprops [new file with mode: 0644]
lib/html/robots/2/.svn/entries [new file with mode: 0644]
lib/html/robots/2/.svn/format [new file with mode: 0644]
lib/html/robots/2/.svn/text-base/runme.rb.svn-base [new file with mode: 0644]
lib/html/robots/2/runme.rb [new file with mode: 0644]
lib/html/rules.html [new file with mode: 0644]
lib/html/simple.html [new file with mode: 0644]
lib/html/skull.jpg [new file with mode: 0644]
lib/html/skull.png [new file with mode: 0644]
lib/html/yellow_pirate.png [new file with mode: 0644]
lib/libcartagena.rb [new file with mode: 0644]
lib/main.rb [new file with mode: 0644]
lib/random-move.rb [new file with mode: 0755]
nbproject/.svn/all-wcprops [new file with mode: 0644]
nbproject/.svn/dir-prop-base [new file with mode: 0644]
nbproject/.svn/entries [new file with mode: 0644]
nbproject/.svn/format [new file with mode: 0644]
nbproject/.svn/text-base/project.properties.svn-base [new file with mode: 0644]
nbproject/.svn/text-base/project.xml.svn-base [new file with mode: 0644]
nbproject/private/private.xml [new file with mode: 0644]
nbproject/private/rake-d.txt [new file with mode: 0644]
nbproject/project.properties [new file with mode: 0644]
nbproject/project.xml [new file with mode: 0644]
test-game-play [new file with mode: 0644]
test-game-play-to-win [new file with mode: 0644]
test-load-file-1 [new file with mode: 0644]
test-load-file-2 [new file with mode: 0644]
test-load-file-3 [new file with mode: 0644]
test/.svn/all-wcprops [new file with mode: 0644]
test/.svn/entries [new file with mode: 0644]
test/.svn/format [new file with mode: 0644]
test/.svn/text-base/libcartagena_test.rb.netbeans-base [new file with mode: 0644]
test/.svn/text-base/libcartagena_test.rb.svn-base [new file with mode: 0644]
test/libcartagena_test.rb [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e91c184
--- /dev/null
+++ b/README
@@ -0,0 +1,3 @@
+== Cartagena
+
+You should document your project here.\r
diff --git a/Rakefile b/Rakefile
new file mode 100644 (file)
index 0000000..9d6872f
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,8 @@
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
diff --git a/game-lines-11.txt b/game-lines-11.txt
new file mode 100644 (file)
index 0000000..6e9b845
--- /dev/null
@@ -0,0 +1,166 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1\r
+hat\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys\r
+hat\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys\r
+skull\r
+bottle\r
+dagger
\ No newline at end of file
diff --git a/game-lines-12.txt b/game-lines-12.txt
new file mode 100644 (file)
index 0000000..3dea36a
--- /dev/null
@@ -0,0 +1,166 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1 1 24 hat\r
+1 3 25 hat\r
+1 4 35 hat\r
+2\r
+hat\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys\r
+hat\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys
\ No newline at end of file
diff --git a/game-lines-13.txt b/game-lines-13.txt
new file mode 100644 (file)
index 0000000..3e051b7
--- /dev/null
@@ -0,0 +1,169 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1 1 24 hat\r
+1 3 25 hat\r
+1 4 35 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+3\r
+skull\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys\r
+bottle\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys
\ No newline at end of file
diff --git a/game-lines-15.txt b/game-lines-15.txt
new file mode 100644 (file)
index 0000000..86663ed
--- /dev/null
@@ -0,0 +1,172 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1 1 24 hat\r
+1 3 25 hat\r
+1 4 35 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+3 4 12 bottle\r
+3 6 21 bottle\r
+3 6 28 bottle\r
+1\r
+skull\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys\r
+bottle\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys
\ No newline at end of file
diff --git a/game-lines-16.txt b/game-lines-16.txt
new file mode 100644 (file)
index 0000000..82f338d
--- /dev/null
@@ -0,0 +1,172 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1 1 24 hat\r
+1 3 25 hat\r
+1 4 35 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+3 4 12 bottle\r
+3 6 21 bottle\r
+3 6 28 bottle\r
+1 4 32 bottle\r
+1 6 37 bottle\r
+1 10 37 bottle\r
+2\r
+skull\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys\r
+skull\r
+skull\r
+skull
\ No newline at end of file
diff --git a/game-lines-17.txt b/game-lines-17.txt
new file mode 100644 (file)
index 0000000..db8a2bb
--- /dev/null
@@ -0,0 +1,175 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1 1 24 hat\r
+1 3 25 hat\r
+1 4 35 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+3 4 12 bottle\r
+3 6 21 bottle\r
+3 6 28 bottle\r
+1 4 32 bottle\r
+1 6 37 bottle\r
+1 10 37 bottle\r
+2 13 37 bottle\r
+2 14 23 keys\r
+2 15 27 keys\r
+3\r
+skull\r
+skull\r
+keys\r
+gun\r
+dagger\r
+keys\r
+skull\r
+skull\r
+skull
\ No newline at end of file
diff --git a/game-lines-18.txt b/game-lines-18.txt
new file mode 100644 (file)
index 0000000..36f4fee
--- /dev/null
@@ -0,0 +1,178 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1 1 24 hat\r
+1 3 25 hat\r
+1 4 35 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+3 4 12 bottle\r
+3 6 21 bottle\r
+3 6 28 bottle\r
+1 4 32 bottle\r
+1 6 37 bottle\r
+1 10 37 bottle\r
+2 13 37 bottle\r
+2 14 23 keys\r
+2 15 27 keys\r
+3 12 20 skull\r
+3 16 31 keys\r
+3 17 37 keys\r
+1\r
+skull\r
+skull\r
+keys\r
+gun\r
+dagger\r
+keys\r
+skull\r
+skull\r
+skull
\ No newline at end of file
diff --git a/game-lines-19.txt b/game-lines-19.txt
new file mode 100644 (file)
index 0000000..e0ef244
--- /dev/null
@@ -0,0 +1,178 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1 1 24 hat\r
+1 3 25 hat\r
+1 4 35 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+3 4 12 bottle\r
+3 6 21 bottle\r
+3 6 28 bottle\r
+1 4 32 bottle\r
+1 6 37 bottle\r
+1 10 37 bottle\r
+2 13 37 bottle\r
+2 14 23 keys\r
+2 15 27 keys\r
+3 12 20 skull\r
+3 16 31 keys\r
+3 17 37 keys\r
+1 24 37 keys\r
+1 25 37 keys\r
+1 32 36 gun\r
+2\r
+skull\r
+skull\r
+gun\r
+gun\r
+dagger\r
+gun
\ No newline at end of file
diff --git a/game-lines-20.txt b/game-lines-20.txt
new file mode 100644 (file)
index 0000000..a47098b
--- /dev/null
@@ -0,0 +1,181 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1 1 24 hat\r
+1 3 25 hat\r
+1 4 35 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+3 4 12 bottle\r
+3 6 21 bottle\r
+3 6 28 bottle\r
+1 4 32 bottle\r
+1 6 37 bottle\r
+1 10 37 bottle\r
+2 13 37 bottle\r
+2 14 23 keys\r
+2 15 27 keys\r
+3 12 20 skull\r
+3 16 31 keys\r
+3 17 37 keys\r
+1 24 37 keys\r
+1 25 37 keys\r
+1 32 36 gun\r
+2 23 30 skull\r
+2 27 33 skull\r
+2 30 37 skull\r
+3\r
+skull\r
+skull\r
+gun\r
+gun\r
+dagger\r
+gun
\ No newline at end of file
diff --git a/game-lines-21.txt b/game-lines-21.txt
new file mode 100644 (file)
index 0000000..f56f978
--- /dev/null
@@ -0,0 +1,184 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+2 2 13 dagger\r
+2 2 14 bottle\r
+2 2 15 keys\r
+3 1 16 gun\r
+3 3 17 hat\r
+3 3 18 skull\r
+1 1 24 hat\r
+1 3 25 hat\r
+1 4 35 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+2 5 37 hat\r
+3 4 12 bottle\r
+3 6 21 bottle\r
+3 6 28 bottle\r
+1 4 32 bottle\r
+1 6 37 bottle\r
+1 10 37 bottle\r
+2 13 37 bottle\r
+2 14 23 keys\r
+2 15 27 keys\r
+3 12 20 skull\r
+3 16 31 keys\r
+3 17 37 keys\r
+1 24 37 keys\r
+1 25 37 keys\r
+1 32 36 gun\r
+2 23 30 skull\r
+2 27 33 skull\r
+2 30 37 skull\r
+3 18 30 skull\r
+3 20 37 skull\r
+3 21 37 skull\r
+1\r
+gun\r
+gun\r
+gun\r
+gun\r
+gun\r
+gun
\ No newline at end of file
diff --git a/game-lines-8.txt b/game-lines-8.txt
new file mode 100644 (file)
index 0000000..4750066
--- /dev/null
@@ -0,0 +1,160 @@
+3\r
+cell\r
+gun\r
+keys\r
+dagger\r
+hat\r
+skull\r
+bottle\r
+keys\r
+dagger\r
+skull\r
+hat\r
+gun\r
+bottle\r
+dagger\r
+bottle\r
+keys\r
+gun\r
+hat\r
+skull\r
+dagger\r
+skull\r
+bottle\r
+gun\r
+keys\r
+hat\r
+hat\r
+dagger\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+bottle\r
+skull\r
+dagger\r
+hat\r
+gun\r
+boat\r
+1 0 1\r
+1 0 2\r
+1 2 1\r
+2 0 2\r
+2 0 3\r
+2 3 2\r
+3 0 3\r
+3 0 4\r
+3 4 3\r
+1 0 4\r
+1 0 5\r
+1 5 4\r
+2 0 5\r
+2 0 6\r
+2 6 5\r
+3 0 6\r
+3 0 11\r
+3 11 6\r
+1 0 7\r
+1 7 6\r
+2 0 8\r
+2 8 5\r
+3 0 9\r
+3 9 4\r
+1 0 10\r
+1 10 3\r
+2 0 11\r
+2 11 2\r
+3 0 12\r
+3 12 1\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+2 2 8\r
+2 8 2\r
+3 3 9\r
+3 9 3\r
+1 1 10\r
+1 10 1\r
+2 2 11\r
+2 11 2\r
+3 3 12\r
+3 12 3\r
+1 1 7\r
+1 7 1\r
+1 1 10\r
+1\r
+hat\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys\r
+hat\r
+skull\r
+bottle\r
+gun\r
+dagger\r
+keys\r
+skull\r
+bottle\r
+dagger
\ No newline at end of file
diff --git a/lib/.LCKlibcartagena.rb~ b/lib/.LCKlibcartagena.rb~
new file mode 100644 (file)
index 0000000..9922eec
--- /dev/null
@@ -0,0 +1 @@
+/home/neil/programming/ruby/Cartagena/lib/.LCKlibcartagena.rb~
\ No newline at end of file
diff --git a/lib/.svn/all-wcprops b/lib/.svn/all-wcprops
new file mode 100644 (file)
index 0000000..3641f48
--- /dev/null
@@ -0,0 +1,35 @@
+K 25
+svn:wc:ra_dav:version-url
+V 41
+/svn/njae/!svn/ver/58/cartagena/trunk/lib
+END
+main.rb
+K 25
+svn:wc:ra_dav:version-url
+V 49
+/svn/njae/!svn/ver/25/cartagena/trunk/lib/main.rb
+END
+random-move.rb
+K 25
+svn:wc:ra_dav:version-url
+V 56
+/svn/njae/!svn/ver/43/cartagena/trunk/lib/random-move.rb
+END
+game_handler.rb
+K 25
+svn:wc:ra_dav:version-url
+V 57
+/svn/njae/!svn/ver/42/cartagena/trunk/lib/game_handler.rb
+END
+first-move.rb
+K 25
+svn:wc:ra_dav:version-url
+V 55
+/svn/njae/!svn/ver/42/cartagena/trunk/lib/first-move.rb
+END
+libcartagena.rb
+K 25
+svn:wc:ra_dav:version-url
+V 57
+/svn/njae/!svn/ver/56/cartagena/trunk/lib/libcartagena.rb
+END
diff --git a/lib/.svn/entries b/lib/.svn/entries
new file mode 100644 (file)
index 0000000..0917298
--- /dev/null
@@ -0,0 +1,95 @@
+8
+
+dir
+65
+http://scripts.njae.me.uk/svn/njae/cartagena/trunk/lib
+http://scripts.njae.me.uk/svn/njae
+
+
+
+2008-12-08T21:03:51.254104Z
+58
+neil
+
+
+svn:special svn:externals svn:needs-lock
+
+
+
+
+
+
+
+
+
+
+
+0ada9808-85ab-4924-adda-5bf89eae3818
+\f
+cgi
+dir
+\f
+html
+dir
+\f
+main.rb
+file
+
+
+
+
+2008-08-19T15:48:21.000000Z
+6c2cf56248662af6cff4847c8912f4c6
+2008-06-12T10:24:34.892518Z
+25
+neil
+\f
+random-move.rb
+file
+
+
+
+
+2008-10-17T07:27:48.000000Z
+9d7aa6b67c047dd18ff05819c78e9a7d
+2008-10-17T07:57:36.358504Z
+43
+
+has-props
+\f
+game_handler.rb
+file
+
+
+
+
+2008-10-16T13:08:44.000000Z
+fe6d99bbf7aa3c4330755efa61255308
+2008-10-16T20:38:07.769887Z
+42
+\f
+first-move.rb
+file
+
+
+
+
+2008-10-16T13:19:45.000000Z
+db3d894459b82c2f69b1ddd6b5d66858
+2008-10-16T20:38:07.769887Z
+42
+
+has-props
+\f
+libcartagena.rb
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+dfafcb99a9164f1b85f78bd7d48d2340
+2008-12-05T21:50:45.570571Z
+56
+neil
+\f
diff --git a/lib/.svn/format b/lib/.svn/format
new file mode 100644 (file)
index 0000000..45a4fb7
--- /dev/null
@@ -0,0 +1 @@
+8
diff --git a/lib/.svn/prop-base/first-move.rb.svn-base b/lib/.svn/prop-base/first-move.rb.svn-base
new file mode 100644 (file)
index 0000000..869ac71
--- /dev/null
@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END
diff --git a/lib/.svn/prop-base/random-move.rb.svn-base b/lib/.svn/prop-base/random-move.rb.svn-base
new file mode 100644 (file)
index 0000000..869ac71
--- /dev/null
@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END
diff --git a/lib/.svn/text-base/first-move.rb.svn-base b/lib/.svn/text-base/first-move.rb.svn-base
new file mode 100644 (file)
index 0000000..4c96457
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/ruby
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# clockwise-p1 
+#  Game state file read on STDIN
+#  Move produced on STDOUT
+#
+# == Author
+# Neil Smith
+
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'libcartagena'
+require 'rdoc/usage'
+
+gamelines = readlines
+game, moves = Game.read_game(gamelines)
+move = game.possible_moves[0]
+puts move.format(game.board, true)
diff --git a/lib/.svn/text-base/game_handler.rb.svn-base b/lib/.svn/text-base/game_handler.rb.svn-base
new file mode 100644 (file)
index 0000000..cc8eda7
--- /dev/null
@@ -0,0 +1,59 @@
+# == Synopsis
+#
+# Play a game of Cartagena.
+# 
+# == Author
+# Neil Smith
+# 
+# == Change history
+# Version 1.1::  23 April 2008 
+
+require 'lib/libcartagena'
+
+# Play a game to completion, given a set of player agents.
+class GameHandler
+    
+  attr_reader :game
+    
+  # Create a game handler that uses a set of players
+  def initialize(players, game_length_limit = 5000, verbose = false, very_verbose = false)
+    @game = Game.new(players.length)
+    @verbose = verbose
+    @very_verbose = very_verbose
+    @game_length_limit = game_length_limit
+    
+    # Give each player a name
+    @named_players = Hash.new 
+    (@game.players.zip players).each do |kv|
+      @named_players[kv[0]] = kv[1]
+    end
+  end
+  
+  # Play a game of Cartagena.  If players make illegal moves, disqualify them and restart the game.  
+  # Terminate the game if there's a winner, there's only one player left, 
+  # or the game has gone on too long.
+  def play
+    while @game.history.length < @game_length_limit
+      move_to_apply = @named_players[@game.current_player].best_move(@game, roll)
+      puts "Move #{@game.history.length + 1}: Player #{@game.current_player} rolled #{roll}: making move #{move_to_apply}" if @verbose
+      @game.apply_moves! [move_to_apply]
+      puts @game if @very_verbose
+    end
+    puts "Game terminated after #{@game.history.length} moves" if @verbose
+    [:draw, @limit]
+  rescue GameWonNotice => win_notification
+    winner = win_notification.message[-1,1]
+    puts "Game won by #{winner} in #{@game.history.length} moves" if @verbose
+    [@named_players[winner], @game.history.length]
+  rescue InvalidMoveError
+    puts "Disqualifying player #{@game.current_player}" if @verbose
+    @named_players.delete @game.current_player
+    if @named_players.length > 1
+      retry
+    else
+      puts "Game won by #{@named_players.keys[0]} by default" if @verbose
+      [@named_players[@named_players.keys[0]], 0]
+    end
+  end 
+end 
+  
\ No newline at end of file
diff --git a/lib/.svn/text-base/libcartagena.rb.netbeans-base b/lib/.svn/text-base/libcartagena.rb.netbeans-base
new file mode 100644 (file)
index 0000000..f1062bf
--- /dev/null
@@ -0,0 +1,726 @@
+# == Synopsis
+#
+# Library to support Cartagena play
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  11 Jun 2008  
+#                * Initial build
+
+# Symbols
+$SYMBOLS = [:bottle, :dagger, :gun, :hat, :keys, :skull]
+
+# The number of cards of each symbol in the deck
+$CARDS_PER_SYMBOL = 17
+
+# The number of cards initiall dealt to each player
+$INITIAL_CARDS_PER_PLAYER = 3
+
+# Maximum number of pieces on a position
+$MAX_PIECES_PER_POSITION = 3
+
+# Number of actions that can be taken by each player in sequence
+$MAX_MOVES_PER_TURN = 3
+
+# Errors for a game
+
+# Moves can only be [1..6] spaces
+class InvalidMoveError < StandardError
+end
+
+# Game is won when only one player has uncaptured pieces
+class GameWonNotice < StandardError
+end
+
+# A position on the board.
+class Position
+  attr_accessor :symbol
+  attr_accessor :contains
+  
+  def initialize(symbol)
+    @symbol = symbol
+    @contains = []
+  end
+end
+
+# A tile that makes up the board.  Each tile has two sides, each with six
+# positions.  Tiles can be either way up, and either way round.
+class Tile
+  attr_reader :exposed, :front, :back
+  
+  def initialize(front, back)
+    @front = front.collect {|s| Position.new(s)}
+    @back = back.collect {|s| Position.new(s)}
+    @exposed = @front
+    @exposed_side = :front
+  end
+  
+  def flip!
+    if @exposed_side == :front
+      @exposed = @back
+      @exposed_side = :back
+    else
+      @exposed = @front
+      @exposed_side = :front
+    end
+  end
+  
+  def reverse!
+    @exposed = @exposed.reverse
+  end
+end
+
+
+# The game board
+class Board
+
+  attr_accessor :positions
+  attr_reader :tiles
+
+  # A laborious procedure to create all the positions and tie them all together
+  def initialize(tiles_used)
+    # A hash of all positions, indexed by position names
+    @tiles = [Tile.new([:hat, :keys, :gun, :bottle, :skull, :dagger], 
+        [:hat, :keys, :gun, :bottle, :skull, :dagger]),
+      Tile.new([:gun, :hat, :dagger, :skull, :bottle, :keys],
+        [:gun, :hat, :dagger, :skull, :bottle, :keys]),
+      Tile.new([:skull, :gun, :bottle, :keys, :dagger, :hat],
+        [:skull, :gun, :bottle, :keys, :dagger, :hat]),
+      Tile.new([:dagger, :bottle, :keys, :gun, :hat, :skull],
+        [:dagger, :bottle, :keys, :gun, :hat, :skull]),
+      Tile.new([:keys, :dagger, :skull, :hat, :gun, :bottle],
+        [:keys, :dagger, :skull, :hat, :gun, :bottle]),
+      Tile.new([:bottle, :skull, :hat, :dagger, :keys, :gun],
+        [:bottle, :skull, :hat, :dagger, :keys, :gun])                 
+    ].shuffle[0...tiles_used]
+    @tiles = @tiles.each do |t|
+      if rand < 0.5
+        t.reverse!
+      else
+        t
+      end
+    end
+
+    @positions = [Position.new(:cell)]
+    @tiles.each {|t| t.exposed.each {|p| @positions << p}}
+    @positions << Position.new(:boat)
+  end # def
+
+  def to_s
+    layout
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # For each position, show its name and what it touches
+  def layout
+    out_string = ""
+    @positions.each {|position| out_string << "#{position.symbol}\n"}
+    out_string
+  end
+
+end
+
+
+# Each piece on the board is an object
+class Piece
+  attr_reader :player, :number
+  attr_accessor :position
+
+  def initialize(position, player, number)
+    @position = position
+    @player = player
+    @number = number
+  end
+
+  def to_s
+    "#{@player}:#{@number}"
+  end
+  
+  def to_str
+    to_s
+  end
+
+  def show(convert_to_zero_based_player_number = false)
+    if convert_to_zero_based_player_number
+      "#{@player + 1}:#{@number}"   
+    else
+      "#{@player}:#{@number}"  
+    end
+  end
+  
+  def move_to(new_position)
+    @position = new_position
+  end
+
+end
+
+
+# A move in a game
+class Move
+  attr_reader :piece, :origin, :destination, :card_played
+  
+  def initialize(piece, origin, destination, card_played = :unspecified)
+    @piece = piece
+    @origin = origin
+    @destination = destination
+    @card_played = card_played
+  end
+
+  def show(board, convert_to_zero_based_player_number = false)
+    if @card_played == :unspecified
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)}"
+    else
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)} (#{@card_played})"
+    end
+  end
+  
+  def format(board, convert_to_zero_based_player_number = false)
+    display_player_number = if convert_to_zero_based_player_number then
+      @piece.player + 1
+    else
+      @piece.player
+    end
+    if @card_played ==:unspecified
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)}"
+    else
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)} #{@card_played}"
+    end
+  end
+  
+  # Write a move to a string
+  # Note the inverse, String#to_move, is defined below
+  def to_s
+    "#{@piece.player} #{@origin.to_s}  #{@destination.to_s} #{@card_played}"
+  end
+  
+  def to_str
+    to_s
+  end
+  
+end
+
+
+
+# A class to record each of the states previously found in a game.  
+# Note that this is a deep copy of the pieces and what they've captured, so 
+# you'll need to convert back
+class GameState
+  attr_accessor :move, :player, :board, :pieces_before_move, :deck_before_move, 
+    :players_cards_before_move, :moves_by_current_player
+  #    this_game_state = GameState.new(move, player, @board, @pieces, @deck, @players_cards)
+  
+  
+  def initialize(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    @move, @player, @board, @pieces_before_move, @deck_before_move, 
+      @players_cards_before_move, @moves_by_current_player = 
+      copy_game_state(move, player, board, pieces, deck, players_cards, 
+      moves_by_current_player)
+  end
+  
+  #  def ==(other)
+  #    @move.to_s == other.move.to_s and
+  #      @player == other.player and
+  #      @piece_after_move == other.pieces_after_move
+  #  end
+  
+  def copy_game_state(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    copy_player = player
+    moving_player = move.piece.player
+
+    copy_board = board.dup
+    copy_board.positions = board.positions.collect {|pos| pos.dup}
+    copy_board.positions.each {|pos| pos.contains = []}
+
+    copy_pieces = pieces.collect do |players_pieces|
+      players_pieces.collect do |piece|
+        new_piece_position = copy_board.positions[board.positions.index(piece.position)]
+        new_piece = Piece.new(new_piece_position, piece.player, piece.number)
+        new_piece_position.contains << new_piece
+        new_piece
+      end
+    end
+
+    piece_index = pieces[moving_player].index(move.piece)
+    origin_index = board.positions.index(move.origin)
+    destination_index = board.positions.index(move.destination)
+    copy_move = Move.new(copy_pieces[moving_player][piece_index], 
+      copy_board.positions[origin_index], 
+      copy_board.positions[destination_index])
+    
+    copy_deck = deck.dup
+    copy_players_cards = players_cards.collect {|p| p.dup}
+    copy_moves_by_current_player = moves_by_current_player
+
+    return copy_move, copy_player, copy_board, copy_pieces, copy_deck, copy_players_cards, copy_moves_by_current_player
+  end
+  
+end
+
+
+# A game of Cartagena.  It keeps a history of all previous states.  
+class Game
+
+  attr_reader :history
+  attr_accessor :current_player
+  attr_reader :players
+  attr_accessor :players_cards
+  attr_accessor :moves_by_current_player
+  attr_reader :board
+  attr_reader :pieces
+  attr_reader :cards
+  attr_accessor :deck
+
+  # Create a new game
+  def initialize(players = 5, number_of_tiles = 6, pieces_each = 6)
+    @board = Board.new(number_of_tiles)
+    @history = []
+    @pieces = []
+    @players = [].fill(0, players) {|i| i}
+    @players_cards = []
+    @current_player = 0
+    @moves_by_current_player = 0
+    @cards = []
+    1.upto($CARDS_PER_SYMBOL) {|x| @cards.concat($SYMBOLS)}
+    @deck = @cards.shuffle
+    @players.each do |p|
+      @pieces[p] = []
+      0.upto(pieces_each - 1) do |count|
+        piece = Piece.new(@board.positions[0], p, count)
+        @pieces[p][count] = piece
+        @board.positions[0].contains << piece
+      end
+      @players_cards[p] = []
+      deal_cards!($INITIAL_CARDS_PER_PLAYER, p)
+    end
+  end
+
+  # Deal some cards to a player.  Remove them from the deck.  Refill the deck
+  # if necessary
+  def deal_cards!(number_of_cards, player = @current_player)
+    1.upto(number_of_cards) do
+      if @deck.empty?
+        @deck = @cards
+        @players_cards.each do |p|
+          p.each do |c|
+            @deck.delete_at(@deck.index(c))
+          end
+        end
+        @deck = @deck.shuffle
+      end
+      @players_cards[player] << @deck.pop
+    end
+  end
+  
+  # Check that a move is valid.  Throw an exception if it's invalid
+  def validate_move(move, player = @current_player)
+    # Check the move is a valid one
+    raise(InvalidMoveError, "Move #{move}: Player #{player} does not exist") unless @players.include?(player)
+    raise(InvalidMoveError, "Move #{move}: None of player #{player}'s pieces on position #{move.origin}") unless move.origin.contains.find {|pc| pc.player == player}
+    raise(InvalidMoveError, "Move #{move}: Origin and destination are the same") if move.origin == move.destination
+  
+    origin_position      = @board.positions.index(move.origin)
+    destination_position = @board.positions.index(move.destination)
+    # Is this move an advance or a retreat?
+    if destination_position > origin_position
+      # Advancing a piece
+      if move.destination == @board.positions[-1] # A move into the boat
+        raise(InvalidMoveError, "Move #{move}: Move into boat and card unspecified") if move.destination == @board.positions[-1] and move.card_played == :unspecified
+        unless @players_cards[player].find {|c| c == move.card_played}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece into the boat")
+        end
+      else
+        if move.card_played != :unspecified and move.destination.symbol != move.card_played
+          raise(InvalidMoveError, "Player #{player} trying to move to #{move.destination}, a #{move.destination.symbol} square with a a #{move.card_played} card")
+        end
+        unless @players_cards[player].find {|c| c == move.destination.symbol}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece to a #{move.destination.symbol} square")
+        end
+        # Check target square is vacant
+        raise(InvalidMoveError, "Advance move #{move}: destination occupied") unless move.destination.contains.empty?
+      end
+      # Check all the intervening squares with this symbol are occupied
+      intervening_empty_position = @board.positions[origin_position...destination_position].index_find do |p| 
+        p.symbol == move.destination.symbol and 
+          p.contains.empty?
+      end
+      raise(InvalidMoveError, "Advance move #{move}: location #{intervening_empty_position} is empty") if intervening_empty_position
+    else
+      # Retreating a piece
+      # Check target position has one or two pieces already on it
+      destination_count = move.destination.contains.length
+      raise(InvalidMoveError, "Retreat move #{move}: destination has no pieces already on it") if destination_count == 0
+      raise(InvalidMoveError, "Retreat move #{move}: destination has too many (#{destination_count}) pieces already on it") if destination_count >= $MAX_PIECES_PER_POSITION
+      # Check none of the intervening squares have any pieces on them
+      # puts "Checking positions #{destination_position} to #{origin_position}"      
+      intervening_target_position = @board.positions[(destination_position + 1)...origin_position].index_find do |p| 
+        # puts "Examining postition #{p} at location #{@board.positions.index(p)} which contains #{p.contains.length} pieces"        
+        p.contains.length > 0 and 
+          p.contains.length < $MAX_PIECES_PER_POSITION
+      end
+      raise(InvalidMoveError, "Retreat move #{move.show(@board)}: location #{intervening_target_position} is a viable target") if intervening_target_position
+    end
+  end
+  
+  # Apply a single move to a game.  
+  def apply_move!(move, player = @current_player, validate = true)
+    
+    validate_move(move, player) if validate
+
+    raise(InvalidMoveError, "Too many consecutive moves by #{@current_player}: has taken #{@moves_by_current_player} and validate is #{validate}") if validate and player == @current_player and @moves_by_current_player >= $MAX_MOVES_PER_TURN
+       
+    # Record the old state
+    this_game_state = GameState.new(move, @current_player, @board, @pieces, @deck, @players_cards, @moves_by_current_player)
+    @history << this_game_state
+    
+    # Apply this move
+    move.origin.contains.delete move.piece
+    move.destination.contains << move.piece
+    move.piece.position = move.destination
+
+    if player == @current_player
+      @moves_by_current_player += 1
+    else
+      @current_player = player
+      @moves_by_current_player = 1
+    end
+   
+    # Update cards
+    if @board.positions.index(move.destination) > @board.positions.index(move.origin)
+      # Advance move
+      if validate
+        card_to_remove = if move.card_played != :unspecified
+          move.card_played
+        else
+          move.destination.symbol
+        end
+        if @players_cards[player].include?(card_to_remove)
+          @players_cards[player].delete_at(@players_cards[player].index(card_to_remove))
+        end
+      end
+    else
+      # Retreat move
+      deal_cards!(move.destination.contains.length - 1, player)
+    end
+    
+    # If this player has all their pieces in the boat, declare a win.
+    if @pieces[player].all? {|pc| pc.position == @board.positions[-1]}
+      raise(GameWonNotice, "Game won by #{player}")
+    end
+  end
+  
+  # Undo a move 
+  def undo_move!
+    state_to_restore = @history[-1]
+    move, @current_player, @board, @pieces, @deck, @players_cards, 
+      @moves_by_current_player = 
+      state_to_restore.copy_game_state(state_to_restore.move, 
+      state_to_restore.player, 
+      state_to_restore.board, 
+      state_to_restore.pieces_before_move, 
+      state_to_restore.deck_before_move, 
+      state_to_restore.players_cards_before_move, 
+      state_to_restore.moves_by_current_player)
+    @history.pop    
+  end
+    
+  # Apply a list of moves in order
+  def apply_moves!(moves)
+    moves.each do |move| 
+      @current_player = move.piece.player
+      apply_move!(move, @current_player)
+      next_player! if @moves_by_current_player >= $MOVES_PER_TURN
+    end
+  end
+
+
+  # Set the current player to be the next player
+  def next_player!
+    if @current_player == @players[-1]
+      @current_player = @players[0]
+    else
+      @current_player = @players[@players.index(@current_player) + 1]
+    end
+    @moves_by_current_player = 0
+    @current_player
+  end
+
+  
+  def reset_current_player(new_current_player)
+    @current_player = new_current_player
+    @moves_by_current_player = 0
+  end
+
+  
+  # Return an array of all possible moves from this state, given the active player
+  def possible_moves(player = @current_player)
+    moves = []
+    @pieces[player].each do |piece|
+      # Do a forward move for each card held
+      unless piece.position == @board.positions[-1]
+        @players_cards[player].each do |card|
+          destination = @board.positions[@board.positions.index(piece.position)..-1].find do |pos|
+            (pos.symbol == card and pos.contains == []) or
+              pos.symbol == :boat
+          end
+          # puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)} to #{@board.positions.index(destination)}, a #{destination.symbol}"        
+          moves << Move.new(piece, piece.position, destination, card)
+        end
+      end
+      # Do a reverse move for the piece
+      unless piece.position == board.positions[0]
+        destination = @board.positions[1...@board.positions.index(piece.position)].reverse.find do |pos|
+          pos.contains.length == 1 or pos.contains.length == 2
+        end
+        if destination
+          # puts "Player #{player}, piece #{piece.number} at position #{@board.positions.index(piece.position)} retreats to #{@board.positions.index(destination)}, a #{destination.symbol} containing #{destination.contains.length} pieces"
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+    end
+    # moves.each {|m| puts m.show(@board)}    
+    moves
+  end
+
+  def build_state_string
+    outstr = "Current player = #{@current_player}\n"
+    0.upto((@board.positions.length)-1) do |i|
+      outstr << "#{i}: #{@board.positions[i].symbol}: "
+      @board.positions[i].contains.each do |piece|
+        outstr << "P#{piece.player}:#{piece.number} "
+      end
+      outstr << "\n"
+    end
+    0.upto((@players.length)-1) do |i|
+      outstr << "Player #{i} holds " << (@players_cards[i].sort_by {|c| c.to_s}).join(', ') << "\n"
+    end
+    outstr << "Deck holds " << @deck.join(', ') << "\n"
+    outstr
+  end
+  
+  # Show the state of the board
+  def show_state
+    puts build_state_string
+    #    @pieces.keys.sort.each do |piece_name|
+    #      if @pieces[piece_name].captured
+    #        puts "Piece #{piece_name} captured, at #{@pieces[piece_name].position}"
+    #      else
+    #        puts "Piece #{piece_name} is at #{@pieces[piece_name].position}, holds #{(@pieces[piece_name].contains.collect{|c| c.name}).join(' ')}"
+    #      end
+    #    end
+  end
+
+  def to_s
+    show_state
+  end
+  
+  def to_str
+    to_s
+  end
+  
+
+  def set_testing_game!
+    srand 1234
+    @board = nil
+    initialize(5, 6, 6)
+    board_symbols = [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat]
+    board_symbols.each_index do |i|
+        @board.positions[i].symbol = board_symbols[i]
+      end
+      
+    @players_cards[0] = [:gun,    :hat,   :skull]
+    @players_cards[1] = [:bottle, :keys,  :keys]
+    @players_cards[2] = [:bottle, :hat,   :keys]
+    @players_cards[3] = [:bottle, :skull, :skull]
+    @players_cards[4] = [:bottle, :gun,   :gun]
+    
+    @deck = [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull]
+    
+  end
+  
+  # Given a set of lines from an input file, turn them into a Game object and 
+  # a set of Move objects.
+  # Note the multiple return values.
+  # Class method
+  def Game.read_game(gamelines)
+    gamelines.each {|l| l.chomp!}
+#    puts "Creating game with #{gamelines[0].to_i} players"    
+    game = Game.new(gamelines[0].to_i, 6, 6)
+    
+    # create the board
+    2.upto(38) do |i|
+      game.board.positions[i-1] = Position.new(gamelines[i].to_sym)
+#      puts "Making board position #{i-1} a #{gamelines[i].to_sym}"      
+    end
+    
+    # read all the moves so far
+    moves = []
+    i = 39
+    current_player_moves = 0
+    current_player = 0
+    player_card_counts = game.players_cards.map {|p| $INITIAL_CARDS_PER_PLAYER}
+    unused_cards = game.cards.map {|c| c}
+    while gamelines[i].in_move_format?
+      move = gamelines[i].to_move(game, true)
+#      puts "Applying move #{move.show(game.board)} from line #{i} of the file.  Current player before move application is #{game.current_player}"      
+      if move.piece.player == current_player
+        current_player_moves += 1
+      else
+        current_player = move.piece.player
+        current_player_moves = 1
+      end
+      # if move is an advance move, decrement the cards held by the player and remove the card from unused cards
+      #   throw an error if an advance move is made when all the cards of that type have been used
+      #   if unused_cards becomes empty, restock it.  
+      if game.board.positions.index(move.destination) > game.board.positions.index(move.origin)
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move with no cards in hand.  Move is #{gamelines[i]} on line #{i}") if player_card_counts[move.piece.player] == 0
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move to a #{move.destination.symbol} place when all those cards have been used. Move is #{gamelines[i]} on line #{i}") unless unused_cards.include? move.destination.symbol
+        player_card_counts[current_player] -= 1
+        unused_cards.delete_at(unused_cards.index(move.destination.symbol))
+      # if move is a retreat move, increment the cards held by the player
+      else
+        player_card_counts[current_player] += move.destination.contains.length
+        if unused_cards.length == player_card_counts.inject {|sum, n| sum + n }
+          unused_cards = game.cards.map {|c| c}
+        end
+      end
+      game.apply_move!(move, move.piece.player, false)
+      moves << move
+#      puts "Applied move #{move.show(game.board)} from line #{i} of the file"      
+      i = i + 1
+    end
+    
+    # note the current player
+    game.current_player = gamelines[i].to_i - 1
+    if game.current_player == current_player
+      game.moves_by_current_player = current_player_moves
+    else
+      game.moves_by_current_player = 0
+    end
+# puts "Setting current player to #{game.current_player} on line #{i}"   
+    current_player = game.current_player
+    
+    # read the cards
+    game.players_cards = game.players_cards.map {|c| []}
+# player_card_counts.each_index {|index| puts "#{index} should have #{player_card_counts[index]} cards"}
+# game.players_cards.each_index {|index| puts "#{index} has #{game.players_cards[index]} cards"}
+# puts "Current player is #{game.current_player} whose cards are #{game.players_cards[game.current_player]}"
+    (i+1).upto(gamelines.length - 1) do |j|
+      game.players_cards[game.current_player] << gamelines[j].to_sym
+      unused_cards.delete_at(unused_cards.index(gamelines[j].to_sym))
+#      puts "Reading line #{j} to give player #{game.current_player} a #{gamelines[j].to_sym} card"
+    end
+    raise(InvalidMoveError, "Player #{game.current_player} given #{game.players_cards[game.current_player].length} cards, but should have #{player_card_counts[game.current_player]} cards from play") if game.players_cards[game.current_player].length != player_card_counts[game.current_player]
+    
+    # Update the game deck
+    
+puts "#{unused_cards.length} unused cards"    
+player_card_counts.each {|c| puts "has #{c} cards"}
+    game.deck = unused_cards.shuffle
+    player_card_counts[game.current_player] = 0
+    player_card_counts.each_index do |player|
+      if player_card_counts[player] > 0
+        game.deal_cards!(player_card_counts[player], player)
+      end
+    end
+puts "#{game.deck.length} cards remaining in deck"   
+game.players_cards.each {|cards| puts "holds #{cards}"}
+    return game, moves
+  end
+
+end
+  
+
+# Extension to String class to convert a move-description string into a Move object.  
+# This is the inverse of the Move#to_s method
+class String
+  def in_move_format?
+    elements = split
+    return ((elements.length == 3 or elements.length == 4) and
+        elements[0].to_i > 0 and elements[0].to_i < 6 and
+        elements[1].to_i >= 0 and elements[1].to_i <= 37 and
+        elements[2].to_i >= 0 and elements[2].to_i <= 37)
+  end
+  
+  def to_move(game, convert_to_zero_based_player_number = false)
+    move_elements = self.downcase.split
+    player = move_elements[0].to_i
+    player = player - 1 if convert_to_zero_based_player_number
+    origin_index = move_elements[1].to_i
+    destination_index = move_elements[2].to_i
+    raise(InvalidMoveError, "Invalid origin #{origin_index} in move read") unless origin_index >= 0 and origin_index < game.board.positions.length
+    raise(InvalidMoveError, "Invalid destination #{destination_index} in move read") unless destination_index > 0 and destination_index <= game.board.positions.length
+    raise(InvalidMoveError, "Player #{player} does not have a piece on position #{origin_index} in move read") unless game.board.positions[origin_index].contains.any? {|p| p.player == player}
+    piece = game.board.positions[origin_index].contains.find {|p| p.player == player}
+    piece_name = piece.number
+    if move_elements.length == 4
+      raise(InvalidMoveError, "Invalid card played: #{move_elements[3]}") unless $SYMBOLS.include?(move_elements[3].chomp.to_sym)
+      Move.new(game.pieces[player][piece_name], 
+        game.board.positions[origin_index], 
+        game.board.positions[destination_index],
+        move_elements[3].chomp.to_sym)
+    else
+      Move.new(game.pieces[player][piece_name], 
+        game.board.positions[origin_index], 
+        game.board.positions[destination_index])
+    end
+  end
+end
+
+
+# Read a game description file and convert it into a Game object and set of Move objects.
+# Note that Game.read_game method returns multiple values, so this one does too.
+class IO
+  def IO.read_game(filename)
+    gamelines = IO.readlines(filename)
+    return Game.read_game(gamelines)
+  end
+end
+
+# Extension to the Array class to include the Array#shuffle function
+class Array
+  def shuffle
+    sort_by { rand }
+  end
+  
+  def index_find(&block)
+    found = find(&block)
+    if found
+      index found
+    else
+      found
+    end
+  end
+end
diff --git a/lib/.svn/text-base/libcartagena.rb.svn-base b/lib/.svn/text-base/libcartagena.rb.svn-base
new file mode 100644 (file)
index 0000000..a0574d8
--- /dev/null
@@ -0,0 +1,722 @@
+# == Synopsis
+#
+# Library to support Cartagena play
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  11 Jun 2008  
+#                * Initial build
+#         1.1::  5 Dec 2008
+# =>             * Now tracks played cards when generating new games
+
+# Symbols
+$SYMBOLS = [:bottle, :dagger, :gun, :hat, :keys, :skull]
+
+# The number of cards of each symbol in the deck
+$CARDS_PER_SYMBOL = 17
+
+# The number of cards initiall dealt to each player
+$INITIAL_CARDS_PER_PLAYER = 3
+
+# Maximum number of pieces on a position
+$MAX_PIECES_PER_POSITION = 3
+
+# Number of actions that can be taken by each player in sequence
+$MAX_MOVES_PER_TURN = 3
+
+# Errors for a game
+
+# Moves can only be [1..6] spaces
+class InvalidMoveError < StandardError
+end
+
+# Game is won when only one player has uncaptured pieces
+class GameWonNotice < StandardError
+end
+
+# A position on the board.
+class Position
+  attr_accessor :symbol
+  attr_accessor :contains
+  
+  def initialize(symbol)
+    @symbol = symbol
+    @contains = []
+  end
+end
+
+# A tile that makes up the board.  Each tile has two sides, each with six
+# positions.  Tiles can be either way up, and either way round.
+class Tile
+  attr_reader :exposed, :front, :back
+  
+  def initialize(front, back)
+    @front = front.collect {|s| Position.new(s)}
+    @back = back.collect {|s| Position.new(s)}
+    @exposed = @front
+    @exposed_side = :front
+  end
+  
+  def flip!
+    if @exposed_side == :front
+      @exposed = @back
+      @exposed_side = :back
+    else
+      @exposed = @front
+      @exposed_side = :front
+    end
+  end
+  
+  def reverse!
+    @exposed = @exposed.reverse
+  end
+end
+
+
+# The game board
+class Board
+
+  attr_accessor :positions
+  attr_reader :tiles
+
+  # A laborious procedure to create all the positions and tie them all together
+  def initialize(tiles_used)
+    # A hash of all positions, indexed by position names
+    @tiles = [Tile.new([:hat, :keys, :gun, :bottle, :skull, :dagger], 
+        [:hat, :keys, :gun, :bottle, :skull, :dagger]),
+      Tile.new([:gun, :hat, :dagger, :skull, :bottle, :keys],
+        [:gun, :hat, :dagger, :skull, :bottle, :keys]),
+      Tile.new([:skull, :gun, :bottle, :keys, :dagger, :hat],
+        [:skull, :gun, :bottle, :keys, :dagger, :hat]),
+      Tile.new([:dagger, :bottle, :keys, :gun, :hat, :skull],
+        [:dagger, :bottle, :keys, :gun, :hat, :skull]),
+      Tile.new([:keys, :dagger, :skull, :hat, :gun, :bottle],
+        [:keys, :dagger, :skull, :hat, :gun, :bottle]),
+      Tile.new([:bottle, :skull, :hat, :dagger, :keys, :gun],
+        [:bottle, :skull, :hat, :dagger, :keys, :gun])                 
+    ].shuffle[0...tiles_used]
+    @tiles = @tiles.each do |t|
+      if rand < 0.5
+        t.reverse!
+      else
+        t
+      end
+    end
+
+    @positions = [Position.new(:cell)]
+    @tiles.each {|t| t.exposed.each {|p| @positions << p}}
+    @positions << Position.new(:boat)
+  end # def
+
+  def to_s
+    layout
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # For each position, show its name and what it touches
+  def layout
+    out_string = ""
+    @positions.each {|position| out_string << "#{position.symbol}\n"}
+    out_string
+  end
+
+end
+
+
+# Each piece on the board is an object
+class Piece
+  attr_reader :player, :number
+  attr_accessor :position
+
+  def initialize(position, player, number)
+    @position = position
+    @player = player
+    @number = number
+  end
+
+  def to_s
+    "#{@player}:#{@number}"
+  end
+  
+  def to_str
+    to_s
+  end
+
+  def show(convert_to_zero_based_player_number = false)
+    if convert_to_zero_based_player_number
+      "#{@player + 1}:#{@number}"   
+    else
+      "#{@player}:#{@number}"  
+    end
+  end
+  
+  def move_to(new_position)
+    @position = new_position
+  end
+
+end
+
+
+# A move in a game
+class Move
+  attr_reader :piece, :origin, :destination, :card_played
+  
+  def initialize(piece, origin, destination, card_played = :unspecified)
+    @piece = piece
+    @origin = origin
+    @destination = destination
+    @card_played = card_played
+  end
+
+  def show(board, convert_to_zero_based_player_number = false)
+    if @card_played == :unspecified
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)}"
+    else
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)} (#{@card_played})"
+    end
+  end
+  
+  def format(board, convert_to_zero_based_player_number = false)
+    display_player_number = if convert_to_zero_based_player_number then
+      @piece.player + 1
+    else
+      @piece.player
+    end
+    if @card_played ==:unspecified
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)}"
+    else
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)} #{@card_played}"
+    end
+  end
+  
+  # Write a move to a string
+  # Note the inverse, String#to_move, is defined below
+  def to_s
+    "#{@piece.player} #{@origin.to_s}  #{@destination.to_s} #{@card_played}"
+  end
+  
+  def to_str
+    to_s
+  end
+  
+end
+
+
+
+# A class to record each of the states previously found in a game.  
+# Note that this is a deep copy of the pieces and what they've captured, so 
+# you'll need to convert back
+class GameState
+  attr_accessor :move, :player, :board, :pieces_before_move, :deck_before_move, 
+    :players_cards_before_move, :moves_by_current_player
+  #    this_game_state = GameState.new(move, player, @board, @pieces, @deck, @players_cards)
+  
+  
+  def initialize(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    @move, @player, @board, @pieces_before_move, @deck_before_move, 
+      @players_cards_before_move, @moves_by_current_player = 
+      copy_game_state(move, player, board, pieces, deck, players_cards, 
+      moves_by_current_player)
+  end
+  
+  #  def ==(other)
+  #    @move.to_s == other.move.to_s and
+  #      @player == other.player and
+  #      @piece_after_move == other.pieces_after_move
+  #  end
+  
+  def copy_game_state(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    copy_player = player
+    moving_player = move.piece.player
+
+    copy_board = board.dup
+    copy_board.positions = board.positions.collect {|pos| pos.dup}
+    copy_board.positions.each {|pos| pos.contains = []}
+
+    copy_pieces = pieces.collect do |players_pieces|
+      players_pieces.collect do |piece|
+        new_piece_position = copy_board.positions[board.positions.index(piece.position)]
+        new_piece = Piece.new(new_piece_position, piece.player, piece.number)
+        new_piece_position.contains << new_piece
+        new_piece
+      end
+    end
+
+    piece_index = pieces[moving_player].index(move.piece)
+    origin_index = board.positions.index(move.origin)
+    destination_index = board.positions.index(move.destination)
+    copy_move = Move.new(copy_pieces[moving_player][piece_index], 
+      copy_board.positions[origin_index], 
+      copy_board.positions[destination_index])
+    
+    copy_deck = deck.dup
+    copy_players_cards = players_cards.collect {|p| p.dup}
+    copy_moves_by_current_player = moves_by_current_player
+
+    return copy_move, copy_player, copy_board, copy_pieces, copy_deck, copy_players_cards, copy_moves_by_current_player
+  end
+  
+end
+
+
+# A game of Cartagena.  It keeps a history of all previous states.  
+class Game
+
+  attr_reader :history
+  attr_accessor :current_player
+  attr_reader :players
+  attr_accessor :players_cards
+  attr_accessor :moves_by_current_player
+  attr_reader :board
+  attr_reader :pieces
+  attr_reader :cards
+  attr_accessor :deck
+
+  # Create a new game
+  def initialize(players = 5, number_of_tiles = 6, pieces_each = 6)
+    @board = Board.new(number_of_tiles)
+    @history = []
+    @pieces = []
+    @players = [].fill(0, players) {|i| i}
+    @players_cards = []
+    @current_player = 0
+    @moves_by_current_player = 0
+    @cards = []
+    1.upto($CARDS_PER_SYMBOL) {|x| @cards.concat($SYMBOLS)}
+    @deck = @cards.shuffle
+    @players.each do |p|
+      @pieces[p] = []
+      0.upto(pieces_each - 1) do |count|
+        piece = Piece.new(@board.positions[0], p, count)
+        @pieces[p][count] = piece
+        @board.positions[0].contains << piece
+      end
+      @players_cards[p] = []
+      deal_cards!($INITIAL_CARDS_PER_PLAYER, p)
+    end
+  end
+
+  # Deal some cards to a player.  Remove them from the deck.  Refill the deck
+  # if necessary
+  def deal_cards!(number_of_cards, player = @current_player)
+    1.upto(number_of_cards) do
+      if @deck.empty?
+        @deck = @cards
+        @players_cards.each do |p|
+          p.each do |c|
+            @deck.delete_at(@deck.index(c))
+          end
+        end
+        @deck = @deck.shuffle
+      end
+      @players_cards[player] << @deck.pop
+    end
+  end
+  
+  # Check that a move is valid.  Throw an exception if it's invalid
+  def validate_move(move, player = @current_player)
+    # Check the move is a valid one
+    raise(InvalidMoveError, "Move #{move}: Player #{player} does not exist") unless @players.include?(player)
+    raise(InvalidMoveError, "Move #{move}: None of player #{player}'s pieces on position #{move.origin}") unless move.origin.contains.find {|pc| pc.player == player}
+    raise(InvalidMoveError, "Move #{move}: Origin and destination are the same") if move.origin == move.destination
+  
+    origin_position      = @board.positions.index(move.origin)
+    destination_position = @board.positions.index(move.destination)
+    # Is this move an advance or a retreat?
+    if destination_position > origin_position
+      # Advancing a piece
+      if move.destination == @board.positions[-1] # A move into the boat
+        raise(InvalidMoveError, "Move #{move}: Move into boat and card unspecified") if move.destination == @board.positions[-1] and move.card_played == :unspecified
+        unless @players_cards[player].find {|c| c == move.card_played}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece into the boat")
+        end
+      else
+        if move.card_played != :unspecified and move.destination.symbol != move.card_played
+          raise(InvalidMoveError, "Player #{player} trying to move to #{move.destination}, a #{move.destination.symbol} square with a a #{move.card_played} card")
+        end
+        unless @players_cards[player].find {|c| c == move.destination.symbol}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece to a #{move.destination.symbol} square")
+        end
+        # Check target square is vacant
+        raise(InvalidMoveError, "Advance move #{move}: destination occupied") unless move.destination.contains.empty?
+      end
+      # Check all the intervening squares with this symbol are occupied
+      intervening_empty_position = @board.positions[origin_position...destination_position].index_find do |p| 
+        p.symbol == move.destination.symbol and 
+          p.contains.empty?
+      end
+      raise(InvalidMoveError, "Advance move #{move}: location #{intervening_empty_position} is empty") if intervening_empty_position
+    else
+      # Retreating a piece
+      # Check target position has one or two pieces already on it
+      destination_count = move.destination.contains.length
+      raise(InvalidMoveError, "Retreat move #{move}: destination has no pieces already on it") if destination_count == 0
+      raise(InvalidMoveError, "Retreat move #{move}: destination has too many (#{destination_count}) pieces already on it") if destination_count >= $MAX_PIECES_PER_POSITION
+      # Check none of the intervening squares have any pieces on them
+      # puts "Checking positions #{destination_position} to #{origin_position}"      
+      intervening_target_position = @board.positions[(destination_position + 1)...origin_position].index_find do |p| 
+        # puts "Examining postition #{p} at location #{@board.positions.index(p)} which contains #{p.contains.length} pieces"        
+        p.contains.length > 0 and 
+          p.contains.length < $MAX_PIECES_PER_POSITION
+      end
+      raise(InvalidMoveError, "Retreat move #{move.show(@board)}: location #{intervening_target_position} is a viable target") if intervening_target_position
+    end
+  end
+  
+  # Apply a single move to a game.  
+  def apply_move!(move, player = @current_player, validate = true)
+    
+    validate_move(move, player) if validate
+
+    raise(InvalidMoveError, "Too many consecutive moves by #{@current_player}: has taken #{@moves_by_current_player} and validate is #{validate}") if validate and player == @current_player and @moves_by_current_player >= $MAX_MOVES_PER_TURN
+       
+    # Record the old state
+    this_game_state = GameState.new(move, @current_player, @board, @pieces, @deck, @players_cards, @moves_by_current_player)
+    @history << this_game_state
+    
+    # Apply this move
+    move.origin.contains.delete move.piece
+    move.destination.contains << move.piece
+    move.piece.position = move.destination
+
+    if player == @current_player
+      @moves_by_current_player += 1
+    else
+      @current_player = player
+      @moves_by_current_player = 1
+    end
+   
+    # Update cards
+    if @board.positions.index(move.destination) > @board.positions.index(move.origin)
+      # Advance move
+      if validate
+        card_to_remove = if move.card_played != :unspecified
+          move.card_played
+        else
+          move.destination.symbol
+        end
+        if @players_cards[player].include?(card_to_remove)
+          @players_cards[player].delete_at(@players_cards[player].index(card_to_remove))
+        end
+      end
+    else
+      # Retreat move
+      deal_cards!(move.destination.contains.length - 1, player)
+    end
+    
+    # If this player has all their pieces in the boat, declare a win.
+    if @pieces[player].all? {|pc| pc.position == @board.positions[-1]}
+      raise(GameWonNotice, "Game won by #{player}")
+    end
+  end
+  
+  # Undo a move 
+  def undo_move!
+    state_to_restore = @history[-1]
+    move, @current_player, @board, @pieces, @deck, @players_cards, 
+      @moves_by_current_player = 
+      state_to_restore.copy_game_state(state_to_restore.move, 
+      state_to_restore.player, 
+      state_to_restore.board, 
+      state_to_restore.pieces_before_move, 
+      state_to_restore.deck_before_move, 
+      state_to_restore.players_cards_before_move, 
+      state_to_restore.moves_by_current_player)
+    @history.pop    
+  end
+    
+  # Apply a list of moves in order
+  def apply_moves!(moves)
+    moves.each do |move| 
+      @current_player = move.piece.player
+      apply_move!(move, @current_player)
+      next_player! if @moves_by_current_player >= $MOVES_PER_TURN
+    end
+  end
+
+
+  # Set the current player to be the next player
+  def next_player!
+    if @current_player == @players[-1]
+      @current_player = @players[0]
+    else
+      @current_player = @players[@players.index(@current_player) + 1]
+    end
+    @moves_by_current_player = 0
+    @current_player
+  end
+
+  
+  def reset_current_player(new_current_player)
+    @current_player = new_current_player
+    @moves_by_current_player = 0
+  end
+
+  
+  # Return an array of all possible moves from this state, given the active player
+  def possible_moves(player = @current_player)
+    moves = []
+    @pieces[player].each do |piece|
+      # Do a forward move for each card held
+      unless piece.position == @board.positions[-1]
+        @players_cards[player].each do |card|
+          destination = @board.positions[@board.positions.index(piece.position)..-1].find do |pos|
+            (pos.symbol == card and pos.contains == []) or
+              pos.symbol == :boat
+          end
+          # puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)} to #{@board.positions.index(destination)}, a #{destination.symbol}"        
+          moves << Move.new(piece, piece.position, destination, card)
+        end
+      end
+      # Do a reverse move for the piece
+      unless piece.position == board.positions[0]
+        destination = @board.positions[1...@board.positions.index(piece.position)].reverse.find do |pos|
+          pos.contains.length == 1 or pos.contains.length == 2
+        end
+        if destination
+          # puts "Player #{player}, piece #{piece.number} at position #{@board.positions.index(piece.position)} retreats to #{@board.positions.index(destination)}, a #{destination.symbol} containing #{destination.contains.length} pieces"
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+    end
+    # moves.each {|m| puts m.show(@board)}    
+    moves
+  end
+
+  def build_state_string
+    outstr = "Current player = #{@current_player}\n"
+    0.upto((@board.positions.length)-1) do |i|
+      outstr << "#{i}: #{@board.positions[i].symbol}: "
+      @board.positions[i].contains.each do |piece|
+        outstr << "P#{piece.player}:#{piece.number} "
+      end
+      outstr << "\n"
+    end
+    0.upto((@players.length)-1) do |i|
+      outstr << "Player #{i} holds " << (@players_cards[i].sort_by {|c| c.to_s}).join(', ') << "\n"
+    end
+    outstr << "Deck holds " << @deck.join(', ') << "\n"
+    outstr
+  end
+  
+  # Show the state of the board
+  def show_state
+    puts build_state_string
+  end
+
+  def to_s
+    show_state
+  end
+  
+  def to_str
+    to_s
+  end
+  
+
+  def set_testing_game!
+    srand 1234
+    @board = nil
+    initialize(5, 6, 6)
+    board_symbols = [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat]
+    board_symbols.each_index do |i|
+        @board.positions[i].symbol = board_symbols[i]
+      end
+      
+    @players_cards[0] = [:gun,    :hat,   :skull]
+    @players_cards[1] = [:bottle, :keys,  :keys]
+    @players_cards[2] = [:bottle, :hat,   :keys]
+    @players_cards[3] = [:bottle, :skull, :skull]
+    @players_cards[4] = [:bottle, :gun,   :gun]
+    
+    @deck = [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull]
+    
+  end
+  
+  # Given a set of lines from an input file, turn them into a Game object and 
+  # a set of Move objects.
+  # Note the multiple return values.
+  # Class method
+  def Game.read_game(gamelines)
+    gamelines.each {|l| l.chomp!}
+    game = Game.new(gamelines[0].to_i, 6, 6)
+    
+    # create the board
+    2.upto(38) do |i|
+      game.board.positions[i-1] = Position.new(gamelines[i].to_sym)
+    end
+    
+    # read all the moves so far
+    moves = []
+    i = 39
+    current_player_moves = 0
+    current_player = 0
+    player_card_counts = game.players_cards.map {|p| $INITIAL_CARDS_PER_PLAYER}
+    unused_cards = game.cards.map {|c| c}
+    while gamelines[i].in_move_format?
+      move = gamelines[i].to_move(game, true)
+      if move.piece.player == current_player
+        current_player_moves += 1
+      else
+        current_player = move.piece.player
+        current_player_moves = 1
+      end
+      # if move is an advance move, decrement the cards held by the player and remove the card from unused cards
+      #   throw an error if an advance move is made when all the cards of that type have been used
+      #   if unused_cards becomes empty, restock it.  
+      if game.board.positions.index(move.destination) > game.board.positions.index(move.origin)
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move with no cards in hand.  Move is #{gamelines[i]} on line #{i}") if player_card_counts[move.piece.player] == 0
+        card_used = move.destination.symbol
+        if card_used == :boat
+          card_used = move.card_played
+        end
+        raise(InvalidMoveError, "Attempting to move into boat without a card specified") if card_used == :unspecified
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move to a #{move.destination.symbol} place when all those cards have been used. Move is #{gamelines[i]} on line #{i}") unless unused_cards.include? card_used
+        player_card_counts[current_player] -= 1
+        unused_cards.delete_at(unused_cards.index(card_used))
+      # if move is a retreat move, increment the cards held by the player
+      else
+        player_card_counts[current_player] += move.destination.contains.length
+        if unused_cards.length == player_card_counts.inject {|sum, n| sum + n }
+          unused_cards = game.cards.map {|c| c}
+        end
+      end
+      game.apply_move!(move, move.piece.player, false)
+      moves << move
+      i = i + 1
+    end
+    
+    # note the current player
+    game.current_player = gamelines[i].to_i - 1
+    if game.current_player == current_player
+      game.moves_by_current_player = current_player_moves
+    else
+      game.moves_by_current_player = 0
+    end
+    current_player = game.current_player
+    
+    # read the cards
+    game.players_cards = game.players_cards.map {|c| []}
+    (i+1).upto(gamelines.length - 1) do |j|
+      game.players_cards[game.current_player] << gamelines[j].to_sym
+      raise(InvalidMoveError, "Player #{game.current_player} given a #{gamelines[j]} card, but none left in deck") unless unused_cards.index(gamelines[j].to_sym)
+      unused_cards.delete_at(unused_cards.index(gamelines[j].to_sym))
+    end
+    raise(InvalidMoveError, "Player #{game.current_player} given #{game.players_cards[game.current_player].length} cards, but should have #{player_card_counts[game.current_player]} cards from play") if game.players_cards[game.current_player].length != player_card_counts[game.current_player]
+    
+    # Update the game deck
+    game.deck = unused_cards.shuffle
+    player_card_counts[game.current_player] = 0
+    player_card_counts.each_index do |player|
+      if player_card_counts[player] > 0
+        game.deal_cards!(player_card_counts[player], player)
+      end
+    end
+    return game, moves
+  end
+
+end
+  
+
+# Extension to String class to convert a move-description string into a Move object.  
+# This is the inverse of the Move#to_s method
+class String
+  def in_move_format?
+    elements = split
+    return ((elements.length == 3 or elements.length == 4) and
+        elements[0].to_i > 0 and elements[0].to_i < 6 and
+        elements[1].to_i >= 0 and elements[1].to_i <= 37 and
+        elements[2].to_i >= 0 and elements[2].to_i <= 37)
+  end
+  
+  def to_move(game, convert_to_zero_based_player_number = false)
+    move_elements = self.downcase.split
+    player = move_elements[0].to_i
+    player = player - 1 if convert_to_zero_based_player_number
+    origin_index = move_elements[1].to_i
+    destination_index = move_elements[2].to_i
+    raise(InvalidMoveError, "Invalid origin #{origin_index} in move read") unless origin_index >= 0 and origin_index < game.board.positions.length
+    raise(InvalidMoveError, "Invalid destination #{destination_index} in move read") unless destination_index > 0 and destination_index <= game.board.positions.length
+    raise(InvalidMoveError, "Player #{player} does not have a piece on position #{origin_index} in move read") unless game.board.positions[origin_index].contains.any? {|p| p.player == player}
+    piece = game.board.positions[origin_index].contains.find {|p| p.player == player}
+    piece_name = piece.number
+    if destination_index > origin_index
+      if move_elements.length == 4 
+        card_used = move_elements[3].chomp.to_sym
+        raise(InvalidMoveError, "Card used (#{card_used}) does not match destination space (#{game.board.positions[destination_index].symbol})") unless card_used == game.board.positions[destination_index].symbol or destination_index == game.board.positions.length - 1
+        Move.new(game.pieces[player][piece_name], 
+                 game.board.positions[origin_index], 
+                 game.board.positions[destination_index],
+                 card_used)
+      else
+        card_used = :unspecified
+        raise(InvalidMoveError, "Move to boat without specifying card used") if destination_index == game.board.positions.length + 1
+        Move.new(game.pieces[player][piece_name], 
+                 game.board.positions[origin_index], 
+                 game.board.positions[destination_index])
+      end
+    else
+      Move.new(game.pieces[player][piece_name], 
+        game.board.positions[origin_index], 
+        game.board.positions[destination_index])
+    end
+  end
+end
+
+
+# Read a game description file and convert it into a Game object and set of Move objects.
+# Note that Game.read_game method returns multiple values, so this one does too.
+class IO
+  def IO.read_game(filename)
+    gamelines = IO.readlines(filename)
+    return Game.read_game(gamelines)
+  end
+end
+
+# Extension to the Array class to include the Array#shuffle function
+class Array
+  def shuffle
+    sort_by { rand }
+  end
+  
+  def index_find(&block)
+    found = find(&block)
+    if found
+      index found
+    else
+      found
+    end
+  end
+end
diff --git a/lib/.svn/text-base/main.rb.svn-base b/lib/.svn/text-base/main.rb.svn-base
new file mode 100644 (file)
index 0000000..bf9b84d
--- /dev/null
@@ -0,0 +1,6 @@
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+puts "Hello World"
diff --git a/lib/.svn/text-base/random-move.rb.netbeans-base b/lib/.svn/text-base/random-move.rb.netbeans-base
new file mode 100644 (file)
index 0000000..53f6c5f
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/ruby
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# clockwise-p1 
+#  Game state file read on STDIN
+#  Move produced on STDOUT
+#
+# == Author
+# Neil Smith
+
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'libcartagena'
+require 'rdoc/usage'
+
+gamelines = readlines
+game, moves = Game.read_game(gamelines)
+possibles = game.possible_moves
+move = possibles[rand(possibles.length)]
+puts move.format(game.board, true)
diff --git a/lib/.svn/text-base/random-move.rb.svn-base b/lib/.svn/text-base/random-move.rb.svn-base
new file mode 100644 (file)
index 0000000..53f6c5f
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/ruby
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# clockwise-p1 
+#  Game state file read on STDIN
+#  Move produced on STDOUT
+#
+# == Author
+# Neil Smith
+
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'libcartagena'
+require 'rdoc/usage'
+
+gamelines = readlines
+game, moves = Game.read_game(gamelines)
+possibles = game.possible_moves
+move = possibles[rand(possibles.length)]
+puts move.format(game.board, true)
diff --git a/lib/.svn/tmp/tempfile.2.tmp b/lib/.svn/tmp/tempfile.2.tmp
new file mode 100644 (file)
index 0000000..220ac25
--- /dev/null
@@ -0,0 +1,497 @@
+# == Synopsis
+#
+# Library to support Cartagena play
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  11 Jun 2008  
+#                * Initial build
+
+# Symbols
+$SYMBOLS = [:bottle, :dagger, :gun, :hat, :keys, :skull]
+
+# The number of cards of each symbol in the deck
+$CARDS_PER_SYMBOL = 17
+
+# The number of cards initiall dealt to each player
+$INITIAL_CARDS_PER_PLAYER = 3
+
+# Maximum number of pieces on a position
+$MAX_PIECES_PER_POSITION = 3
+
+# Errors for a game
+
+# Moves can only be [1..6] spaces
+class InvalidMoveError < StandardError
+end
+
+# Game is won when only one player has uncaptured pieces
+class GameWonNotice < StandardError
+end
+
+# A position on the board.
+class Position
+  attr_reader :symbol, :contains
+  
+  def initialize(symbol)
+    @symbol = symbol
+    @contains = []
+  end
+end
+
+# A tile that makes up the board.  Each tile has two sides, each with six
+# positions.  Tiles can be either way up, and either way round.
+class Tile
+  attr_reader :exposed, :front, :back
+  
+  def initialize(front, back)
+    @front = front.collect {|s| Position.new(s)}
+    @back = back.collect {|s| Position.new(s)}
+    @exposed = @front
+    @exposed_side = :front
+  end
+  
+  def flip!
+    if @exposed_side == :front
+      @exposed = @back
+      @exposed_side = :back
+    else
+      @exposed = @front
+      @exposed_side = :front
+    end
+  end
+  
+  def reverse!
+    @exposed = @exposed.reverse
+  end
+end
+
+
+# The game board
+class Board
+
+  attr_reader :positions
+  attr_reader :tiles
+
+  # A laborious procedure to create all the positions and tie them all together
+  def initialize(tiles_used)
+    # A hash of all positions, indexed by position names
+    @tiles = [Tile.new([:hat, :keys, :gun, :bottle, :skull, :dagger], 
+        [:hat, :keys, :gun, :bottle, :skull, :dagger]),
+      Tile.new([:gun, :hat, :dagger, :skull, :bottle, :keys],
+        [:gun, :hat, :dagger, :skull, :bottle, :keys]),
+      Tile.new([:skull, :gun, :bottle, :keys, :dagger, :hat],
+        [:skull, :gun, :bottle, :keys, :dagger, :hat]),
+      Tile.new([:dagger, :bottle, :keys, :gun, :hat, :skull],
+        [:dagger, :bottle, :keys, :gun, :hat, :skull]),
+      Tile.new([:keys, :dagger, :skull, :hat, :gun, :bottle],
+        [:keys, :dagger, :skull, :hat, :gun, :bottle]),
+      Tile.new([:bottle, :skull, :hat, :dagger, :keys, :gun],
+        [:bottle, :skull, :hat, :dagger, :keys, :gun])                 
+    ].shuffle[0...tiles_used]
+    @tiles = @tiles.each do |t|
+      if rand < 0.5
+        t.reverse!
+      else
+        t
+      end
+    end
+
+    @positions = [Position.new(:cell)]
+    @tiles.each {|t| t.exposed.each {|p| @positions << p}}
+    @positions << Position.new(:boat)
+  end # def
+
+  def to_s
+    layout
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # For each position, show its name and what it touches
+  def layout
+    out_string = ""
+    @positions.each {|position| out_string << "#{position}\n"}
+    out_string
+  end
+
+end
+
+
+# Each piece on the board is an object
+class Piece
+  attr_reader :player, :number
+  attr_accessor :position
+
+  def initialize(position, player, number)
+    @position = position
+    @player = player
+    @number = number
+  end
+
+  def to_s
+    "#{@player}:#{@number}"
+  end
+  
+  def to_str
+    to_s
+  end
+
+  def move_to(new_position)
+    @position = new_position
+  end
+
+end
+
+
+# A move in a game
+class Move
+  attr_reader :piece, :origin, :destination
+  
+  def initialize(piece, origin, destination)
+    @piece = piece
+    @origin = origin
+    @destination = destination
+  end
+
+  def show(board)
+    "#{@piece.to_s}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)}"
+  end
+  
+  # Write a move to a string
+  # Note the inverse, String#to_move, is defined below
+  def to_s
+    "#{@piece.player}: #{@origin.to_s} ->  #{@destination.to_s}"
+  end
+  
+  def to_str
+    to_s
+  end
+  
+end
+
+
+
+# A class to record each of the states previously found in a game.  
+# Note that this is a deep copy of the pieces and what they've captured, so 
+# you'll need to convert back
+class GameState
+  attr_accessor :move, :player, :pieces_after_move
+  
+  def initialize(move, player, pieces)
+    @move = move
+    @player = player
+    @pieces_after_move = Hash.new
+    pieces.each {|k, p| @pieces_after_move[k] = p.dup}
+  end
+  
+  def ==(other)
+    @move.to_s == other.move.to_s and
+      @player == other.player and
+      @piece_after_move == other.pieces_after_move
+  end
+  
+end
+
+
+# A game of Cartagena.  It keeps a history of all previous states.  
+class Game
+
+  attr_reader :history
+  attr_reader :current_player, :players, :players_cards
+  attr_reader :board
+  attr_reader :pieces
+  attr_reader :deck
+
+  # Create a new game
+  def initialize(players = 6, number_of_tiles = 6, pieces_each = 6)
+    @board = Board.new(number_of_tiles)
+    @history = []
+    @pieces = []
+    @players = [].fill(0, players) {|i| i}
+    @players_cards = []
+    @current_player = 0
+    @cards = []
+    1.upto($CARDS_PER_SYMBOL) {|x| @cards.concat($SYMBOLS)}
+    @deck = @cards.shuffle
+    @players.each do |p|
+      @pieces[p] = []
+      0.upto(pieces_each - 1) do |count|
+        piece = Piece.new(@board.positions[0], p, count)
+        @pieces[p][count] = piece
+        @board.positions[0].contains << piece
+      end
+      @players_cards[p] = []
+      deal_cards!($INITIAL_CARDS_PER_PLAYER, p)
+    end
+  end
+  
+  def deal_cards!(number_of_cards, player = @current_player)
+    1.upto(number_of_cards) do
+      @deck = @cards.shuffle if @deck.empty?
+      @players_cards[player] << @deck.pop
+    end
+  end
+  
+  # Check that a move is valid.  Throw an exception if it's invalid
+  def validate_move(move, player = @current_player)
+    # Check the move is a valid one
+    raise(InvalidMoveError, "Move #{move}: Player #{player} does not exist") unless @players.include?(player)
+    raise(InvalidMoveError, "Move #{move}: None of player #{player}'s pieces on position #{move.origin}") unless move.origin.contains.find {|pc| pc.player == player}
+    raise(InvalidMoveError, "Move #{move}: Origin and destination are the same") if move.origin == move.destination
+  
+    origin_position      = @board.positions.index(move.origin)
+    destination_position = @board.positions.index(move.destination)
+    # Is this move an advance or a retreat?
+    if destination_position > origin_position
+      # Advancing a piece
+      unless @players_cards[player].find {|c| c == move.destination.symbol}
+        raise(InvalidMoveError, "Player #{player} does not have a card to move a piece to a #{move.destination.symbol} square")
+      end
+      # Check target square is vacant
+      raise(InvalidMoveError, "Advance move #{move}: destination occupied") unless move.destination.contains.empty?
+      # Check all the intervening squares with this symbol are occupied
+      intervening_empty_position = @board.positions[origin_position...destination_position].index_find do |p| 
+          p.symbol == move.destination.symbol and 
+          p.contains.empty?
+      end
+      raise(InvalidMoveError, "Advance move #{move}: location #{intervening_empty_position} is empty") if intervening_empty_position
+    else
+      # Retreating a piece
+      # Check target position has one or two pieces already on it
+      destination_count = move.destination.contains.length
+      raise(InvalidMoveError, "Retreat move #{move}: destination has no pieces already on it") if destination_count == 0
+      raise(InvalidMoveError, "Retreat move #{move}: destination has too many (#{destination_count}) pieces already on it") if destination_count >= $MAX_PIECES_PER_POSITION
+      # Check none of the intervening squares have any pieces on them
+      intervening_target_position = @board.positions[destination_position...origin_position].index_find do |p| 
+          p.contains.length > 0 and 
+          p.contains.length < $MAX_PIECES_PER_POSITION
+      end
+      raise(InvalidMoveError, "Retreat move #{move}: location #{intervening_target_position} is a viable target") if intervening_target_position
+    end
+  end
+  
+  # Apply a single move to a game.  
+  def apply_move!(move, player = @current_player)
+    
+    validate_move(move, player)
+    
+    # Apply this move
+    move.origin.contains.delete move.piece
+    move.destination.contains << move.piece
+    move.piece.position = move.destination
+
+    # Update cards
+    if @board.positions.index(move.destination) > @board.positions.index(move.origin)
+      # Advance move
+      @players_cards[player].delete_at(@players_cards[player].index(move.destination.symbol))
+    else
+      # Retreat move
+      deal_cards!(move.destination.contains.length, player)
+    end
+
+    # Record the new stae
+#    this_game_state = GameState.new(move, player, @pieces)
+#    @history << this_game_state
+
+    # If this player has all their pieces in the boat, declare a win.
+    if @pieces[player].all? {|pc| pc.position == :boat}
+      raise(GameWonNotice, "Game won by #{player}")
+    end
+  end
+  
+  # Undo a move 
+  def undo_move!
+    if @history.length > 1
+      # general case
+      state_to_restore = @history[-2]
+      @current_player = @history[-1].player
+      @pieces.each do |name, piece| 
+        copy_piece = state_to_restore.pieces_after_move[name]
+        piece.position = copy_piece.position
+      end
+      @history.pop    
+    elsif @history.length == 1
+      # reset to start
+      @current_player = @players[1]
+      @pieces.each do |name, piece| 
+        piece.position = @board.positions[piece.colour]
+      end
+      @history.pop    
+    end
+  end
+    
+  # Apply a list of moves in order
+  def apply_moves!(moves)
+    moves.each do |move| 
+      if move.via_base?
+        moved_distance = board.distance_between[move.piece.position.place][@current_player] +
+          board.distance_between[@current_player][move.destination.place]
+      else
+        moved_distance = board.distance_between[move.piece.position.place][move.destination.place]
+      end
+      self.apply_move!(move, @current_player)
+      next_player! unless moved_distance == 6
+    end
+  end
+
+
+  # Set the current player to be the next player
+  def next_player!
+    original_player = @current_player
+    if @current_player == @players[-1]
+      @current_player = @players[1]
+    else
+      @current_player = @players[@players.index(@current_player) + 1]
+    end
+    @current_player
+  end
+
+
+  # Return an array of all possible moves from this state, given the active player
+  def possible_moves(player = @current_player)
+    moves = []
+    @pieces[player].each do |piece|
+      # Do a forward move for each card held
+      unless piece.position == @board.positions[-1]
+        @players_cards[player].each do |card|
+          puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)}"        
+          destination = @board.positions[@board.positions.index(piece.position)..-1].find do |pos|
+            (pos.symbol == card and pos.contains == []) or
+              pos.symbol == :boat
+          end
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+      # Do a reverse move for the piece
+      unless piece.position == board.positions[0]
+        destination = @board.positions[0...@board.positions.index(piece.position)].reverse.find do |pos|
+          pos.contains.length == 1 or pos.contains.length == 2
+        end
+        moves << Move.new(piece, piece.position, destination)
+      end
+    end
+    moves
+  end
+
+
+  def build_state_string
+    outstr = "Current player = #{@current_player}\n"
+    0.upto((@board.positions.length)-1) do |i|
+      outstr << "#{i}: #{@board.positions[i].symbol}: "
+      @board.positions[i].contains.each do |piece|
+        outstr << "P#{piece.player}:#{piece.number} "
+      end
+      outstr << "\n"
+    end
+    0.upto((@players.length)-1) do |i|
+      outstr << "Player #{i} holds " << (@players_cards[i].sort_by {|c| c.to_s}).join(', ') << "\n"
+    end
+    outstr << "Deck holds " << @deck.join(', ') << "\n"
+    outstr
+  end
+  
+  # Show the state of the board
+  def show_state
+    puts build_state_string
+    #    @pieces.keys.sort.each do |piece_name|
+    #      if @pieces[piece_name].captured
+    #        puts "Piece #{piece_name} captured, at #{@pieces[piece_name].position}"
+    #      else
+    #        puts "Piece #{piece_name} is at #{@pieces[piece_name].position}, holds #{(@pieces[piece_name].contains.collect{|c| c.name}).join(' ')}"
+    #      end
+    #    end
+  end
+
+  def to_s
+    show_state
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # Given a set of lines from an input file, turn them into a Game object and 
+  # a set of Move objects.
+  # Note the multiple return values.
+  # Class method
+  def Game.read_game(gamelines)
+    gamelines.each {|l| l.chomp!}
+    game = Game.new(gamelines[0].to_i, 6, 6, 6, 6)
+    moves = []
+    gamelines[1..-2].each {|m| moves << m.to_move(game)}
+    return game, moves, gamelines[-1].to_i
+  end
+
+
+end
+  
+
+# Extension to String class to convert a move-description string into a Move object.  
+# This is the inverse of the Move#to_s method
+class String
+  def to_move(game)
+    move_elements = self.downcase.split
+    piece_name = move_elements[0]
+    destination_name = move_elements[-1]
+    if destination_name.length > 2 and 
+        destination_name[-2,2] == game.board.centre.place[-2,2]
+      destination_name = game.board.centre.place
+    end
+    raise(InvalidMoveError, "Invalid piece in move read") unless game.pieces.has_key?(piece_name)
+    raise(InvalidMoveError, "Invalid destination in move read") unless game.board.positions.has_key?(destination_name)
+    # Deal with the synonyms for the centre position
+    via_base = (destination_name.length == 1 or move_elements.length > 2)
+    Move.new(game.pieces[piece_name], game.board.positions[destination_name], via_base)
+  end
+end
+
+
+# Read a game description file and convert it into a Game object and set of Move objects.
+# Note that Game.read_game method returns multiple values, so this one does too.
+class IO
+  def IO.read_game(filename)
+    gamelines = IO.readlines(filename)
+    return Game.read_game(gamelines)
+  end
+end
+
+# Extension to the Array class to include the Array#shuffle function
+class Array
+  def shuffle
+    sort_by { rand }
+  end
+  
+<<<<<<< .mine
+  def index_find(&proc)
+    self.index(self.find &proc) # added some additional comments
+=======
+  def index_find(&block)
+    found = find(&block)
+    if found
+      index found
+    else
+      found
+    end
+>>>>>>> .r32
+  end
+end
diff --git a/lib/.svn/tmp/tempfile.3.tmp b/lib/.svn/tmp/tempfile.3.tmp
new file mode 100644 (file)
index 0000000..8c1eb00
--- /dev/null
@@ -0,0 +1,503 @@
+# == Synopsis
+#
+# Library to support Cartagena play
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  11 Jun 2008  
+#                * Initial build
+
+# Symbols
+$SYMBOLS = [:bottle, :dagger, :gun, :hat, :keys, :skull]
+
+# The number of cards of each symbol in the deck
+$CARDS_PER_SYMBOL = 17
+
+# The number of cards initiall dealt to each player
+$INITIAL_CARDS_PER_PLAYER = 3
+
+# Maximum number of pieces on a position
+$MAX_PIECES_PER_POSITION = 3
+
+# Errors for a game
+
+# Moves can only be [1..6] spaces
+class InvalidMoveError < StandardError
+end
+
+# Game is won when only one player has uncaptured pieces
+class GameWonNotice < StandardError
+end
+
+# A position on the board.
+class Position
+  attr_reader :symbol, :contains
+  
+  def initialize(symbol)
+    @symbol = symbol
+    @contains = []
+  end
+end
+
+# A tile that makes up the board.  Each tile has two sides, each with six
+# positions.  Tiles can be either way up, and either way round.
+class Tile
+  attr_reader :exposed, :front, :back
+  
+  def initialize(front, back)
+    @front = front.collect {|s| Position.new(s)}
+    @back = back.collect {|s| Position.new(s)}
+    @exposed = @front
+    @exposed_side = :front
+  end
+  
+  def flip!
+    if @exposed_side == :front
+      @exposed = @back
+      @exposed_side = :back
+    else
+      @exposed = @front
+      @exposed_side = :front
+    end
+  end
+  
+  def reverse!
+    @exposed = @exposed.reverse
+  end
+end
+
+
+# The game board
+class Board
+
+  attr_reader :positions
+  attr_reader :tiles
+
+  # A laborious procedure to create all the positions and tie them all together
+  def initialize(tiles_used)
+    # A hash of all positions, indexed by position names
+    @tiles = [Tile.new([:hat, :keys, :gun, :bottle, :skull, :dagger], 
+        [:hat, :keys, :gun, :bottle, :skull, :dagger]),
+      Tile.new([:gun, :hat, :dagger, :skull, :bottle, :keys],
+        [:gun, :hat, :dagger, :skull, :bottle, :keys]),
+      Tile.new([:skull, :gun, :bottle, :keys, :dagger, :hat],
+        [:skull, :gun, :bottle, :keys, :dagger, :hat]),
+      Tile.new([:dagger, :bottle, :keys, :gun, :hat, :skull],
+        [:dagger, :bottle, :keys, :gun, :hat, :skull]),
+      Tile.new([:keys, :dagger, :skull, :hat, :gun, :bottle],
+        [:keys, :dagger, :skull, :hat, :gun, :bottle]),
+      Tile.new([:bottle, :skull, :hat, :dagger, :keys, :gun],
+        [:bottle, :skull, :hat, :dagger, :keys, :gun])                 
+    ].shuffle[0...tiles_used]
+    @tiles = @tiles.each do |t|
+      if rand < 0.5
+        t.reverse!
+      else
+        t
+      end
+    end
+
+    @positions = [Position.new(:cell)]
+    @tiles.each {|t| t.exposed.each {|p| @positions << p}}
+    @positions << Position.new(:boat)
+  end # def
+
+  def to_s
+    layout
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # For each position, show its name and what it touches
+  def layout
+    out_string = ""
+    @positions.each {|position| out_string << "#{position.symbol}\n"}
+    out_string
+  end
+
+end
+
+
+# Each piece on the board is an object
+class Piece
+  attr_reader :player, :number
+  attr_accessor :position
+
+  def initialize(position, player, number)
+    @position = position
+    @player = player
+    @number = number
+  end
+
+  def to_s
+    "#{@player}:#{@number}"
+  end
+  
+  def to_str
+    to_s
+  end
+
+  def move_to(new_position)
+    @position = new_position
+  end
+
+end
+
+
+# A move in a game
+class Move
+  attr_reader :piece, :origin, :destination
+  
+  def initialize(piece, origin, destination)
+    @piece = piece
+    @origin = origin
+    @destination = destination
+  end
+
+  def show(board)
+    "#{@piece.to_s}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)}"
+  end
+  
+  # Write a move to a string
+  # Note the inverse, String#to_move, is defined below
+  def to_s
+    "#{@piece.player}: #{@origin.to_s} ->  #{@destination.to_s}"
+  end
+  
+  def to_str
+    to_s
+  end
+  
+end
+
+
+
+# A class to record each of the states previously found in a game.  
+# Note that this is a deep copy of the pieces and what they've captured, so 
+# you'll need to convert back
+class GameState
+  attr_accessor :move, :player, :pieces_after_move
+  
+  def initialize(move, player, pieces)
+    @move = move
+    @player = player
+    @pieces_after_move = Hash.new
+    pieces.each {|k, p| @pieces_after_move[k] = p.dup}
+  end
+  
+  def ==(other)
+    @move.to_s == other.move.to_s and
+      @player == other.player and
+      @piece_after_move == other.pieces_after_move
+  end
+  
+end
+
+
+# A game of Cartagena.  It keeps a history of all previous states.  
+class Game
+
+  attr_reader :history
+  attr_reader :current_player, :players, :players_cards
+  attr_reader :board
+  attr_reader :pieces
+  attr_reader :deck
+
+  # Create a new game
+  def initialize(players = 6, number_of_tiles = 6, pieces_each = 6)
+    @board = Board.new(number_of_tiles)
+    @history = []
+    @pieces = []
+    @players = [].fill(0, players) {|i| i}
+    @players_cards = []
+    @current_player = 0
+    @cards = []
+    1.upto($CARDS_PER_SYMBOL) {|x| @cards.concat($SYMBOLS)}
+    @deck = @cards.shuffle
+    @players.each do |p|
+      @pieces[p] = []
+      0.upto(pieces_each - 1) do |count|
+        piece = Piece.new(@board.positions[0], p, count)
+        @pieces[p][count] = piece
+        @board.positions[0].contains << piece
+      end
+      @players_cards[p] = []
+      deal_cards!($INITIAL_CARDS_PER_PLAYER, p)
+    end
+  end
+  
+  def deal_cards!(number_of_cards, player = @current_player)
+    1.upto(number_of_cards) do
+      @deck = @cards.shuffle if @deck.empty?
+      @players_cards[player] << @deck.pop
+    end
+  end
+  
+  # Check that a move is valid.  Throw an exception if it's invalid
+  def validate_move(move, player = @current_player)
+    # Check the move is a valid one
+    raise(InvalidMoveError, "Move #{move}: Player #{player} does not exist") unless @players.include?(player)
+    raise(InvalidMoveError, "Move #{move}: None of player #{player}'s pieces on position #{move.origin}") unless move.origin.contains.find {|pc| pc.player == player}
+    raise(InvalidMoveError, "Move #{move}: Origin and destination are the same") if move.origin == move.destination
+  
+    origin_position      = @board.positions.index(move.origin)
+    destination_position = @board.positions.index(move.destination)
+    # Is this move an advance or a retreat?
+    if destination_position > origin_position
+      # Advancing a piece
+      unless @players_cards[player].find {|c| c == move.destination.symbol}
+        raise(InvalidMoveError, "Player #{player} does not have a card to move a piece to a #{move.destination.symbol} square")
+      end
+      # Check target square is vacant
+      raise(InvalidMoveError, "Advance move #{move}: destination occupied") unless move.destination.contains.empty?
+      # Check all the intervening squares with this symbol are occupied
+      intervening_empty_position = @board.positions[origin_position...destination_position].index_find do |p| 
+          p.symbol == move.destination.symbol and 
+          p.contains.empty?
+      end
+      raise(InvalidMoveError, "Advance move #{move}: location #{intervening_empty_position} is empty") if intervening_empty_position
+    else
+      # Retreating a piece
+      # Check target position has one or two pieces already on it
+      destination_count = move.destination.contains.length
+      raise(InvalidMoveError, "Retreat move #{move}: destination has no pieces already on it") if destination_count == 0
+      raise(InvalidMoveError, "Retreat move #{move}: destination has too many (#{destination_count}) pieces already on it") if destination_count >= $MAX_PIECES_PER_POSITION
+      # Check none of the intervening squares have any pieces on them
+# puts "Checking positions #{destination_position} to #{origin_position}"      
+      intervening_target_position = @board.positions[(destination_position + 1)...origin_position].index_find do |p| 
+# puts "Examining postition #{p} at location #{@board.positions.index(p)} which contains #{p.contains.length} pieces"        
+          p.contains.length > 0 and 
+          p.contains.length < $MAX_PIECES_PER_POSITION
+      end
+      raise(InvalidMoveError, "Retreat move #{move.show(@board)}: location #{intervening_target_position} is a viable target") if intervening_target_position
+    end
+  end
+  
+  # Apply a single move to a game.  
+  def apply_move!(move, player = @current_player)
+    
+    validate_move(move, player)
+    
+    # Apply this move
+    move.origin.contains.delete move.piece
+    move.destination.contains << move.piece
+    move.piece.position = move.destination
+
+    # Update cards
+    if @board.positions.index(move.destination) > @board.positions.index(move.origin)
+      # Advance move
+      @players_cards[player].delete_at(@players_cards[player].index(move.destination.symbol))
+    else
+      # Retreat move
+      deal_cards!(move.destination.contains.length - 1, player)
+    end
+
+    # Record the new stae
+#    this_game_state = GameState.new(move, player, @pieces)
+#    @history << this_game_state
+
+    # If this player has all their pieces in the boat, declare a win.
+    if @pieces[player].all? {|pc| pc.position == :boat}
+      raise(GameWonNotice, "Game won by #{player}")
+    end
+  end
+  
+  # Undo a move 
+  def undo_move!
+    if @history.length > 1
+      # general case
+      state_to_restore = @history[-2]
+      @current_player = @history[-1].player
+      @pieces.each do |name, piece| 
+        copy_piece = state_to_restore.pieces_after_move[name]
+        piece.position = copy_piece.position
+      end
+      @history.pop    
+    elsif @history.length == 1
+      # reset to start
+      @current_player = @players[1]
+      @pieces.each do |name, piece| 
+        piece.position = @board.positions[piece.colour]
+      end
+      @history.pop    
+    end
+  end
+    
+  # Apply a list of moves in order
+  def apply_moves!(moves)
+    moves.each do |move| 
+      if move.via_base?
+        moved_distance = board.distance_between[move.piece.position.place][@current_player] +
+          board.distance_between[@current_player][move.destination.place]
+      else
+        moved_distance = board.distance_between[move.piece.position.place][move.destination.place]
+      end
+      self.apply_move!(move, @current_player)
+      next_player! unless moved_distance == 6
+    end
+  end
+
+
+  # Set the current player to be the next player
+  def next_player!
+    original_player = @current_player
+    if @current_player == @players[-1]
+      @current_player = @players[1]
+    else
+      @current_player = @players[@players.index(@current_player) + 1]
+    end
+    @current_player
+  end
+
+
+  # Return an array of all possible moves from this state, given the active player
+  def possible_moves(player = @current_player)
+    moves = []
+    @pieces[player].each do |piece|
+      # Do a forward move for each card held
+      unless piece.position == @board.positions[-1]
+        @players_cards[player].each do |card|
+          destination = @board.positions[@board.positions.index(piece.position)..-1].find do |pos|
+            (pos.symbol == card and pos.contains == []) or
+              pos.symbol == :boat
+          end
+# puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)} to #{@board.positions.index(destination)}, a #{destination.symbol}"        
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+      # Do a reverse move for the piece
+      unless piece.position == board.positions[0]
+        destination = @board.positions[0...@board.positions.index(piece.position)].reverse.find do |pos|
+          pos.contains.length == 1 or pos.contains.length == 2
+        end
+        if destination
+# puts "Player #{player}, piece #{piece.number} at position #{@board.positions.index(piece.position)} retreats to #{@board.positions.index(destination)}, a #{destination.symbol} containing #{destination.contains.length} pieces"
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+    end
+# moves.each {|m| puts m.show(@board)}    
+    moves
+  end
+
+  def build_state_string
+    outstr = "Current player = #{@current_player}\n"
+    0.upto((@board.positions.length)-1) do |i|
+      outstr << "#{i}: #{@board.positions[i].symbol}: "
+      @board.positions[i].contains.each do |piece|
+        outstr << "P#{piece.player}:#{piece.number} "
+      end
+      outstr << "\n"
+    end
+    0.upto((@players.length)-1) do |i|
+      outstr << "Player #{i} holds " << (@players_cards[i].sort_by {|c| c.to_s}).join(', ') << "\n"
+    end
+    outstr << "Deck holds " << @deck.join(', ') << "\n"
+    outstr
+  end
+  
+  # Show the state of the board
+  def show_state
+    puts build_state_string
+    #    @pieces.keys.sort.each do |piece_name|
+    #      if @pieces[piece_name].captured
+    #        puts "Piece #{piece_name} captured, at #{@pieces[piece_name].position}"
+    #      else
+    #        puts "Piece #{piece_name} is at #{@pieces[piece_name].position}, holds #{(@pieces[piece_name].contains.collect{|c| c.name}).join(' ')}"
+    #      end
+    #    end
+  end
+
+  def to_s
+    show_state
+  end
+  
+  def to_str
+    to_s
+  end
+  
+
+  def set_testing_game!
+    srand 1234
+    @board = nil
+    initialize(6, 6, 6)
+  end
+  
+  # Given a set of lines from an input file, turn them into a Game object and 
+  # a set of Move objects.
+  # Note the multiple return values.
+  # Class method
+  def Game.read_game(gamelines)
+    gamelines.each {|l| l.chomp!}
+    game = Game.new(gamelines[0].to_i, 6, 6, 6, 6)
+    moves = []
+    gamelines[1..-2].each {|m| moves << m.to_move(game)}
+    return game, moves, gamelines[-1].to_i
+  end
+
+
+end
+  
+
+# Extension to String class to convert a move-description string into a Move object.  
+# This is the inverse of the Move#to_s method
+class String
+  def to_move(game)
+    move_elements = self.downcase.split
+    piece_name = move_elements[0]
+    destination_name = move_elements[-1]
+    if destination_name.length > 2 and 
+        destination_name[-2,2] == game.board.centre.place[-2,2]
+      destination_name = game.board.centre.place
+    end
+    raise(InvalidMoveError, "Invalid piece in move read") unless game.pieces.has_key?(piece_name)
+    raise(InvalidMoveError, "Invalid destination in move read") unless game.board.positions.has_key?(destination_name)
+    # Deal with the synonyms for the centre position
+    via_base = (destination_name.length == 1 or move_elements.length > 2)
+    Move.new(game.pieces[piece_name], game.board.positions[destination_name], via_base)
+  end
+end
+
+
+# Read a game description file and convert it into a Game object and set of Move objects.
+# Note that Game.read_game method returns multiple values, so this one does too.
+class IO
+  def IO.read_game(filename)
+    gamelines = IO.readlines(filename)
+    return Game.read_game(gamelines)
+  end
+end
+
+# Extension to the Array class to include the Array#shuffle function
+class Array
+  def shuffle
+    sort_by { rand }
+  end
+  
+  def index_find(&block)
+    found = find(&block)
+    if found
+      index found
+    else
+      found
+    end
+  end
+end
diff --git a/lib/.svn/tmp/tempfile.4.tmp b/lib/.svn/tmp/tempfile.4.tmp
new file mode 100644 (file)
index 0000000..6efb53c
--- /dev/null
@@ -0,0 +1,510 @@
+# == Synopsis
+#
+# Library to support Cartagena play
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  11 Jun 2008  
+#                * Initial build
+
+# Symbols
+$SYMBOLS = [:bottle, :dagger, :gun, :hat, :keys, :skull]
+
+# The number of cards of each symbol in the deck
+$CARDS_PER_SYMBOL = 17
+
+# The number of cards initiall dealt to each player
+$INITIAL_CARDS_PER_PLAYER = 3
+
+# Maximum number of pieces on a position
+$MAX_PIECES_PER_POSITION = 3
+
+# Number of actions that can be taken by each player in sequence
+$MAX_MOVES_PER_TURN = 3
+
+# Errors for a game
+
+# Moves can only be [1..6] spaces
+class InvalidMoveError < StandardError
+end
+
+# Game is won when only one player has uncaptured pieces
+class GameWonNotice < StandardError
+end
+
+# A position on the board.
+class Position
+  attr_reader :symbol, :contains
+  
+  def initialize(symbol)
+    @symbol = symbol
+    @contains = []
+  end
+end
+
+# A tile that makes up the board.  Each tile has two sides, each with six
+# positions.  Tiles can be either way up, and either way round.
+class Tile
+  attr_reader :exposed, :front, :back
+  
+  def initialize(front, back)
+    @front = front.collect {|s| Position.new(s)}
+    @back = back.collect {|s| Position.new(s)}
+    @exposed = @front
+    @exposed_side = :front
+  end
+  
+  def flip!
+    if @exposed_side == :front
+      @exposed = @back
+      @exposed_side = :back
+    else
+      @exposed = @front
+      @exposed_side = :front
+    end
+  end
+  
+  def reverse!
+    @exposed = @exposed.reverse
+  end
+end
+
+
+# The game board
+class Board
+
+  attr_reader :positions
+  attr_reader :tiles
+
+  # A laborious procedure to create all the positions and tie them all together
+  def initialize(tiles_used)
+    # A hash of all positions, indexed by position names
+    @tiles = [Tile.new([:hat, :keys, :gun, :bottle, :skull, :dagger], 
+        [:hat, :keys, :gun, :bottle, :skull, :dagger]),
+      Tile.new([:gun, :hat, :dagger, :skull, :bottle, :keys],
+        [:gun, :hat, :dagger, :skull, :bottle, :keys]),
+      Tile.new([:skull, :gun, :bottle, :keys, :dagger, :hat],
+        [:skull, :gun, :bottle, :keys, :dagger, :hat]),
+      Tile.new([:dagger, :bottle, :keys, :gun, :hat, :skull],
+        [:dagger, :bottle, :keys, :gun, :hat, :skull]),
+      Tile.new([:keys, :dagger, :skull, :hat, :gun, :bottle],
+        [:keys, :dagger, :skull, :hat, :gun, :bottle]),
+      Tile.new([:bottle, :skull, :hat, :dagger, :keys, :gun],
+        [:bottle, :skull, :hat, :dagger, :keys, :gun])                 
+    ].shuffle[0...tiles_used]
+    @tiles = @tiles.each do |t|
+      if rand < 0.5
+        t.reverse!
+      else
+        t
+      end
+    end
+
+    @positions = [Position.new(:cell)]
+    @tiles.each {|t| t.exposed.each {|p| @positions << p}}
+    @positions << Position.new(:boat)
+  end # def
+
+  def to_s
+    layout
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # For each position, show its name and what it touches
+  def layout
+    out_string = ""
+    @positions.each {|position| out_string << "#{position.symbol}\n"}
+    out_string
+  end
+
+end
+
+
+# Each piece on the board is an object
+class Piece
+  attr_reader :player, :number
+  attr_accessor :position
+
+  def initialize(position, player, number)
+    @position = position
+    @player = player
+    @number = number
+  end
+
+  def to_s
+    "#{@player}:#{@number}"
+  end
+  
+  def to_str
+    to_s
+  end
+
+  def move_to(new_position)
+    @position = new_position
+  end
+
+end
+
+
+# A move in a game
+class Move
+  attr_reader :piece, :origin, :destination
+  
+  def initialize(piece, origin, destination)
+    @piece = piece
+    @origin = origin
+    @destination = destination
+  end
+
+  def show(board)
+    "#{@piece.to_s}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)}"
+  end
+  
+  # Write a move to a string
+  # Note the inverse, String#to_move, is defined below
+  def to_s
+    "#{@piece.player}: #{@origin.to_s} ->  #{@destination.to_s}"
+  end
+  
+  def to_str
+    to_s
+  end
+  
+end
+
+
+
+# A class to record each of the states previously found in a game.  
+# Note that this is a deep copy of the pieces and what they've captured, so 
+# you'll need to convert back
+class GameState
+  attr_accessor :move, :player, :pieces_after_move, :deck_after_move, 
+    :players_cards_after_move, :moves_by_current_player
+#    this_game_state = GameState.new(move, player, @board, @pieces, @deck, @players_cards)
+  
+  
+  def initialize(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    @move = move
+    @player = player
+    @pieces_after_move = Hash.new
+    pieces.each {|k, p| @pieces_after_move[k] = p.dup}
+  end
+  
+  def ==(other)
+    @move.to_s == other.move.to_s and
+      @player == other.player and
+      @piece_after_move == other.pieces_after_move
+  end
+  
+end
+
+
+# A game of Cartagena.  It keeps a history of all previous states.  
+class Game
+
+  attr_reader :history
+  attr_reader :current_player, :players, :players_cards
+  attr_reader :moves_by_current_player
+  attr_reader :board
+  attr_reader :pieces
+  attr_reader :deck
+
+  # Create a new game
+  def initialize(players = 6, number_of_tiles = 6, pieces_each = 6)
+    @board = Board.new(number_of_tiles)
+    @history = []
+    @pieces = []
+    @players = [].fill(0, players) {|i| i}
+    @players_cards = []
+    @current_player = 0
+    @moves_by_current_player = 0
+    @cards = []
+    1.upto($CARDS_PER_SYMBOL) {|x| @cards.concat($SYMBOLS)}
+    @deck = @cards.shuffle
+    @players.each do |p|
+      @pieces[p] = []
+      0.upto(pieces_each - 1) do |count|
+        piece = Piece.new(@board.positions[0], p, count)
+        @pieces[p][count] = piece
+        @board.positions[0].contains << piece
+      end
+      @players_cards[p] = []
+      deal_cards!($INITIAL_CARDS_PER_PLAYER, p)
+    end
+  end
+  
+  def deal_cards!(number_of_cards, player = @current_player)
+    1.upto(number_of_cards) do
+      @deck = @cards.shuffle if @deck.empty?
+      @players_cards[player] << @deck.pop
+    end
+  end
+  
+  # Check that a move is valid.  Throw an exception if it's invalid
+  def validate_move(move, player = @current_player)
+    # Check the move is a valid one
+    raise(InvalidMoveError, "Move #{move}: Player #{player} does not exist") unless @players.include?(player)
+    raise(InvalidMoveError, "Move #{move}: None of player #{player}'s pieces on position #{move.origin}") unless move.origin.contains.find {|pc| pc.player == player}
+    raise(InvalidMoveError, "Move #{move}: Origin and destination are the same") if move.origin == move.destination
+  
+    origin_position      = @board.positions.index(move.origin)
+    destination_position = @board.positions.index(move.destination)
+    # Is this move an advance or a retreat?
+    if destination_position > origin_position
+      # Advancing a piece
+      unless @players_cards[player].find {|c| c == move.destination.symbol}
+        raise(InvalidMoveError, "Player #{player} does not have a card to move a piece to a #{move.destination.symbol} square")
+      end
+      # Check target square is vacant
+      raise(InvalidMoveError, "Advance move #{move}: destination occupied") unless move.destination.contains.empty?
+      # Check all the intervening squares with this symbol are occupied
+      intervening_empty_position = @board.positions[origin_position...destination_position].index_find do |p| 
+          p.symbol == move.destination.symbol and 
+          p.contains.empty?
+      end
+      raise(InvalidMoveError, "Advance move #{move}: location #{intervening_empty_position} is empty") if intervening_empty_position
+    else
+      # Retreating a piece
+      # Check target position has one or two pieces already on it
+      destination_count = move.destination.contains.length
+      raise(InvalidMoveError, "Retreat move #{move}: destination has no pieces already on it") if destination_count == 0
+      raise(InvalidMoveError, "Retreat move #{move}: destination has too many (#{destination_count}) pieces already on it") if destination_count >= $MAX_PIECES_PER_POSITION
+      # Check none of the intervening squares have any pieces on them
+# puts "Checking positions #{destination_position} to #{origin_position}"      
+      intervening_target_position = @board.positions[(destination_position + 1)...origin_position].index_find do |p| 
+# puts "Examining postition #{p} at location #{@board.positions.index(p)} which contains #{p.contains.length} pieces"        
+          p.contains.length > 0 and 
+          p.contains.length < $MAX_PIECES_PER_POSITION
+      end
+      raise(InvalidMoveError, "Retreat move #{move.show(@board)}: location #{intervening_target_position} is a viable target") if intervening_target_position
+    end
+  end
+  
+  # Apply a single move to a game.  
+  def apply_move!(move, player = @current_player)
+    
+    validate_move(move, player)
+    
+    # Apply this move
+    move.origin.contains.delete move.piece
+    move.destination.contains << move.piece
+    move.piece.position = move.destination
+
+    # Update cards
+    if @board.positions.index(move.destination) > @board.positions.index(move.origin)
+      # Advance move
+      @players_cards[player].delete_at(@players_cards[player].index(move.destination.symbol))
+    else
+      # Retreat move
+      deal_cards!(move.destination.contains.length - 1, player)
+    end
+
+    # Record the new stae
+#    this_game_state = GameState.new(move, player, @board, @pieces, @deck, @players_cards, @moves_by_current_player)
+#    @history << this_game_state
+
+    if player == @current_player
+      @moves_by_current_player = @moves_by_current_player + 1
+    end
+    
+    # If this player has all their pieces in the boat, declare a win.
+    if @pieces[player].all? {|pc| pc.position == :boat}
+      raise(GameWonNotice, "Game won by #{player}")
+    end
+  end
+  
+  # Undo a move 
+#  def undo_move!
+#    if @history.length > 1
+#      # general case
+#      state_to_restore = @history[-2]
+#      @current_player = @history[-1].player
+#      @pieces.each do |name, piece| 
+#        copy_piece = state_to_restore.pieces_after_move[name]
+#        piece.position = copy_piece.position
+#      end
+#      @history.pop    
+#    elsif @history.length == 1
+#      # reset to start
+#      @current_player = @players[1]
+#      @pieces.each do |name, piece| 
+#        piece.position = @board.positions[piece.colour]
+#      end
+#      @history.pop    
+#    end
+#  end
+    
+  # Apply a list of moves in order
+  def apply_moves!(moves)
+    moves.each do |move| 
+      apply_move!(move, @current_player)
+      next_player! if @moves_by_current_player >= $MOVES_PER_TURN
+    end
+  end
+
+
+  # Set the current player to be the next player
+  def next_player!
+    original_player = @current_player
+    if @current_player == @players[-1]
+      @current_player = @players[0]
+    else
+      @current_player = @players[@players.index(@current_player) + 1]
+    end
+    @moves_by_current_player = 0
+    @current_player
+  end
+
+
+  # Return an array of all possible moves from this state, given the active player
+  def possible_moves(player = @current_player)
+    moves = []
+    @pieces[player].each do |piece|
+      # Do a forward move for each card held
+      unless piece.position == @board.positions[-1]
+        @players_cards[player].each do |card|
+          destination = @board.positions[@board.positions.index(piece.position)..-1].find do |pos|
+            (pos.symbol == card and pos.contains == []) or
+              pos.symbol == :boat
+          end
+# puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)} to #{@board.positions.index(destination)}, a #{destination.symbol}"        
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+      # Do a reverse move for the piece
+      unless piece.position == board.positions[0]
+        destination = @board.positions[0...@board.positions.index(piece.position)].reverse.find do |pos|
+          pos.contains.length == 1 or pos.contains.length == 2
+        end
+        if destination
+# puts "Player #{player}, piece #{piece.number} at position #{@board.positions.index(piece.position)} retreats to #{@board.positions.index(destination)}, a #{destination.symbol} containing #{destination.contains.length} pieces"
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+    end
+# moves.each {|m| puts m.show(@board)}    
+    moves
+  end
+
+  def build_state_string
+    outstr = "Current player = #{@current_player}\n"
+    0.upto((@board.positions.length)-1) do |i|
+      outstr << "#{i}: #{@board.positions[i].symbol}: "
+      @board.positions[i].contains.each do |piece|
+        outstr << "P#{piece.player}:#{piece.number} "
+      end
+      outstr << "\n"
+    end
+    0.upto((@players.length)-1) do |i|
+      outstr << "Player #{i} holds " << (@players_cards[i].sort_by {|c| c.to_s}).join(', ') << "\n"
+    end
+    outstr << "Deck holds " << @deck.join(', ') << "\n"
+    outstr
+  end
+  
+  # Show the state of the board
+  def show_state
+    puts build_state_string
+    #    @pieces.keys.sort.each do |piece_name|
+    #      if @pieces[piece_name].captured
+    #        puts "Piece #{piece_name} captured, at #{@pieces[piece_name].position}"
+    #      else
+    #        puts "Piece #{piece_name} is at #{@pieces[piece_name].position}, holds #{(@pieces[piece_name].contains.collect{|c| c.name}).join(' ')}"
+    #      end
+    #    end
+  end
+
+  def to_s
+    show_state
+  end
+  
+  def to_str
+    to_s
+  end
+  
+
+  def set_testing_game!
+    srand 1234
+    @board = nil
+    initialize(6, 6, 6)
+  end
+  
+  # Given a set of lines from an input file, turn them into a Game object and 
+  # a set of Move objects.
+  # Note the multiple return values.
+  # Class method
+  def Game.read_game(gamelines)
+    gamelines.each {|l| l.chomp!}
+    game = Game.new(gamelines[0].to_i, 6, 6, 6, 6)
+    moves = []
+    gamelines[1..-2].each {|m| moves << m.to_move(game)}
+    return game, moves, gamelines[-1].to_i
+  end
+
+
+end
+  
+
+# Extension to String class to convert a move-description string into a Move object.  
+# This is the inverse of the Move#to_s method
+class String
+  def to_move(game)
+    move_elements = self.downcase.split
+    piece_name = move_elements[0]
+    destination_name = move_elements[-1]
+    if destination_name.length > 2 and 
+        destination_name[-2,2] == game.board.centre.place[-2,2]
+      destination_name = game.board.centre.place
+    end
+    raise(InvalidMoveError, "Invalid piece in move read") unless game.pieces.has_key?(piece_name)
+    raise(InvalidMoveError, "Invalid destination in move read") unless game.board.positions.has_key?(destination_name)
+    # Deal with the synonyms for the centre position
+    via_base = (destination_name.length == 1 or move_elements.length > 2)
+    Move.new(game.pieces[piece_name], game.board.positions[destination_name], via_base)
+  end
+end
+
+
+# Read a game description file and convert it into a Game object and set of Move objects.
+# Note that Game.read_game method returns multiple values, so this one does too.
+class IO
+  def IO.read_game(filename)
+    gamelines = IO.readlines(filename)
+    return Game.read_game(gamelines)
+  end
+end
+
+# Extension to the Array class to include the Array#shuffle function
+class Array
+  def shuffle
+    sort_by { rand }
+  end
+  
+  def index_find(&block)
+    found = find(&block)
+    if found
+      index found
+    else
+      found
+    end
+  end
+end
diff --git a/lib/.svn/tmp/tempfile.5.tmp b/lib/.svn/tmp/tempfile.5.tmp
new file mode 100644 (file)
index 0000000..6ddb8d2
--- /dev/null
@@ -0,0 +1,707 @@
+# == Synopsis
+#
+# Library to support Cartagena play
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  11 Jun 2008  
+#                * Initial build
+
+# Symbols
+$SYMBOLS = [:bottle, :dagger, :gun, :hat, :keys, :skull]
+
+# The number of cards of each symbol in the deck
+$CARDS_PER_SYMBOL = 17
+
+# The number of cards initiall dealt to each player
+$INITIAL_CARDS_PER_PLAYER = 3
+
+# Maximum number of pieces on a position
+$MAX_PIECES_PER_POSITION = 3
+
+# Number of actions that can be taken by each player in sequence
+$MAX_MOVES_PER_TURN = 3
+
+# Errors for a game
+
+# Moves can only be [1..6] spaces
+class InvalidMoveError < StandardError
+end
+
+# Game is won when only one player has uncaptured pieces
+class GameWonNotice < StandardError
+end
+
+# A position on the board.
+class Position
+  attr_reader :symbol
+  attr_accessor :contains
+  
+  def initialize(symbol)
+    @symbol = symbol
+    @contains = []
+  end
+end
+
+# A tile that makes up the board.  Each tile has two sides, each with six
+# positions.  Tiles can be either way up, and either way round.
+class Tile
+  attr_reader :exposed, :front, :back
+  
+  def initialize(front, back)
+    @front = front.collect {|s| Position.new(s)}
+    @back = back.collect {|s| Position.new(s)}
+    @exposed = @front
+    @exposed_side = :front
+  end
+  
+  def flip!
+    if @exposed_side == :front
+      @exposed = @back
+      @exposed_side = :back
+    else
+      @exposed = @front
+      @exposed_side = :front
+    end
+  end
+  
+  def reverse!
+    @exposed = @exposed.reverse
+  end
+end
+
+
+# The game board
+class Board
+
+  attr_accessor :positions
+  attr_reader :tiles
+
+  # A laborious procedure to create all the positions and tie them all together
+  def initialize(tiles_used)
+    # A hash of all positions, indexed by position names
+    @tiles = [Tile.new([:hat, :keys, :gun, :bottle, :skull, :dagger], 
+        [:hat, :keys, :gun, :bottle, :skull, :dagger]),
+      Tile.new([:gun, :hat, :dagger, :skull, :bottle, :keys],
+        [:gun, :hat, :dagger, :skull, :bottle, :keys]),
+      Tile.new([:skull, :gun, :bottle, :keys, :dagger, :hat],
+        [:skull, :gun, :bottle, :keys, :dagger, :hat]),
+      Tile.new([:dagger, :bottle, :keys, :gun, :hat, :skull],
+        [:dagger, :bottle, :keys, :gun, :hat, :skull]),
+      Tile.new([:keys, :dagger, :skull, :hat, :gun, :bottle],
+        [:keys, :dagger, :skull, :hat, :gun, :bottle]),
+      Tile.new([:bottle, :skull, :hat, :dagger, :keys, :gun],
+        [:bottle, :skull, :hat, :dagger, :keys, :gun])                 
+    ].shuffle[0...tiles_used]
+    @tiles = @tiles.each do |t|
+      if rand < 0.5
+        t.reverse!
+      else
+        t
+      end
+    end
+
+    @positions = [Position.new(:cell)]
+    @tiles.each {|t| t.exposed.each {|p| @positions << p}}
+    @positions << Position.new(:boat)
+  end # def
+
+  def to_s
+    layout
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # For each position, show its name and what it touches
+  def layout
+    out_string = ""
+    @positions.each {|position| out_string << "#{position.symbol}\n"}
+    out_string
+  end
+
+end
+
+
+# Each piece on the board is an object
+class Piece
+  attr_reader :player, :number
+  attr_accessor :position
+
+  def initialize(position, player, number)
+    @position = position
+    @player = player
+    @number = number
+  end
+
+  def to_s
+    "#{@player}:#{@number}"
+  end
+  
+  def to_str
+    to_s
+  end
+
+  def show(convert_to_zero_based_player_number = false)
+    if convert_to_zero_based_player_number
+      "#{@player + 1}:#{@number}"   
+    else
+      "#{@player}:#{@number}"  
+    end
+  end
+  
+  def move_to(new_position)
+    @position = new_position
+  end
+
+end
+
+
+# A move in a game
+class Move
+  attr_reader :piece, :origin, :destination, :card_played
+  
+  def initialize(piece, origin, destination, card_played = :unspecified)
+    @piece = piece
+    @origin = origin
+    @destination = destination
+    @card_played = card_played
+  end
+
+  def show(board, convert_to_zero_based_player_number = false)
+    if @card_played == :unspecified
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)}"
+    else
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)} (#{@card_played})"
+    end
+  end
+  
+  def format(board, convert_to_zero_based_player_number = false)
+    display_player_number = if convert_to_zero_based_player_number then
+      @piece.player + 1
+    else
+      @piece.player
+    end
+    if @card_played ==:unspecified
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)}"
+    else
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)} #{@card_played}"
+    end
+  end
+  
+  # Write a move to a string
+  # Note the inverse, String#to_move, is defined below
+  def to_s
+    "#{@piece.player} #{@origin.to_s}  #{@destination.to_s} #{@card_played}"
+  end
+  
+  def to_str
+    to_s
+  end
+  
+end
+
+
+
+# A class to record each of the states previously found in a game.  
+# Note that this is a deep copy of the pieces and what they've captured, so 
+# you'll need to convert back
+class GameState
+  attr_accessor :move, :player, :board, :pieces_before_move, :deck_before_move, 
+    :players_cards_before_move, :moves_by_current_player
+  #    this_game_state = GameState.new(move, player, @board, @pieces, @deck, @players_cards)
+  
+  
+  def initialize(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    @move, @player, @board, @pieces_before_move, @deck_before_move, 
+      @players_cards_before_move, @moves_by_current_player = 
+      copy_game_state(move, player, board, pieces, deck, players_cards, 
+      moves_by_current_player)
+  end
+  
+  #  def ==(other)
+  #    @move.to_s == other.move.to_s and
+  #      @player == other.player and
+  #      @piece_after_move == other.pieces_after_move
+  #  end
+  
+  def copy_game_state(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    copy_player = player
+    moving_player = move.piece.player
+
+    copy_board = board.dup
+    copy_board.positions = board.positions.collect {|pos| pos.dup}
+    copy_board.positions.each {|pos| pos.contains = []}
+
+    copy_pieces = pieces.collect do |players_pieces|
+      players_pieces.collect do |piece|
+        new_piece_position = copy_board.positions[board.positions.index(piece.position)]
+        new_piece = Piece.new(new_piece_position, piece.player, piece.number)
+        new_piece_position.contains << new_piece
+        new_piece
+      end
+    end
+
+    piece_index = pieces[moving_player].index(move.piece)
+    origin_index = board.positions.index(move.origin)
+    destination_index = board.positions.index(move.destination)
+    copy_move = Move.new(copy_pieces[moving_player][piece_index], 
+      copy_board.positions[origin_index], 
+      copy_board.positions[destination_index])
+    
+    copy_deck = deck.dup
+    copy_players_cards = players_cards.collect {|p| p.dup}
+    copy_moves_by_current_player = moves_by_current_player
+
+    return copy_move, copy_player, copy_board, copy_pieces, copy_deck, copy_players_cards, copy_moves_by_current_player
+  end
+  
+end
+
+
+# A game of Cartagena.  It keeps a history of all previous states.  
+class Game
+
+  attr_reader :history
+  attr_accessor :current_player
+  attr_reader :players
+  attr_accessor :players_cards
+  attr_accessor :moves_by_current_player
+  attr_reader :board
+  attr_reader :pieces
+  attr_reader :cards
+  attr_accessor :deck
+
+  # Create a new game
+  def initialize(players = 5, number_of_tiles = 6, pieces_each = 6)
+    @board = Board.new(number_of_tiles)
+    @history = []
+    @pieces = []
+    @players = [].fill(0, players) {|i| i}
+    @players_cards = []
+    @current_player = 0
+    @moves_by_current_player = 0
+    @cards = []
+    1.upto($CARDS_PER_SYMBOL) {|x| @cards.concat($SYMBOLS)}
+    @deck = @cards.shuffle
+    @players.each do |p|
+      @pieces[p] = []
+      0.upto(pieces_each - 1) do |count|
+        piece = Piece.new(@board.positions[0], p, count)
+        @pieces[p][count] = piece
+        @board.positions[0].contains << piece
+      end
+      @players_cards[p] = []
+      deal_cards!($INITIAL_CARDS_PER_PLAYER, p)
+    end
+  end
+  
+  def deal_cards!(number_of_cards, player = @current_player)
+    1.upto(number_of_cards) do
+      if @deck.empty?
+        @deck = @cards
+        @players_cards.each do |p|
+          p.each do |c|
+            @deck.delete_at(@deck.index(c))
+          end
+        end
+        @deck = @deck.shuffle
+      end
+#      @deck = @cards.shuffle if @deck.empty?
+      @players_cards[player] << @deck.pop
+    end
+  end
+  
+  # Check that a move is valid.  Throw an exception if it's invalid
+  def validate_move(move, player = @current_player)
+    # Check the move is a valid one
+    raise(InvalidMoveError, "Move #{move}: Player #{player} does not exist") unless @players.include?(player)
+    raise(InvalidMoveError, "Move #{move}: None of player #{player}'s pieces on position #{move.origin}") unless move.origin.contains.find {|pc| pc.player == player}
+    raise(InvalidMoveError, "Move #{move}: Origin and destination are the same") if move.origin == move.destination
+  
+    origin_position      = @board.positions.index(move.origin)
+    destination_position = @board.positions.index(move.destination)
+    # Is this move an advance or a retreat?
+    if destination_position > origin_position
+      # Advancing a piece
+      if move.destination == @board.positions[-1] # A move into the boat
+        raise(InvalidMoveError, "Move #{move}: Move into boat and card unspecified") if move.destination == @board.positions[-1] and move.card_played == :unspecified
+        unless @players_cards[player].find {|c| c == move.card_played}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece into the boat")
+        end
+      else
+        if move.card_played != :unspecified and move.destination.symbol != move.card_played
+          raise(InvalidMoveError, "Player #{player} trying to move to #{move.destination}, a #{move.destination.symbol} square with a a #{move.card_played} card")
+        end
+        unless @players_cards[player].find {|c| c == move.destination.symbol}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece to a #{move.destination.symbol} square")
+        end
+        # Check target square is vacant
+        raise(InvalidMoveError, "Advance move #{move}: destination occupied") unless move.destination.contains.empty?
+      end
+      # Check all the intervening squares with this symbol are occupied
+      intervening_empty_position = @board.positions[origin_position...destination_position].index_find do |p| 
+        p.symbol == move.destination.symbol and 
+          p.contains.empty?
+      end
+      raise(InvalidMoveError, "Advance move #{move}: location #{intervening_empty_position} is empty") if intervening_empty_position
+    else
+      # Retreating a piece
+      # Check target position has one or two pieces already on it
+      destination_count = move.destination.contains.length
+      raise(InvalidMoveError, "Retreat move #{move}: destination has no pieces already on it") if destination_count == 0
+      raise(InvalidMoveError, "Retreat move #{move}: destination has too many (#{destination_count}) pieces already on it") if destination_count >= $MAX_PIECES_PER_POSITION
+      # Check none of the intervening squares have any pieces on them
+      # puts "Checking positions #{destination_position} to #{origin_position}"      
+      intervening_target_position = @board.positions[(destination_position + 1)...origin_position].index_find do |p| 
+        # puts "Examining postition #{p} at location #{@board.positions.index(p)} which contains #{p.contains.length} pieces"        
+        p.contains.length > 0 and 
+          p.contains.length < $MAX_PIECES_PER_POSITION
+      end
+      raise(InvalidMoveError, "Retreat move #{move.show(@board)}: location #{intervening_target_position} is a viable target") if intervening_target_position
+    end
+  end
+  
+  # Apply a single move to a game.  
+  def apply_move!(move, player = @current_player, validate = true)
+    
+    validate_move(move, player) if validate
+
+    raise(InvalidMoveError, "Too many consecutive moves by #{@current_player}: has taken #{@moves_by_current_player} and validate is #{validate}") if validate and player == @current_player and @moves_by_current_player >= $MAX_MOVES_PER_TURN
+       
+    # Record the old state
+    this_game_state = GameState.new(move, @current_player, @board, @pieces, @deck, @players_cards, @moves_by_current_player)
+    @history << this_game_state
+    
+    # Apply this move
+    move.origin.contains.delete move.piece
+    move.destination.contains << move.piece
+    move.piece.position = move.destination
+
+    if player == @current_player
+      @moves_by_current_player += 1
+    else
+      @current_player = player
+      @moves_by_current_player = 1
+    end
+   
+    # Update cards
+    if @board.positions.index(move.destination) > @board.positions.index(move.origin)
+      # Advance move
+      if validate
+        card_to_remove = if move.card_played != :unspecified
+          move.card_played
+        else
+          move.destination.symbol
+        end
+        if @players_cards[player].include?(card_to_remove)
+          @players_cards[player].delete_at(@players_cards[player].index(card_to_remove))
+        end
+      end
+    else
+      # Retreat move
+      deal_cards!(move.destination.contains.length - 1, player)
+    end
+    
+    # If this player has all their pieces in the boat, declare a win.
+    if @pieces[player].all? {|pc| pc.position == @board.positions[-1]}
+      raise(GameWonNotice, "Game won by #{player}")
+    end
+  end
+  
+  # Undo a move 
+  def undo_move!
+    state_to_restore = @history[-1]
+    move, @current_player, @board, @pieces, @deck, @players_cards, 
+      @moves_by_current_player = 
+      state_to_restore.copy_game_state(state_to_restore.move, 
+      state_to_restore.player, 
+      state_to_restore.board, 
+      state_to_restore.pieces_before_move, 
+      state_to_restore.deck_before_move, 
+      state_to_restore.players_cards_before_move, 
+      state_to_restore.moves_by_current_player)
+    @history.pop    
+  end
+    
+  # Apply a list of moves in order
+  def apply_moves!(moves)
+    moves.each do |move| 
+      @current_player = move.piece.player
+      apply_move!(move, @current_player)
+      next_player! if @moves_by_current_player >= $MOVES_PER_TURN
+    end
+  end
+
+
+  # Set the current player to be the next player
+  def next_player!
+    if @current_player == @players[-1]
+      @current_player = @players[0]
+    else
+      @current_player = @players[@players.index(@current_player) + 1]
+    end
+    @moves_by_current_player = 0
+    @current_player
+  end
+
+  
+  def reset_current_player(new_current_player)
+    @current_player = new_current_player
+    @moves_by_current_player = 0
+  end
+
+  
+  # Return an array of all possible moves from this state, given the active player
+  def possible_moves(player = @current_player)
+    moves = []
+    @pieces[player].each do |piece|
+      # Do a forward move for each card held
+      unless piece.position == @board.positions[-1]
+        @players_cards[player].each do |card|
+          destination = @board.positions[@board.positions.index(piece.position)..-1].find do |pos|
+            (pos.symbol == card and pos.contains == []) or
+              pos.symbol == :boat
+          end
+          # puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)} to #{@board.positions.index(destination)}, a #{destination.symbol}"        
+          moves << Move.new(piece, piece.position, destination, card)
+        end
+      end
+      # Do a reverse move for the piece
+      unless piece.position == board.positions[0]
+        destination = @board.positions[1...@board.positions.index(piece.position)].reverse.find do |pos|
+          pos.contains.length == 1 or pos.contains.length == 2
+        end
+        if destination
+          # puts "Player #{player}, piece #{piece.number} at position #{@board.positions.index(piece.position)} retreats to #{@board.positions.index(destination)}, a #{destination.symbol} containing #{destination.contains.length} pieces"
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+    end
+    # moves.each {|m| puts m.show(@board)}    
+    moves
+  end
+
+  def build_state_string
+    outstr = "Current player = #{@current_player}\n"
+    0.upto((@board.positions.length)-1) do |i|
+      outstr << "#{i}: #{@board.positions[i].symbol}: "
+      @board.positions[i].contains.each do |piece|
+        outstr << "P#{piece.player}:#{piece.number} "
+      end
+      outstr << "\n"
+    end
+    0.upto((@players.length)-1) do |i|
+      outstr << "Player #{i} holds " << (@players_cards[i].sort_by {|c| c.to_s}).join(', ') << "\n"
+    end
+    outstr << "Deck holds " << @deck.join(', ') << "\n"
+    outstr
+  end
+  
+  # Show the state of the board
+  def show_state
+    puts build_state_string
+    #    @pieces.keys.sort.each do |piece_name|
+    #      if @pieces[piece_name].captured
+    #        puts "Piece #{piece_name} captured, at #{@pieces[piece_name].position}"
+    #      else
+    #        puts "Piece #{piece_name} is at #{@pieces[piece_name].position}, holds #{(@pieces[piece_name].contains.collect{|c| c.name}).join(' ')}"
+    #      end
+    #    end
+  end
+
+  def to_s
+    show_state
+  end
+  
+  def to_str
+    to_s
+  end
+  
+
+  def set_testing_game!
+    srand 1234
+    @board = nil
+    initialize(5, 6, 6)
+  end
+  
+  # Given a set of lines from an input file, turn them into a Game object and 
+  # a set of Move objects.
+  # Note the multiple return values.
+  # Class method
+  def Game.read_game(gamelines)
+    gamelines.each {|l| l.chomp!}
+#    puts "Creating game with #{gamelines[0].to_i} players"    
+    game = Game.new(gamelines[0].to_i, 6, 6)
+    
+    # create the board
+    2.upto(38) do |i|
+      game.board.positions[i-1] = Position.new(gamelines[i].to_sym)
+#      puts "Making board position #{i-1} a #{gamelines[i].to_sym}"      
+    end
+    
+    # read all the moves so far
+    moves = []
+    i = 39
+    current_player_moves = 0
+    current_player = 0
+<<<<<<< .mine
+    player_card_counts = game.players_cards.map{|p| 3}
+#    player_card_counts.each {|i| i = 3} 
+#    unused_cards = game.cards.map{|c| c}
+=======
+    player_card_counts = game.players_cards.map {|p| $INITIAL_CARDS_PER_PLAYER}
+    unused_cards = game.cards.map {|c| c}
+>>>>>>> .r49
+    while gamelines[i].in_move_format?
+      move = gamelines[i].to_move(game, true)
+#      puts "Applying move #{move.show(game.board)} from line #{i} of the file.  Current player before move application is #{game.current_player}"      
+      if move.piece.player == current_player
+        current_player_moves += 1
+      else
+        current_player = move.piece.player
+        current_player_moves = 1
+      end
+      # if move is an advance move, decrement the cards held by the player and remove the card from unused cards
+      #   throw an error if an advance move is made when all the cards of that type have been used
+      #   if unused_cards becomes empty, restock it.  
+      if game.board.positions.index(move.destination) > game.board.positions.index(move.origin)
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move with no cards in hand.  Move is #{gamelines[i]} on line #{i}") if player_card_counts[move.piece.player] == 0
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move to a #{move.destination.symbol} place when all those cards have been used. Move is #{gamelines[i]} on line #{i}") unless unused_cards.include? move.destination.symbol
+        player_card_counts[current_player] -= 1
+        unused_cards.delete_at(unused_cards.index(move.destination.symbol))
+      # if move is a retreat move, increment the cards held by the player
+      else
+        player_card_counts[current_player] += move.destination.contains.length
+        if unused_cards.length == player_card_counts.inject {|sum, n| sum + n }
+          unused_cards = game.cards.map {|c| c}
+        end
+      end
+      game.apply_move!(move, move.piece.player, false)
+      moves << move
+#      puts "Applied move #{move.show(game.board)} from line #{i} of the file"      
+      i = i + 1
+    end
+    
+    # note the current player
+    game.current_player = gamelines[i].to_i - 1
+    if game.current_player == current_player
+      game.moves_by_current_player = current_player_moves
+    else
+      game.moves_by_current_player = 0
+    end
+    puts "Setting current player to #{game.current_player} on line #{i}"   
+    current_player = game.current_player
+    
+    # read the cards
+    game.players_cards = game.players_cards.map {|c| []}
+player_card_counts.each_index {|index| puts "#{index} should have #{player_card_counts[index]} cards"}
+game.players_cards.each_index {|index| puts "#{index} has #{game.players_cards[index]} cards"}
+puts "Current player is #{game.current_player} whose cards are #{game.players_cards[game.current_player]}"
+#    game.players_cards[game.current_player] = []
+    (i+1).upto(gamelines.length - 1) do |j|
+      game.players_cards[game.current_player] << gamelines[j].to_sym
+      unused_cards.delete_at(unused_cards.index(move.destination.symbol))
+      puts "Reading line #{j} to give player #{game.current_player} a #{gamelines[j].to_sym} card"
+    end
+    raise(InvalidMoveError, "Player #{game.current_player} given #{game.players_cards[game.current_player].length} cards, but should have #{player_card_counts[game.current_player]} cards from play") if game.players_cards[game.current_player].length != player_card_counts[game.current_player]
+    
+    # Update the game deck
+    
+puts "#{unused_cards.length} unused cards"    
+player_card_counts.each {|c| puts "has #{c} cards"}
+    unused_cards = unused_cards.shuffle
+    player_card_counts[game.current_player] = 0
+    player_card_counts.each_index do |player|
+      if player_card_counts[player] > 0
+        game.deal_cards!(player_card_counts[player], player)
+#        1.upto(count) {unused_cards.pop}
+      end
+    end
+    game.deck = unused_cards
+puts "#{game.deck.length} cards remaining in deck"   
+game.players_cards.each {|cards| puts "holds #{cards}"}
+    return game, moves
+  end
+
+end
+  
+
+# Extension to String class to convert a move-description string into a Move object.  
+# This is the inverse of the Move#to_s method
+class String
+  def in_move_format?
+    elements = split
+    return ((elements.length == 3 or elements.length == 4) and
+        elements[0].to_i > 0 and elements[0].to_i < 6 and
+        elements[1].to_i >= 0 and elements[1].to_i <= 37 and
+        elements[2].to_i >= 0 and elements[2].to_i <= 37)
+  end
+  
+  def to_move(game, convert_to_zero_based_player_number = false)
+    move_elements = self.downcase.split
+    player = move_elements[0].to_i
+    player = player - 1 if convert_to_zero_based_player_number
+    origin_index = move_elements[1].to_i
+    destination_index = move_elements[2].to_i
+    raise(InvalidMoveError, "Invalid origin #{origin_index} in move read") unless origin_index >= 0 and origin_index < game.board.positions.length
+    raise(InvalidMoveError, "Invalid destination #{destination_index} in move read") unless destination_index > 0 and destination_index <= game.board.positions.length
+    raise(InvalidMoveError, "Player #{player} does not have a piece on position #{origin_index} in move read") unless game.board.positions[origin_index].contains.any? {|p| p.player == player}
+    piece = game.board.positions[origin_index].contains.find {|p| p.player == player}
+    piece_name = piece.number
+    if move_elements.length == 4
+      raise(InvalidMoveError, "Invalid card played: #{move_elements[3]}") unless $SYMBOLS.include?(move_elements[3].chomp.to_sym)
+      Move.new(game.pieces[player][piece_name], 
+        game.board.positions[origin_index], 
+        game.board.positions[destination_index],
+        move_elements[3].chomp.to_sym)
+    else
+      Move.new(game.pieces[player][piece_name], 
+        game.board.positions[origin_index], 
+        game.board.positions[destination_index])
+    end
+  end
+end
+
+
+# Read a game description file and convert it into a Game object and set of Move objects.
+# Note that Game.read_game method returns multiple values, so this one does too.
+class IO
+  def IO.read_game(filename)
+    gamelines = IO.readlines(filename)
+    return Game.read_game(gamelines)
+  end
+end
+
+# Extension to the Array class to include the Array#shuffle function
+class Array
+  def shuffle
+    sort_by { rand }
+  end
+  
+  def index_find(&block)
+    found = find(&block)
+    if found
+      index found
+    else
+      found
+    end
+  end
+end
diff --git a/lib/.svn/tmp/tempfile.6.tmp b/lib/.svn/tmp/tempfile.6.tmp
new file mode 100644 (file)
index 0000000..a881e56
--- /dev/null
@@ -0,0 +1,704 @@
+# == Synopsis
+#
+# Library to support Cartagena play
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  11 Jun 2008  
+#                * Initial build
+
+# Symbols
+$SYMBOLS = [:bottle, :dagger, :gun, :hat, :keys, :skull]
+
+# The number of cards of each symbol in the deck
+$CARDS_PER_SYMBOL = 17
+
+# The number of cards initiall dealt to each player
+$INITIAL_CARDS_PER_PLAYER = 3
+
+# Maximum number of pieces on a position
+$MAX_PIECES_PER_POSITION = 3
+
+# Number of actions that can be taken by each player in sequence
+$MAX_MOVES_PER_TURN = 3
+
+# Errors for a game
+
+# Moves can only be [1..6] spaces
+class InvalidMoveError < StandardError
+end
+
+# Game is won when only one player has uncaptured pieces
+class GameWonNotice < StandardError
+end
+
+# A position on the board.
+class Position
+  attr_reader :symbol
+  attr_accessor :contains
+  
+  def initialize(symbol)
+    @symbol = symbol
+    @contains = []
+  end
+end
+
+# A tile that makes up the board.  Each tile has two sides, each with six
+# positions.  Tiles can be either way up, and either way round.
+class Tile
+  attr_reader :exposed, :front, :back
+  
+  def initialize(front, back)
+    @front = front.collect {|s| Position.new(s)}
+    @back = back.collect {|s| Position.new(s)}
+    @exposed = @front
+    @exposed_side = :front
+  end
+  
+  def flip!
+    if @exposed_side == :front
+      @exposed = @back
+      @exposed_side = :back
+    else
+      @exposed = @front
+      @exposed_side = :front
+    end
+  end
+  
+  def reverse!
+    @exposed = @exposed.reverse
+  end
+end
+
+
+# The game board
+class Board
+
+  attr_accessor :positions
+  attr_reader :tiles
+
+  # A laborious procedure to create all the positions and tie them all together
+  def initialize(tiles_used)
+    # A hash of all positions, indexed by position names
+    @tiles = [Tile.new([:hat, :keys, :gun, :bottle, :skull, :dagger], 
+        [:hat, :keys, :gun, :bottle, :skull, :dagger]),
+      Tile.new([:gun, :hat, :dagger, :skull, :bottle, :keys],
+        [:gun, :hat, :dagger, :skull, :bottle, :keys]),
+      Tile.new([:skull, :gun, :bottle, :keys, :dagger, :hat],
+        [:skull, :gun, :bottle, :keys, :dagger, :hat]),
+      Tile.new([:dagger, :bottle, :keys, :gun, :hat, :skull],
+        [:dagger, :bottle, :keys, :gun, :hat, :skull]),
+      Tile.new([:keys, :dagger, :skull, :hat, :gun, :bottle],
+        [:keys, :dagger, :skull, :hat, :gun, :bottle]),
+      Tile.new([:bottle, :skull, :hat, :dagger, :keys, :gun],
+        [:bottle, :skull, :hat, :dagger, :keys, :gun])                 
+    ].shuffle[0...tiles_used]
+    @tiles = @tiles.each do |t|
+      if rand < 0.5
+        t.reverse!
+      else
+        t
+      end
+    end
+
+    @positions = [Position.new(:cell)]
+    @tiles.each {|t| t.exposed.each {|p| @positions << p}}
+    @positions << Position.new(:boat)
+  end # def
+
+  def to_s
+    layout
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # For each position, show its name and what it touches
+  def layout
+    out_string = ""
+    @positions.each {|position| out_string << "#{position.symbol}\n"}
+    out_string
+  end
+
+end
+
+
+# Each piece on the board is an object
+class Piece
+  attr_reader :player, :number
+  attr_accessor :position
+
+  def initialize(position, player, number)
+    @position = position
+    @player = player
+    @number = number
+  end
+
+  def to_s
+    "#{@player}:#{@number}"
+  end
+  
+  def to_str
+    to_s
+  end
+
+  def show(convert_to_zero_based_player_number = false)
+    if convert_to_zero_based_player_number
+      "#{@player + 1}:#{@number}"   
+    else
+      "#{@player}:#{@number}"  
+    end
+  end
+  
+  def move_to(new_position)
+    @position = new_position
+  end
+
+end
+
+
+# A move in a game
+class Move
+  attr_reader :piece, :origin, :destination, :card_played
+  
+  def initialize(piece, origin, destination, card_played = :unspecified)
+    @piece = piece
+    @origin = origin
+    @destination = destination
+    @card_played = card_played
+  end
+
+  def show(board, convert_to_zero_based_player_number = false)
+    if @card_played == :unspecified
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)}"
+    else
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)} (#{@card_played})"
+    end
+  end
+  
+  def format(board, convert_to_zero_based_player_number = false)
+    display_player_number = if convert_to_zero_based_player_number then
+      @piece.player + 1
+    else
+      @piece.player
+    end
+    if @card_played ==:unspecified
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)}"
+    else
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)} #{@card_played}"
+    end
+  end
+  
+  # Write a move to a string
+  # Note the inverse, String#to_move, is defined below
+  def to_s
+    "#{@piece.player} #{@origin.to_s}  #{@destination.to_s} #{@card_played}"
+  end
+  
+  def to_str
+    to_s
+  end
+  
+end
+
+
+
+# A class to record each of the states previously found in a game.  
+# Note that this is a deep copy of the pieces and what they've captured, so 
+# you'll need to convert back
+class GameState
+  attr_accessor :move, :player, :board, :pieces_before_move, :deck_before_move, 
+    :players_cards_before_move, :moves_by_current_player
+  #    this_game_state = GameState.new(move, player, @board, @pieces, @deck, @players_cards)
+  
+  
+  def initialize(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    @move, @player, @board, @pieces_before_move, @deck_before_move, 
+      @players_cards_before_move, @moves_by_current_player = 
+      copy_game_state(move, player, board, pieces, deck, players_cards, 
+      moves_by_current_player)
+  end
+  
+  #  def ==(other)
+  #    @move.to_s == other.move.to_s and
+  #      @player == other.player and
+  #      @piece_after_move == other.pieces_after_move
+  #  end
+  
+  def copy_game_state(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    copy_player = player
+    moving_player = move.piece.player
+
+    copy_board = board.dup
+    copy_board.positions = board.positions.collect {|pos| pos.dup}
+    copy_board.positions.each {|pos| pos.contains = []}
+
+    copy_pieces = pieces.collect do |players_pieces|
+      players_pieces.collect do |piece|
+        new_piece_position = copy_board.positions[board.positions.index(piece.position)]
+        new_piece = Piece.new(new_piece_position, piece.player, piece.number)
+        new_piece_position.contains << new_piece
+        new_piece
+      end
+    end
+
+    piece_index = pieces[moving_player].index(move.piece)
+    origin_index = board.positions.index(move.origin)
+    destination_index = board.positions.index(move.destination)
+    copy_move = Move.new(copy_pieces[moving_player][piece_index], 
+      copy_board.positions[origin_index], 
+      copy_board.positions[destination_index])
+    
+    copy_deck = deck.dup
+    copy_players_cards = players_cards.collect {|p| p.dup}
+    copy_moves_by_current_player = moves_by_current_player
+
+    return copy_move, copy_player, copy_board, copy_pieces, copy_deck, copy_players_cards, copy_moves_by_current_player
+  end
+  
+end
+
+
+# A game of Cartagena.  It keeps a history of all previous states.  
+class Game
+
+  attr_reader :history
+  attr_accessor :current_player
+  attr_reader :players
+  attr_accessor :players_cards
+  attr_accessor :moves_by_current_player
+  attr_reader :board
+  attr_reader :pieces
+  attr_reader :cards
+  attr_accessor :deck
+
+  # Create a new game
+  def initialize(players = 5, number_of_tiles = 6, pieces_each = 6)
+    @board = Board.new(number_of_tiles)
+    @history = []
+    @pieces = []
+    @players = [].fill(0, players) {|i| i}
+    @players_cards = []
+    @current_player = 0
+    @moves_by_current_player = 0
+    @cards = []
+    1.upto($CARDS_PER_SYMBOL) {|x| @cards.concat($SYMBOLS)}
+    @deck = @cards.shuffle
+    @players.each do |p|
+      @pieces[p] = []
+      0.upto(pieces_each - 1) do |count|
+        piece = Piece.new(@board.positions[0], p, count)
+        @pieces[p][count] = piece
+        @board.positions[0].contains << piece
+      end
+      @players_cards[p] = []
+      deal_cards!($INITIAL_CARDS_PER_PLAYER, p)
+    end
+  end
+  
+  def deal_cards!(number_of_cards, player = @current_player)
+    1.upto(number_of_cards) do
+      if @deck.empty?
+        @deck = @cards
+        @players_cards.each do |p|
+          p.each do |c|
+            @deck.delete_at(@deck.index(c))
+          end
+        end
+        @deck = @deck.shuffle
+      end
+#      @deck = @cards.shuffle if @deck.empty?
+      @players_cards[player] << @deck.pop
+    end
+  end
+  
+  # Check that a move is valid.  Throw an exception if it's invalid
+  def validate_move(move, player = @current_player)
+    # Check the move is a valid one
+    raise(InvalidMoveError, "Move #{move}: Player #{player} does not exist") unless @players.include?(player)
+    raise(InvalidMoveError, "Move #{move}: None of player #{player}'s pieces on position #{move.origin}") unless move.origin.contains.find {|pc| pc.player == player}
+    raise(InvalidMoveError, "Move #{move}: Origin and destination are the same") if move.origin == move.destination
+  
+    origin_position      = @board.positions.index(move.origin)
+    destination_position = @board.positions.index(move.destination)
+    # Is this move an advance or a retreat?
+    if destination_position > origin_position
+      # Advancing a piece
+      if move.destination == @board.positions[-1] # A move into the boat
+        raise(InvalidMoveError, "Move #{move}: Move into boat and card unspecified") if move.destination == @board.positions[-1] and move.card_played == :unspecified
+        unless @players_cards[player].find {|c| c == move.card_played}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece into the boat")
+        end
+      else
+        if move.card_played != :unspecified and move.destination.symbol != move.card_played
+          raise(InvalidMoveError, "Player #{player} trying to move to #{move.destination}, a #{move.destination.symbol} square with a a #{move.card_played} card")
+        end
+        unless @players_cards[player].find {|c| c == move.destination.symbol}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece to a #{move.destination.symbol} square")
+        end
+        # Check target square is vacant
+        raise(InvalidMoveError, "Advance move #{move}: destination occupied") unless move.destination.contains.empty?
+      end
+      # Check all the intervening squares with this symbol are occupied
+      intervening_empty_position = @board.positions[origin_position...destination_position].index_find do |p| 
+        p.symbol == move.destination.symbol and 
+          p.contains.empty?
+      end
+      raise(InvalidMoveError, "Advance move #{move}: location #{intervening_empty_position} is empty") if intervening_empty_position
+    else
+      # Retreating a piece
+      # Check target position has one or two pieces already on it
+      destination_count = move.destination.contains.length
+      raise(InvalidMoveError, "Retreat move #{move}: destination has no pieces already on it") if destination_count == 0
+      raise(InvalidMoveError, "Retreat move #{move}: destination has too many (#{destination_count}) pieces already on it") if destination_count >= $MAX_PIECES_PER_POSITION
+      # Check none of the intervening squares have any pieces on them
+      # puts "Checking positions #{destination_position} to #{origin_position}"      
+      intervening_target_position = @board.positions[(destination_position + 1)...origin_position].index_find do |p| 
+        # puts "Examining postition #{p} at location #{@board.positions.index(p)} which contains #{p.contains.length} pieces"        
+        p.contains.length > 0 and 
+          p.contains.length < $MAX_PIECES_PER_POSITION
+      end
+      raise(InvalidMoveError, "Retreat move #{move.show(@board)}: location #{intervening_target_position} is a viable target") if intervening_target_position
+    end
+  end
+  
+  # Apply a single move to a game.  
+  def apply_move!(move, player = @current_player, validate = true)
+    
+    validate_move(move, player) if validate
+
+    raise(InvalidMoveError, "Too many consecutive moves by #{@current_player}: has taken #{@moves_by_current_player} and validate is #{validate}") if validate and player == @current_player and @moves_by_current_player >= $MAX_MOVES_PER_TURN
+       
+    # Record the old state
+    this_game_state = GameState.new(move, @current_player, @board, @pieces, @deck, @players_cards, @moves_by_current_player)
+    @history << this_game_state
+    
+    # Apply this move
+    move.origin.contains.delete move.piece
+    move.destination.contains << move.piece
+    move.piece.position = move.destination
+
+    if player == @current_player
+      @moves_by_current_player += 1
+    else
+      @current_player = player
+      @moves_by_current_player = 1
+    end
+   
+    # Update cards
+    if @board.positions.index(move.destination) > @board.positions.index(move.origin)
+      # Advance move
+      if validate
+        card_to_remove = if move.card_played != :unspecified
+          move.card_played
+        else
+          move.destination.symbol
+        end
+        if @players_cards[player].include?(card_to_remove)
+          @players_cards[player].delete_at(@players_cards[player].index(card_to_remove))
+        end
+      end
+    else
+      # Retreat move
+      deal_cards!(move.destination.contains.length - 1, player)
+    end
+    
+    # If this player has all their pieces in the boat, declare a win.
+    if @pieces[player].all? {|pc| pc.position == @board.positions[-1]}
+      raise(GameWonNotice, "Game won by #{player}")
+    end
+  end
+  
+  # Undo a move 
+  def undo_move!
+    state_to_restore = @history[-1]
+    move, @current_player, @board, @pieces, @deck, @players_cards, 
+      @moves_by_current_player = 
+      state_to_restore.copy_game_state(state_to_restore.move, 
+      state_to_restore.player, 
+      state_to_restore.board, 
+      state_to_restore.pieces_before_move, 
+      state_to_restore.deck_before_move, 
+      state_to_restore.players_cards_before_move, 
+      state_to_restore.moves_by_current_player)
+    @history.pop    
+  end
+    
+  # Apply a list of moves in order
+  def apply_moves!(moves)
+    moves.each do |move| 
+      @current_player = move.piece.player
+      apply_move!(move, @current_player)
+      next_player! if @moves_by_current_player >= $MOVES_PER_TURN
+    end
+  end
+
+
+  # Set the current player to be the next player
+  def next_player!
+    if @current_player == @players[-1]
+      @current_player = @players[0]
+    else
+      @current_player = @players[@players.index(@current_player) + 1]
+    end
+    @moves_by_current_player = 0
+    @current_player
+  end
+
+  
+  def reset_current_player(new_current_player)
+    @current_player = new_current_player
+    @moves_by_current_player = 0
+  end
+
+  
+  # Return an array of all possible moves from this state, given the active player
+  def possible_moves(player = @current_player)
+    moves = []
+    @pieces[player].each do |piece|
+      # Do a forward move for each card held
+      unless piece.position == @board.positions[-1]
+        @players_cards[player].each do |card|
+          destination = @board.positions[@board.positions.index(piece.position)..-1].find do |pos|
+            (pos.symbol == card and pos.contains == []) or
+              pos.symbol == :boat
+          end
+          # puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)} to #{@board.positions.index(destination)}, a #{destination.symbol}"        
+          moves << Move.new(piece, piece.position, destination, card)
+        end
+      end
+      # Do a reverse move for the piece
+      unless piece.position == board.positions[0]
+        destination = @board.positions[1...@board.positions.index(piece.position)].reverse.find do |pos|
+          pos.contains.length == 1 or pos.contains.length == 2
+        end
+        if destination
+          # puts "Player #{player}, piece #{piece.number} at position #{@board.positions.index(piece.position)} retreats to #{@board.positions.index(destination)}, a #{destination.symbol} containing #{destination.contains.length} pieces"
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+    end
+    # moves.each {|m| puts m.show(@board)}    
+    moves
+  end
+
+  def build_state_string
+    outstr = "Current player = #{@current_player}\n"
+    0.upto((@board.positions.length)-1) do |i|
+      outstr << "#{i}: #{@board.positions[i].symbol}: "
+      @board.positions[i].contains.each do |piece|
+        outstr << "P#{piece.player}:#{piece.number} "
+      end
+      outstr << "\n"
+    end
+    0.upto((@players.length)-1) do |i|
+      outstr << "Player #{i} holds " << (@players_cards[i].sort_by {|c| c.to_s}).join(', ') << "\n"
+    end
+    outstr << "Deck holds " << @deck.join(', ') << "\n"
+    outstr
+  end
+  
+  # Show the state of the board
+  def show_state
+    puts build_state_string
+    #    @pieces.keys.sort.each do |piece_name|
+    #      if @pieces[piece_name].captured
+    #        puts "Piece #{piece_name} captured, at #{@pieces[piece_name].position}"
+    #      else
+    #        puts "Piece #{piece_name} is at #{@pieces[piece_name].position}, holds #{(@pieces[piece_name].contains.collect{|c| c.name}).join(' ')}"
+    #      end
+    #    end
+  end
+
+  def to_s
+    show_state
+  end
+  
+  def to_str
+    to_s
+  end
+  
+
+  def set_testing_game!
+    srand 1234
+    @board = nil
+    initialize(5, 6, 6)
+  end
+  
+  # Given a set of lines from an input file, turn them into a Game object and 
+  # a set of Move objects.
+  # Note the multiple return values.
+  # Class method
+  def Game.read_game(gamelines)
+    gamelines.each {|l| l.chomp!}
+#    puts "Creating game with #{gamelines[0].to_i} players"    
+    game = Game.new(gamelines[0].to_i, 6, 6)
+    
+    # create the board
+    2.upto(38) do |i|
+      game.board.positions[i-1] = Position.new(gamelines[i].to_sym)
+#      puts "Making board position #{i-1} a #{gamelines[i].to_sym}"      
+    end
+    
+    # read all the moves so far
+    moves = []
+    i = 39
+    current_player_moves = 0
+    current_player = 0
+<<<<<<< .mine
+    player_card_counts = game.players_cards.map{|p| 3}
+#    player_card_counts.each {|i| i = 3} 
+#    unused_cards = game.cards.map{|c| c}
+=======
+    player_card_counts = game.players_cards.map {|p| $INITIAL_CARDS_PER_PLAYER}
+    unused_cards = game.cards.map {|c| c}
+>>>>>>> .r49
+    while gamelines[i].in_move_format?
+      move = gamelines[i].to_move(game, true)
+#      puts "Applying move #{move.show(game.board)} from line #{i} of the file.  Current player before move application is #{game.current_player}"      
+      if move.piece.player == current_player
+        current_player_moves += 1
+      else
+        current_player = move.piece.player
+        current_player_moves = 1
+      end
+      # if move is an advance move, decrement the cards held by the player and remove the card from unused cards
+      #   throw an error if an advance move is made when all the cards of that type have been used
+      #   if unused_cards becomes empty, restock it.  
+      if game.board.positions.index(move.destination) > game.board.positions.index(move.origin)
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move with no cards in hand.  Move is #{gamelines[i]} on line #{i}") if player_card_counts[move.piece.player] == 0
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move to a #{move.destination.symbol} place when all those cards have been used. Move is #{gamelines[i]} on line #{i}") unless unused_cards.include? move.destination.symbol
+        player_card_counts[current_player] -= 1
+        unused_cards.delete_at(unused_cards.index(move.destination.symbol))
+      # if move is a retreat move, increment the cards held by the player
+      else
+        player_card_counts[current_player] += move.destination.contains.length
+        if unused_cards.length == player_card_counts.inject {|sum, n| sum + n }
+          unused_cards = game.cards.map {|c| c}
+        end
+      end
+      game.apply_move!(move, move.piece.player, false)
+      moves << move
+#      puts "Applied move #{move.show(game.board)} from line #{i} of the file"      
+      i = i + 1
+    end
+    
+    # note the current player
+    game.current_player = gamelines[i].to_i - 1
+    if game.current_player == current_player
+      game.moves_by_current_player = current_player_moves
+    else
+      game.moves_by_current_player = 0
+    end
+# puts "Setting current player to #{game.current_player} on line #{i}"   
+    current_player = game.current_player
+    
+    # read the cards
+    game.players_cards = game.players_cards.map {|c| []}
+# player_card_counts.each_index {|index| puts "#{index} should have #{player_card_counts[index]} cards"}
+# game.players_cards.each_index {|index| puts "#{index} has #{game.players_cards[index]} cards"}
+# puts "Current player is #{game.current_player} whose cards are #{game.players_cards[game.current_player]}"
+    (i+1).upto(gamelines.length - 1) do |j|
+      game.players_cards[game.current_player] << gamelines[j].to_sym
+      unused_cards.delete_at(unused_cards.index(move.destination.symbol))
+#      puts "Reading line #{j} to give player #{game.current_player} a #{gamelines[j].to_sym} card"
+    end
+    raise(InvalidMoveError, "Player #{game.current_player} given #{game.players_cards[game.current_player].length} cards, but should have #{player_card_counts[game.current_player]} cards from play") if game.players_cards[game.current_player].length != player_card_counts[game.current_player]
+    
+    # Update the game deck
+    
+# puts "#{unused_cards.length} unused cards"    
+# player_card_counts.each {|c| puts "has #{c} cards"}
+    game.deck = unused_cards.shuffle
+    player_card_counts[game.current_player] = 0
+    player_card_counts.each_index do |player|
+      if player_card_counts[player] > 0
+        game.deal_cards!(player_card_counts[player], player)
+      end
+    end
+# puts "#{game.deck.length} cards remaining in deck"   
+# game.players_cards.each {|cards| puts "holds #{cards}"}
+    return game, moves
+  end
+
+end
+  
+
+# Extension to String class to convert a move-description string into a Move object.  
+# This is the inverse of the Move#to_s method
+class String
+  def in_move_format?
+    elements = split
+    return ((elements.length == 3 or elements.length == 4) and
+        elements[0].to_i > 0 and elements[0].to_i < 6 and
+        elements[1].to_i >= 0 and elements[1].to_i <= 37 and
+        elements[2].to_i >= 0 and elements[2].to_i <= 37)
+  end
+  
+  def to_move(game, convert_to_zero_based_player_number = false)
+    move_elements = self.downcase.split
+    player = move_elements[0].to_i
+    player = player - 1 if convert_to_zero_based_player_number
+    origin_index = move_elements[1].to_i
+    destination_index = move_elements[2].to_i
+    raise(InvalidMoveError, "Invalid origin #{origin_index} in move read") unless origin_index >= 0 and origin_index < game.board.positions.length
+    raise(InvalidMoveError, "Invalid destination #{destination_index} in move read") unless destination_index > 0 and destination_index <= game.board.positions.length
+    raise(InvalidMoveError, "Player #{player} does not have a piece on position #{origin_index} in move read") unless game.board.positions[origin_index].contains.any? {|p| p.player == player}
+    piece = game.board.positions[origin_index].contains.find {|p| p.player == player}
+    piece_name = piece.number
+    if move_elements.length == 4
+      raise(InvalidMoveError, "Invalid card played: #{move_elements[3]}") unless $SYMBOLS.include?(move_elements[3].chomp.to_sym)
+      Move.new(game.pieces[player][piece_name], 
+        game.board.positions[origin_index], 
+        game.board.positions[destination_index],
+        move_elements[3].chomp.to_sym)
+    else
+      Move.new(game.pieces[player][piece_name], 
+        game.board.positions[origin_index], 
+        game.board.positions[destination_index])
+    end
+  end
+end
+
+
+# Read a game description file and convert it into a Game object and set of Move objects.
+# Note that Game.read_game method returns multiple values, so this one does too.
+class IO
+  def IO.read_game(filename)
+    gamelines = IO.readlines(filename)
+    return Game.read_game(gamelines)
+  end
+end
+
+# Extension to the Array class to include the Array#shuffle function
+class Array
+  def shuffle
+    sort_by { rand }
+  end
+  
+  def index_find(&block)
+    found = find(&block)
+    if found
+      index found
+    else
+      found
+    end
+  end
+end
diff --git a/lib/.svn/tmp/tempfile.tmp b/lib/.svn/tmp/tempfile.tmp
new file mode 100644 (file)
index 0000000..572ef8e
--- /dev/null
@@ -0,0 +1,508 @@
+# == Synopsis
+#
+# Library to support Cartagena play
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  11 Jun 2008  
+#                * Initial build
+
+# Symbols
+$SYMBOLS = [:bottle, :dagger, :gun, :hat, :keys, :skull]
+
+# The number of cards of each symbol in the deck
+$CARDS_PER_SYMBOL = 17
+
+# The number of cards initiall dealt to each player
+$INITIAL_CARDS_PER_PLAYER = 3
+
+# Maximum number of pieces on a position
+$MAX_PIECES_PER_POSITION = 3
+
+# Errors for a game
+
+# Moves can only be [1..6] spaces
+class InvalidMoveError < StandardError
+end
+
+# Game is won when only one player has uncaptured pieces
+class GameWonNotice < StandardError
+end
+
+# A position on the board.
+class Position
+  attr_reader :symbol, :contains
+  
+  def initialize(symbol)
+    @symbol = symbol
+    @contains = []
+  end
+end
+
+# A tile that makes up the board.  Each tile has two sides, each with six
+# positions.  Tiles can be either way up, and either way round.
+class Tile
+  attr_reader :exposed, :front, :back
+  
+  def initialize(front, back)
+    @front = front.collect {|s| Position.new(s)}
+    @back = back.collect {|s| Position.new(s)}
+    @exposed = @front
+    @exposed_side = :front
+  end
+  
+  def flip!
+    if @exposed_side == :front
+      @exposed = @back
+      @exposed_side = :back
+    else
+      @exposed = @front
+      @exposed_side = :front
+    end
+  end
+  
+  def reverse!
+    @exposed = @exposed.reverse
+  end
+end
+
+
+# The game board
+class Board
+
+  attr_reader :positions
+  attr_reader :tiles
+
+  # A laborious procedure to create all the positions and tie them all together
+  def initialize(tiles_used)
+    # A hash of all positions, indexed by position names
+    @tiles = [Tile.new([:hat, :keys, :gun, :bottle, :skull, :dagger], 
+        [:hat, :keys, :gun, :bottle, :skull, :dagger]),
+      Tile.new([:gun, :hat, :dagger, :skull, :bottle, :keys],
+        [:gun, :hat, :dagger, :skull, :bottle, :keys]),
+      Tile.new([:skull, :gun, :bottle, :keys, :dagger, :hat],
+        [:skull, :gun, :bottle, :keys, :dagger, :hat]),
+      Tile.new([:dagger, :bottle, :keys, :gun, :hat, :skull],
+        [:dagger, :bottle, :keys, :gun, :hat, :skull]),
+      Tile.new([:keys, :dagger, :skull, :hat, :gun, :bottle],
+        [:keys, :dagger, :skull, :hat, :gun, :bottle]),
+      Tile.new([:bottle, :skull, :hat, :dagger, :keys, :gun],
+        [:bottle, :skull, :hat, :dagger, :keys, :gun])                 
+    ].shuffle[0...tiles_used]
+    @tiles = @tiles.each do |t|
+      if rand < 0.5
+        t.reverse!
+      else
+        t
+      end
+    end
+
+    @positions = [Position.new(:cell)]
+    @tiles.each {|t| t.exposed.each {|p| @positions << p}}
+    @positions << Position.new(:boat)
+  end # def
+
+  def to_s
+    layout
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # For each position, show its name and what it touches
+  def layout
+    out_string = ""
+    @positions.each {|position| out_string << "#{position.symbol}\n"}
+    out_string
+  end
+
+end
+
+
+# Each piece on the board is an object
+class Piece
+  attr_reader :player, :number
+  attr_accessor :position
+
+  def initialize(position, player, number)
+    @position = position
+    @player = player
+    @number = number
+  end
+
+  def to_s
+    "#{@player}:#{@number}"
+  end
+  
+  def to_str
+    to_s
+  end
+
+  def move_to(new_position)
+    @position = new_position
+  end
+
+end
+
+
+# A move in a game
+class Move
+  attr_reader :piece, :origin, :destination
+  
+  def initialize(piece, origin, destination)
+    @piece = piece
+    @origin = origin
+    @destination = destination
+  end
+
+  def show(board)
+    "#{@piece.to_s}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)}"
+  end
+  
+  # Write a move to a string
+  # Note the inverse, String#to_move, is defined below
+  def to_s
+    "#{@piece.player}: #{@origin.to_s} ->  #{@destination.to_s}"
+  end
+  
+  def to_str
+    to_s
+  end
+  
+end
+
+
+
+# A class to record each of the states previously found in a game.  
+# Note that this is a deep copy of the pieces and what they've captured, so 
+# you'll need to convert back
+class GameState
+  attr_accessor :move, :player, :pieces_after_move
+  
+  def initialize(move, player, pieces)
+    @move = move
+    @player = player
+    @pieces_after_move = Hash.new
+    pieces.each {|k, p| @pieces_after_move[k] = p.dup}
+  end
+  
+  def ==(other)
+    @move.to_s == other.move.to_s and
+      @player == other.player and
+      @piece_after_move == other.pieces_after_move
+  end
+  
+end
+
+
+# A game of Cartagena.  It keeps a history of all previous states.  
+class Game
+
+  attr_reader :history
+  attr_reader :current_player, :players, :players_cards
+  attr_reader :board
+  attr_reader :pieces
+  attr_reader :deck
+
+  # Create a new game
+  def initialize(players = 6, number_of_tiles = 6, pieces_each = 6)
+    @board = Board.new(number_of_tiles)
+    @history = []
+    @pieces = []
+    @players = [].fill(0, players) {|i| i}
+    @players_cards = []
+    @current_player = 0
+    @cards = []
+    1.upto($CARDS_PER_SYMBOL) {|x| @cards.concat($SYMBOLS)}
+    @deck = @cards.shuffle
+    @players.each do |p|
+      @pieces[p] = []
+      0.upto(pieces_each - 1) do |count|
+        piece = Piece.new(@board.positions[0], p, count)
+        @pieces[p][count] = piece
+        @board.positions[0].contains << piece
+      end
+      @players_cards[p] = []
+      deal_cards!($INITIAL_CARDS_PER_PLAYER, p)
+    end
+  end
+  
+  def deal_cards!(number_of_cards, player = @current_player)
+    1.upto(number_of_cards) do
+      @deck = @cards.shuffle if @deck.empty?
+      @players_cards[player] << @deck.pop
+    end
+  end
+  
+  # Check that a move is valid.  Throw an exception if it's invalid
+  def validate_move(move, player = @current_player)
+    # Check the move is a valid one
+    raise(InvalidMoveError, "Move #{move}: Player #{player} does not exist") unless @players.include?(player)
+    raise(InvalidMoveError, "Move #{move}: None of player #{player}'s pieces on position #{move.origin}") unless move.origin.contains.find {|pc| pc.player == player}
+    raise(InvalidMoveError, "Move #{move}: Origin and destination are the same") if move.origin == move.destination
+  
+    origin_position      = @board.positions.index(move.origin)
+    destination_position = @board.positions.index(move.destination)
+    # Is this move an advance or a retreat?
+    if destination_position > origin_position
+      # Advancing a piece
+      unless @players_cards[player].find {|c| c == move.destination.symbol}
+        raise(InvalidMoveError, "Player #{player} does not have a card to move a piece to a #{move.destination.symbol} square")
+      end
+      # Check target square is vacant
+      raise(InvalidMoveError, "Advance move #{move}: destination occupied") unless move.destination.contains.empty?
+      # Check all the intervening squares with this symbol are occupied
+      intervening_empty_position = @board.positions[origin_position...destination_position].index_find do |p| 
+          p.symbol == move.destination.symbol and 
+          p.contains.empty?
+      end
+      raise(InvalidMoveError, "Advance move #{move}: location #{intervening_empty_position} is empty") if intervening_empty_position
+    else
+      # Retreating a piece
+      # Check target position has one or two pieces already on it
+      destination_count = move.destination.contains.length
+      raise(InvalidMoveError, "Retreat move #{move}: destination has no pieces already on it") if destination_count == 0
+      raise(InvalidMoveError, "Retreat move #{move}: destination has too many (#{destination_count}) pieces already on it") if destination_count >= $MAX_PIECES_PER_POSITION
+      # Check none of the intervening squares have any pieces on them
+# puts "Checking positions #{destination_position} to #{origin_position}"      
+      intervening_target_position = @board.positions[(destination_position + 1)...origin_position].index_find do |p| 
+# puts "Examining postition #{p} at location #{@board.positions.index(p)} which contains #{p.contains.length} pieces"        
+          p.contains.length > 0 and 
+          p.contains.length < $MAX_PIECES_PER_POSITION
+      end
+      raise(InvalidMoveError, "Retreat move #{move.show(@board)}: location #{intervening_target_position} is a viable target") if intervening_target_position
+    end
+  end
+  
+  # Apply a single move to a game.  
+  def apply_move!(move, player = @current_player)
+    
+    validate_move(move, player)
+    
+    # Apply this move
+    move.origin.contains.delete move.piece
+    move.destination.contains << move.piece
+    move.piece.position = move.destination
+
+    # Update cards
+    if @board.positions.index(move.destination) > @board.positions.index(move.origin)
+      # Advance move
+      @players_cards[player].delete_at(@players_cards[player].index(move.destination.symbol))
+    else
+      # Retreat move
+      deal_cards!(move.destination.contains.length - 1, player)
+    end
+
+    # Record the new stae
+#    this_game_state = GameState.new(move, player, @pieces)
+#    @history << this_game_state
+
+    # If this player has all their pieces in the boat, declare a win.
+    if @pieces[player].all? {|pc| pc.position == :boat}
+      raise(GameWonNotice, "Game won by #{player}")
+    end
+  end
+  
+  # Undo a move 
+  def undo_move!
+    if @history.length > 1
+      # general case
+      state_to_restore = @history[-2]
+      @current_player = @history[-1].player
+      @pieces.each do |name, piece| 
+        copy_piece = state_to_restore.pieces_after_move[name]
+        piece.position = copy_piece.position
+      end
+      @history.pop    
+    elsif @history.length == 1
+      # reset to start
+      @current_player = @players[1]
+      @pieces.each do |name, piece| 
+        piece.position = @board.positions[piece.colour]
+      end
+      @history.pop    
+    end
+  end
+    
+  # Apply a list of moves in order
+  def apply_moves!(moves)
+    moves.each do |move| 
+      if move.via_base?
+        moved_distance = board.distance_between[move.piece.position.place][@current_player] +
+          board.distance_between[@current_player][move.destination.place]
+      else
+        moved_distance = board.distance_between[move.piece.position.place][move.destination.place]
+      end
+      self.apply_move!(move, @current_player)
+      next_player! unless moved_distance == 6
+    end
+  end
+
+
+  # Set the current player to be the next player
+  def next_player!
+    original_player = @current_player
+    if @current_player == @players[-1]
+      @current_player = @players[1]
+    else
+      @current_player = @players[@players.index(@current_player) + 1]
+    end
+    @current_player
+  end
+
+
+  # Return an array of all possible moves from this state, given the active player
+  def possible_moves(player = @current_player)
+    moves = []
+    @pieces[player].each do |piece|
+      # Do a forward move for each card held
+      unless piece.position == @board.positions[-1]
+        @players_cards[player].each do |card|
+          destination = @board.positions[@board.positions.index(piece.position)..-1].find do |pos|
+            (pos.symbol == card and pos.contains == []) or
+              pos.symbol == :boat
+          end
+<<<<<<< .mine
+puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)} moving to #{@board.positions.index(destination)}, a #{destination.symbol}"        
+=======
+# puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)} to #{@board.positions.index(destination)}, a #{destination.symbol}"        
+>>>>>>> .r34
+          moves << Move.new(piece, piece.position, destination)
+puts "Added move #{piece.number}: #{piece.position.symbol} -> #{destination.symbol}"
+        end
+      end
+      # Do a reverse move for the piece
+      unless piece.position == board.positions[0]
+        destination = @board.positions[0...@board.positions.index(piece.position)].reverse.find do |pos|
+          pos.contains.length == 1 or pos.contains.length == 2
+        end
+        if destination
+# puts "Player #{player}, piece #{piece.number} at position #{@board.positions.index(piece.position)} retreats to #{@board.positions.index(destination)}, a #{destination.symbol} containing #{destination.contains.length} pieces"
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+    end
+# moves.each {|m| puts m.show(@board)}    
+    moves
+  end
+
+  def build_state_string
+    outstr = "Current player = #{@current_player}\n"
+    0.upto((@board.positions.length)-1) do |i|
+      outstr << "#{i}: #{@board.positions[i].symbol}: "
+      @board.positions[i].contains.each do |piece|
+        outstr << "P#{piece.player}:#{piece.number} "
+      end
+      outstr << "\n"
+    end
+    0.upto((@players.length)-1) do |i|
+      outstr << "Player #{i} holds " << (@players_cards[i].sort_by {|c| c.to_s}).join(', ') << "\n"
+    end
+    outstr << "Deck holds " << @deck.join(', ') << "\n"
+    outstr
+  end
+  
+  # Show the state of the board
+  def show_state
+    puts build_state_string
+    #    @pieces.keys.sort.each do |piece_name|
+    #      if @pieces[piece_name].captured
+    #        puts "Piece #{piece_name} captured, at #{@pieces[piece_name].position}"
+    #      else
+    #        puts "Piece #{piece_name} is at #{@pieces[piece_name].position}, holds #{(@pieces[piece_name].contains.collect{|c| c.name}).join(' ')}"
+    #      end
+    #    end
+  end
+
+  def to_s
+    show_state
+  end
+  
+  def to_str
+    to_s
+  end
+  
+
+  def set_testing_game!
+    srand 1234
+    @board = nil
+    initialize(6, 6, 6)
+  end
+  
+  # Given a set of lines from an input file, turn them into a Game object and 
+  # a set of Move objects.
+  # Note the multiple return values.
+  # Class method
+  def Game.read_game(gamelines)
+    gamelines.each {|l| l.chomp!}
+    game = Game.new(gamelines[0].to_i, 6, 6, 6, 6)
+    moves = []
+    gamelines[1..-2].each {|m| moves << m.to_move(game)}
+    return game, moves, gamelines[-1].to_i
+  end
+
+
+end
+  
+
+# Extension to String class to convert a move-description string into a Move object.  
+# This is the inverse of the Move#to_s method
+class String
+  def to_move(game)
+    move_elements = self.downcase.split
+    piece_name = move_elements[0]
+    destination_name = move_elements[-1]
+    if destination_name.length > 2 and 
+        destination_name[-2,2] == game.board.centre.place[-2,2]
+      destination_name = game.board.centre.place
+    end
+    raise(InvalidMoveError, "Invalid piece in move read") unless game.pieces.has_key?(piece_name)
+    raise(InvalidMoveError, "Invalid destination in move read") unless game.board.positions.has_key?(destination_name)
+    # Deal with the synonyms for the centre position
+    via_base = (destination_name.length == 1 or move_elements.length > 2)
+    Move.new(game.pieces[piece_name], game.board.positions[destination_name], via_base)
+  end
+end
+
+
+# Read a game description file and convert it into a Game object and set of Move objects.
+# Note that Game.read_game method returns multiple values, so this one does too.
+class IO
+  def IO.read_game(filename)
+    gamelines = IO.readlines(filename)
+    return Game.read_game(gamelines)
+  end
+end
+
+# Extension to the Array class to include the Array#shuffle function
+class Array
+  def shuffle
+    sort_by { rand }
+  end
+  
+  def index_find(&block)
+    found = find(&block)
+    if found
+      index found
+    else
+      found
+    end
+  end
+end
diff --git a/lib/cgi/.svn/all-wcprops b/lib/cgi/.svn/all-wcprops
new file mode 100644 (file)
index 0000000..217831e
--- /dev/null
@@ -0,0 +1,11 @@
+K 25
+svn:wc:ra_dav:version-url
+V 45
+/svn/njae/!svn/ver/43/cartagena/trunk/lib/cgi
+END
+cartagena-simple.rb
+K 25
+svn:wc:ra_dav:version-url
+V 65
+/svn/njae/!svn/ver/66/cartagena/trunk/lib/cgi/cartagena-simple.rb
+END
diff --git a/lib/cgi/.svn/entries b/lib/cgi/.svn/entries
new file mode 100644 (file)
index 0000000..4cf7e00
--- /dev/null
@@ -0,0 +1,39 @@
+8
+
+dir
+65
+http://scripts.njae.me.uk/svn/njae/cartagena/trunk/lib/cgi
+http://scripts.njae.me.uk/svn/njae
+
+
+
+2008-10-17T07:57:36.358504Z
+43
+
+
+
+svn:special svn:externals svn:needs-lock
+
+
+
+
+
+
+
+
+
+
+
+0ada9808-85ab-4924-adda-5bf89eae3818
+\f
+cartagena-simple.rb
+file
+66
+
+
+
+2009-01-30T14:54:15.000000Z
+cabe60e563744fa37032ef3a9cad7b63
+2009-01-30T15:02:26.543445Z
+66
+\f
diff --git a/lib/cgi/.svn/format b/lib/cgi/.svn/format
new file mode 100644 (file)
index 0000000..45a4fb7
--- /dev/null
@@ -0,0 +1 @@
+8
diff --git a/lib/cgi/.svn/text-base/cartagena-simple.rb.svn-base b/lib/cgi/.svn/text-base/cartagena-simple.rb.svn-base
new file mode 100644 (file)
index 0000000..5b58ca5
--- /dev/null
@@ -0,0 +1,150 @@
+#!/usr/bin/ruby -w
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# CGI script
+#
+# == Author
+# Neil Smith
+
+require 'libcartagena'
+require 'cgi'
+require 'open3'
+require 'timeout'
+
+# Prevent dangerous operations
+$SAFE = 1
+
+class InvalidRobotError < StandardError
+end
+
+class NoInputFileError < StandardError
+end
+
+class InvalidMoveInHistoryError < StandardError
+end
+
+class GameWonInHistoryError < StandardError
+end
+
+# A table of the known game-playing robots
+
+$ROBOT_NAME_TABLE = {
+  'First Possible' => '/var/www/scripts.njae.me.uk/cartagena/robots/1',
+  'Random'         => '/var/www/scripts.njae.me.uk/cartagena/robots/2'
+}
+
+$ROBOT_CODE_TABLE = {
+  '1'  => '/var/www/scripts.njae.me.uk/cartagena/robots/1',
+  '2'  => '/var/www/scripts.njae.me.uk/cartagena/robots/2'
+}
+
+
+# How long the robot has to respond
+$ROBOT_TIMEOUT = 15
+
+
+def generate_output(cgi, returned_move, annotation)
+  if returned_move.empty?
+    return_part = ""
+  else
+    return_part = cgi.p { "Result is: " } + "\n" + cgi.h1 { returned_move } + "\n"
+  end
+
+  if annotation.empty?
+    annotation_part = ""
+  else
+    annotation_part = cgi.p { annotation }
+  end
+
+  cgi.html {
+    cgi.head { "\n" + cgi.title{"Move result"} } + "\n" +
+    cgi.body { return_part + annotation_part }
+    }
+end
+
+
+begin
+
+  cgi = CGI.new("html4")
+
+  chosen_robot = cgi['robot']
+  if $ROBOT_NAME_TABLE.has_key? chosen_robot
+    chosen_robot_dir = $ROBOT_NAME_TABLE[chosen_robot]
+    chosen_robot_dir.untaint
+  else
+    raise InvalidRobotError, "#{chosen_robot} is not a valid robot code"
+  end
+
+  begin
+    game, moves = Game.read_game(cgi['history'].downcase.split("\n"))
+  rescue InvalidMoveError => error
+    raise(InvalidMoveInHistoryError, error.message)
+  rescue GameWonNotice => error
+    raise(GameWonInHistoryError, error.message)
+  end
+
+  current_directory = Dir.pwd
+  current_directory.untaint
+  Dir.chdir chosen_robot_dir
+
+  returned_move = ""
+  returned_error = ""
+  robot_input  = game.players.length.to_s + "\n" 
+  robot_input += game.board.positions.collect {|p| p.symbol.to_s}.join("\n") + "\n"
+  robot_input += (moves.collect {|m| m.format(game.board, true)}).join("\n") + "\n" if not moves.empty?
+  robot_input += (game.current_player + 1).to_s + "\n"
+  robot_input += game.players_cards[game.current_player].collect {|c| c.to_s}.join("\n") + "\n" if not game.players_cards[game.current_player].empty?
+
+# cgi.out { generate_output(cgi, "", "passing robot #{game.players.length.to_s + "#" + (moves.collect {|m| m.to_s}).join('!\n!') + "#" + next_roll.to_s}!") }
+# cgi.out { generate_output(cgi, "", "passing robot #{robot_input}!") }
+
+
+  Timeout.timeout($ROBOT_TIMEOUT + 1) do
+    Open3.popen3('./runme') do |robot_in, robot_out, robot_err|
+      robot_in << robot_input
+      robot_in.close_write
+      returned_move  = robot_out.gets
+      returned_error = robot_err.gets
+    end
+  end
+
+  Dir.chdir current_directory
+
+#  cgi.out { generate_output(cgi, "", "Returned move #{returned_move}; robot error was #{returned_error}!") }
+
+  raise(InvalidMoveError, "Robot returned error '#{returned_error}'") if returned_error != nil
+  raise(InvalidMoveError, "Null move") if returned_move.nil? or returned_move.empty?
+  next_move = returned_move.chomp.to_move(game, true)
+  game.apply_move! next_move
+
+#    cgi.out { generate_output(cgi, "", "Applied move #{returned_move}!") }
+  cgi.out { generate_output(cgi, returned_move, "")  }
+
+rescue InvalidMoveInHistoryError => error
+  cgi.out { generate_output(cgi, "", "Invalid move in history: #{error}") }
+#  puts "Invalid move in history: #{error}"
+rescue GameWonInHistoryError => error
+  cgi.out { generate_output(cgi, "", "Game already won in historic moves: #{error}") }
+#  puts "Game already won in historic moves: #{error}"
+
+rescue InvalidMoveError => error
+  cgi.out { generate_output(cgi, "", "Robot returned invalid move: #{error}") }
+#  puts "Robot returned invalid move: #{error}"
+rescue GameWonNotice => error
+  cgi.out { generate_output(cgi, returned_move, "Game won: #{error}") }
+#  puts "Robot returned #{returned_move}"
+#  puts "Game won: #{error}"
+
+rescue InvalidRobotError => error
+  cgi.out { generate_output(cgi, "", "Invalid robot selection: #{error}") }
+#  puts "Invalid robot selection: #{error}"
+rescue Timeout::Error => error
+  cgi.out { generate_output(cgi, "", "Robot timed out") }
+#  puts "Robot timeout: #{error}"
+
+end
diff --git a/lib/cgi/cartagena-simple.rb b/lib/cgi/cartagena-simple.rb
new file mode 100755 (executable)
index 0000000..5b58ca5
--- /dev/null
@@ -0,0 +1,150 @@
+#!/usr/bin/ruby -w
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# CGI script
+#
+# == Author
+# Neil Smith
+
+require 'libcartagena'
+require 'cgi'
+require 'open3'
+require 'timeout'
+
+# Prevent dangerous operations
+$SAFE = 1
+
+class InvalidRobotError < StandardError
+end
+
+class NoInputFileError < StandardError
+end
+
+class InvalidMoveInHistoryError < StandardError
+end
+
+class GameWonInHistoryError < StandardError
+end
+
+# A table of the known game-playing robots
+
+$ROBOT_NAME_TABLE = {
+  'First Possible' => '/var/www/scripts.njae.me.uk/cartagena/robots/1',
+  'Random'         => '/var/www/scripts.njae.me.uk/cartagena/robots/2'
+}
+
+$ROBOT_CODE_TABLE = {
+  '1'  => '/var/www/scripts.njae.me.uk/cartagena/robots/1',
+  '2'  => '/var/www/scripts.njae.me.uk/cartagena/robots/2'
+}
+
+
+# How long the robot has to respond
+$ROBOT_TIMEOUT = 15
+
+
+def generate_output(cgi, returned_move, annotation)
+  if returned_move.empty?
+    return_part = ""
+  else
+    return_part = cgi.p { "Result is: " } + "\n" + cgi.h1 { returned_move } + "\n"
+  end
+
+  if annotation.empty?
+    annotation_part = ""
+  else
+    annotation_part = cgi.p { annotation }
+  end
+
+  cgi.html {
+    cgi.head { "\n" + cgi.title{"Move result"} } + "\n" +
+    cgi.body { return_part + annotation_part }
+    }
+end
+
+
+begin
+
+  cgi = CGI.new("html4")
+
+  chosen_robot = cgi['robot']
+  if $ROBOT_NAME_TABLE.has_key? chosen_robot
+    chosen_robot_dir = $ROBOT_NAME_TABLE[chosen_robot]
+    chosen_robot_dir.untaint
+  else
+    raise InvalidRobotError, "#{chosen_robot} is not a valid robot code"
+  end
+
+  begin
+    game, moves = Game.read_game(cgi['history'].downcase.split("\n"))
+  rescue InvalidMoveError => error
+    raise(InvalidMoveInHistoryError, error.message)
+  rescue GameWonNotice => error
+    raise(GameWonInHistoryError, error.message)
+  end
+
+  current_directory = Dir.pwd
+  current_directory.untaint
+  Dir.chdir chosen_robot_dir
+
+  returned_move = ""
+  returned_error = ""
+  robot_input  = game.players.length.to_s + "\n" 
+  robot_input += game.board.positions.collect {|p| p.symbol.to_s}.join("\n") + "\n"
+  robot_input += (moves.collect {|m| m.format(game.board, true)}).join("\n") + "\n" if not moves.empty?
+  robot_input += (game.current_player + 1).to_s + "\n"
+  robot_input += game.players_cards[game.current_player].collect {|c| c.to_s}.join("\n") + "\n" if not game.players_cards[game.current_player].empty?
+
+# cgi.out { generate_output(cgi, "", "passing robot #{game.players.length.to_s + "#" + (moves.collect {|m| m.to_s}).join('!\n!') + "#" + next_roll.to_s}!") }
+# cgi.out { generate_output(cgi, "", "passing robot #{robot_input}!") }
+
+
+  Timeout.timeout($ROBOT_TIMEOUT + 1) do
+    Open3.popen3('./runme') do |robot_in, robot_out, robot_err|
+      robot_in << robot_input
+      robot_in.close_write
+      returned_move  = robot_out.gets
+      returned_error = robot_err.gets
+    end
+  end
+
+  Dir.chdir current_directory
+
+#  cgi.out { generate_output(cgi, "", "Returned move #{returned_move}; robot error was #{returned_error}!") }
+
+  raise(InvalidMoveError, "Robot returned error '#{returned_error}'") if returned_error != nil
+  raise(InvalidMoveError, "Null move") if returned_move.nil? or returned_move.empty?
+  next_move = returned_move.chomp.to_move(game, true)
+  game.apply_move! next_move
+
+#    cgi.out { generate_output(cgi, "", "Applied move #{returned_move}!") }
+  cgi.out { generate_output(cgi, returned_move, "")  }
+
+rescue InvalidMoveInHistoryError => error
+  cgi.out { generate_output(cgi, "", "Invalid move in history: #{error}") }
+#  puts "Invalid move in history: #{error}"
+rescue GameWonInHistoryError => error
+  cgi.out { generate_output(cgi, "", "Game already won in historic moves: #{error}") }
+#  puts "Game already won in historic moves: #{error}"
+
+rescue InvalidMoveError => error
+  cgi.out { generate_output(cgi, "", "Robot returned invalid move: #{error}") }
+#  puts "Robot returned invalid move: #{error}"
+rescue GameWonNotice => error
+  cgi.out { generate_output(cgi, returned_move, "Game won: #{error}") }
+#  puts "Robot returned #{returned_move}"
+#  puts "Game won: #{error}"
+
+rescue InvalidRobotError => error
+  cgi.out { generate_output(cgi, "", "Invalid robot selection: #{error}") }
+#  puts "Invalid robot selection: #{error}"
+rescue Timeout::Error => error
+  cgi.out { generate_output(cgi, "", "Robot timed out") }
+#  puts "Robot timeout: #{error}"
+
+end
diff --git a/lib/first-move.rb b/lib/first-move.rb
new file mode 100755 (executable)
index 0000000..4c96457
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/ruby
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# clockwise-p1 
+#  Game state file read on STDIN
+#  Move produced on STDOUT
+#
+# == Author
+# Neil Smith
+
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'libcartagena'
+require 'rdoc/usage'
+
+gamelines = readlines
+game, moves = Game.read_game(gamelines)
+move = game.possible_moves[0]
+puts move.format(game.board, true)
diff --git a/lib/game_handler.rb b/lib/game_handler.rb
new file mode 100644 (file)
index 0000000..cc8eda7
--- /dev/null
@@ -0,0 +1,59 @@
+# == Synopsis
+#
+# Play a game of Cartagena.
+# 
+# == Author
+# Neil Smith
+# 
+# == Change history
+# Version 1.1::  23 April 2008 
+
+require 'lib/libcartagena'
+
+# Play a game to completion, given a set of player agents.
+class GameHandler
+    
+  attr_reader :game
+    
+  # Create a game handler that uses a set of players
+  def initialize(players, game_length_limit = 5000, verbose = false, very_verbose = false)
+    @game = Game.new(players.length)
+    @verbose = verbose
+    @very_verbose = very_verbose
+    @game_length_limit = game_length_limit
+    
+    # Give each player a name
+    @named_players = Hash.new 
+    (@game.players.zip players).each do |kv|
+      @named_players[kv[0]] = kv[1]
+    end
+  end
+  
+  # Play a game of Cartagena.  If players make illegal moves, disqualify them and restart the game.  
+  # Terminate the game if there's a winner, there's only one player left, 
+  # or the game has gone on too long.
+  def play
+    while @game.history.length < @game_length_limit
+      move_to_apply = @named_players[@game.current_player].best_move(@game, roll)
+      puts "Move #{@game.history.length + 1}: Player #{@game.current_player} rolled #{roll}: making move #{move_to_apply}" if @verbose
+      @game.apply_moves! [move_to_apply]
+      puts @game if @very_verbose
+    end
+    puts "Game terminated after #{@game.history.length} moves" if @verbose
+    [:draw, @limit]
+  rescue GameWonNotice => win_notification
+    winner = win_notification.message[-1,1]
+    puts "Game won by #{winner} in #{@game.history.length} moves" if @verbose
+    [@named_players[winner], @game.history.length]
+  rescue InvalidMoveError
+    puts "Disqualifying player #{@game.current_player}" if @verbose
+    @named_players.delete @game.current_player
+    if @named_players.length > 1
+      retry
+    else
+      puts "Game won by #{@named_players.keys[0]} by default" if @verbose
+      [@named_players[@named_players.keys[0]], 0]
+    end
+  end 
+end 
+  
\ No newline at end of file
diff --git a/lib/html/.svn/all-wcprops b/lib/html/.svn/all-wcprops
new file mode 100644 (file)
index 0000000..7aeda2e
--- /dev/null
@@ -0,0 +1,167 @@
+K 25
+svn:wc:ra_dav:version-url
+V 46
+/svn/njae/!svn/ver/58/cartagena/trunk/lib/html
+END
+green_pirate.png
+K 25
+svn:wc:ra_dav:version-url
+V 63
+/svn/njae/!svn/ver/55/cartagena/trunk/lib/html/green_pirate.png
+END
+boat.gif
+K 25
+svn:wc:ra_dav:version-url
+V 55
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/boat.gif
+END
+retreat.png
+K 25
+svn:wc:ra_dav:version-url
+V 58
+/svn/njae/!svn/ver/57/cartagena/trunk/lib/html/retreat.png
+END
+keys.jpg
+K 25
+svn:wc:ra_dav:version-url
+V 55
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/keys.jpg
+END
+hat.jpg
+K 25
+svn:wc:ra_dav:version-url
+V 54
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/hat.jpg
+END
+simple.html
+K 25
+svn:wc:ra_dav:version-url
+V 58
+/svn/njae/!svn/ver/42/cartagena/trunk/lib/html/simple.html
+END
+rules.html
+K 25
+svn:wc:ra_dav:version-url
+V 57
+/svn/njae/!svn/ver/46/cartagena/trunk/lib/html/rules.html
+END
+keys.png
+K 25
+svn:wc:ra_dav:version-url
+V 55
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/keys.png
+END
+hat.png
+K 25
+svn:wc:ra_dav:version-url
+V 54
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/hat.png
+END
+red_pirate.png
+K 25
+svn:wc:ra_dav:version-url
+V 61
+/svn/njae/!svn/ver/55/cartagena/trunk/lib/html/red_pirate.png
+END
+cell.png
+K 25
+svn:wc:ra_dav:version-url
+V 55
+/svn/njae/!svn/ver/57/cartagena/trunk/lib/html/cell.png
+END
+yellow_pirate.png
+K 25
+svn:wc:ra_dav:version-url
+V 64
+/svn/njae/!svn/ver/55/cartagena/trunk/lib/html/yellow_pirate.png
+END
+interface.html
+K 25
+svn:wc:ra_dav:version-url
+V 61
+/svn/njae/!svn/ver/46/cartagena/trunk/lib/html/interface.html
+END
+gun.jpg
+K 25
+svn:wc:ra_dav:version-url
+V 54
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/gun.jpg
+END
+dagger.jpg
+K 25
+svn:wc:ra_dav:version-url
+V 57
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/dagger.jpg
+END
+bottle.jpg
+K 25
+svn:wc:ra_dav:version-url
+V 57
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/bottle.jpg
+END
+boat.png
+K 25
+svn:wc:ra_dav:version-url
+V 55
+/svn/njae/!svn/ver/57/cartagena/trunk/lib/html/boat.png
+END
+skull.jpg
+K 25
+svn:wc:ra_dav:version-url
+V 56
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/skull.jpg
+END
+index.html
+K 25
+svn:wc:ra_dav:version-url
+V 57
+/svn/njae/!svn/ver/46/cartagena/trunk/lib/html/index.html
+END
+complex.html
+K 25
+svn:wc:ra_dav:version-url
+V 59
+/svn/njae/!svn/ver/58/cartagena/trunk/lib/html/complex.html
+END
+gun.png
+K 25
+svn:wc:ra_dav:version-url
+V 54
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/gun.png
+END
+dagger.png
+K 25
+svn:wc:ra_dav:version-url
+V 57
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/dagger.png
+END
+bottle.png
+K 25
+svn:wc:ra_dav:version-url
+V 57
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/bottle.png
+END
+blue-pirate.gif
+K 25
+svn:wc:ra_dav:version-url
+V 62
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/blue-pirate.gif
+END
+skull.png
+K 25
+svn:wc:ra_dav:version-url
+V 56
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/skull.png
+END
+blue_pirate.png
+K 25
+svn:wc:ra_dav:version-url
+V 62
+/svn/njae/!svn/ver/45/cartagena/trunk/lib/html/blue_pirate.png
+END
+brown_pirate.png
+K 25
+svn:wc:ra_dav:version-url
+V 63
+/svn/njae/!svn/ver/55/cartagena/trunk/lib/html/brown_pirate.png
+END
diff --git a/lib/html/.svn/entries b/lib/html/.svn/entries
new file mode 100644 (file)
index 0000000..9f5bcaf
--- /dev/null
@@ -0,0 +1,372 @@
+8
+
+dir
+65
+http://scripts.njae.me.uk/svn/njae/cartagena/trunk/lib/html
+http://scripts.njae.me.uk/svn/njae
+
+
+
+2008-12-08T21:03:51.254104Z
+58
+
+
+
+svn:special svn:externals svn:needs-lock
+
+
+
+
+
+
+
+
+
+
+
+0ada9808-85ab-4924-adda-5bf89eae3818
+\f
+green_pirate.png
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+6529f173ff4a2357ce8a2b35f59f4b4a
+2008-12-02T15:51:28.975261Z
+55
+
+has-props
+\f
+retreat.png
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+6708b25489b8f455d1919997cbf060bb
+2008-12-07T21:19:32.325161Z
+57
+
+has-props
+\f
+boat.gif
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+23eee87206d4c79d418003d928330790
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+keys.jpg
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+42c46cd908897ee683f2fd28188da438
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+hat.jpg
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+71474422d343530c758ca271ea06853e
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+simple.html
+file
+
+
+
+
+2008-10-16T14:47:35.000000Z
+5be43e2fd5c280555ba3e3d3f002657b
+2008-10-16T20:38:07.769887Z
+42
+\f
+rules.html
+file
+
+
+
+
+2008-04-22T14:04:55.000000Z
+93131d55d7ea7311cbe9f0b333b3c4ef
+2008-10-24T21:30:40.873147Z
+46
+\f
+keys.png
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+914e3af1491cc9f1abdf05ee1eabe958
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+hat.png
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+10243670e82d10cd014a94289692c4e7
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+red_pirate.png
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+9815ffc52aeefeecbf2a74a3b7948459
+2008-12-02T15:51:28.975261Z
+55
+
+has-props
+\f
+yellow_pirate.png
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+841e2f51e26f556f83adcb0ed17a3b4b
+2008-12-02T15:51:28.975261Z
+55
+
+has-props
+\f
+cell.png
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+eed7e5ed5823fb2ff0c49559583590a1
+2008-12-07T21:19:32.325161Z
+57
+
+has-props
+\f
+interface.html
+file
+
+
+
+
+2008-10-16T14:18:37.000000Z
+5838426b483f70f5b3a1348e3519256a
+2008-10-24T21:30:40.873147Z
+46
+\f
+bottle.jpg
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+716d077b7f69bd08d0c8c82d62db0e23
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+boat.png
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+7d6697bceadf3cb9fd0efb9dbe90327b
+2008-12-07T21:19:32.325161Z
+57
+
+has-props
+\f
+dagger.jpg
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+6cb01e7fcf6d919da31a4e76582ed85b
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+gun.jpg
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+dd8325ea3b0d6238778aa361036b78e8
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+robots
+dir
+\f
+skull.jpg
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+876ae69155595d5382aa6d0f57c5bfa1
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+complex.html
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+2f3b2bae81894f64a9f9c6d68521395c
+2008-12-08T21:03:51.254104Z
+58
+\f
+index.html
+file
+
+
+
+
+2008-10-24T21:28:42.000000Z
+3aad4d96ddd9c1becc11cd26c55ad574
+2008-10-24T21:30:40.873147Z
+46
+\f
+gun.png
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+ad9520ba128ac8411bfd34eb2d4ec1f4
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+dagger.png
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+25166cd34066416d5570ec025e763d1b
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+bottle.png
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+697d020086074d9db3113eb2e63e8342
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+blue-pirate.gif
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+b31c4824b42a831b962e0f0f7fad3c91
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+skull.png
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+20808c2e7e5a90962db0461c5f57d4b9
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+blue_pirate.png
+file
+
+
+
+
+2008-10-22T08:21:37.000000Z
+6e8360a580cf0fe525eed6e30d19b557
+2008-10-21T15:24:11.718099Z
+45
+
+has-props
+\f
+brown_pirate.png
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+09e6579d62cc02c8688090583343a2b6
+2008-12-02T15:51:28.975261Z
+55
+
+has-props
+\f
diff --git a/lib/html/.svn/format b/lib/html/.svn/format
new file mode 100644 (file)
index 0000000..45a4fb7
--- /dev/null
@@ -0,0 +1 @@
+8
diff --git a/lib/html/.svn/prop-base/blue-pirate.gif.svn-base b/lib/html/.svn/prop-base/blue-pirate.gif.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/blue_pirate.png.svn-base b/lib/html/.svn/prop-base/blue_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/boat.gif.svn-base b/lib/html/.svn/prop-base/boat.gif.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/boat.png.svn-base b/lib/html/.svn/prop-base/boat.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/bottle.jpg.svn-base b/lib/html/.svn/prop-base/bottle.jpg.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/bottle.png.svn-base b/lib/html/.svn/prop-base/bottle.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/brown_pirate.png.svn-base b/lib/html/.svn/prop-base/brown_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/cell.png.svn-base b/lib/html/.svn/prop-base/cell.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/dagger.jpg.svn-base b/lib/html/.svn/prop-base/dagger.jpg.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/dagger.png.svn-base b/lib/html/.svn/prop-base/dagger.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/green_pirate.png.svn-base b/lib/html/.svn/prop-base/green_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/gun.jpg.svn-base b/lib/html/.svn/prop-base/gun.jpg.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/gun.png.svn-base b/lib/html/.svn/prop-base/gun.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/hat.jpg.svn-base b/lib/html/.svn/prop-base/hat.jpg.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/hat.png.svn-base b/lib/html/.svn/prop-base/hat.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/keys.jpg.svn-base b/lib/html/.svn/prop-base/keys.jpg.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/keys.png.svn-base b/lib/html/.svn/prop-base/keys.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/red_pirate.png.svn-base b/lib/html/.svn/prop-base/red_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/retreat.png.svn-base b/lib/html/.svn/prop-base/retreat.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/skull.jpg.svn-base b/lib/html/.svn/prop-base/skull.jpg.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/skull.png.svn-base b/lib/html/.svn/prop-base/skull.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/prop-base/yellow_pirate.png.svn-base b/lib/html/.svn/prop-base/yellow_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..5e9587e
--- /dev/null
@@ -0,0 +1,5 @@
+K 13
+svn:mime-type
+V 24
+application/octet-stream
+END
diff --git a/lib/html/.svn/text-base/blue-pirate.gif.svn-base b/lib/html/.svn/text-base/blue-pirate.gif.svn-base
new file mode 100644 (file)
index 0000000..8630fab
Binary files /dev/null and b/lib/html/.svn/text-base/blue-pirate.gif.svn-base differ
diff --git a/lib/html/.svn/text-base/blue_pirate.png.svn-base b/lib/html/.svn/text-base/blue_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..1625856
Binary files /dev/null and b/lib/html/.svn/text-base/blue_pirate.png.svn-base differ
diff --git a/lib/html/.svn/text-base/boat.gif.svn-base b/lib/html/.svn/text-base/boat.gif.svn-base
new file mode 100644 (file)
index 0000000..849efa0
Binary files /dev/null and b/lib/html/.svn/text-base/boat.gif.svn-base differ
diff --git a/lib/html/.svn/text-base/boat.png.svn-base b/lib/html/.svn/text-base/boat.png.svn-base
new file mode 100644 (file)
index 0000000..a471eb1
Binary files /dev/null and b/lib/html/.svn/text-base/boat.png.svn-base differ
diff --git a/lib/html/.svn/text-base/bottle.jpg.svn-base b/lib/html/.svn/text-base/bottle.jpg.svn-base
new file mode 100644 (file)
index 0000000..f030006
Binary files /dev/null and b/lib/html/.svn/text-base/bottle.jpg.svn-base differ
diff --git a/lib/html/.svn/text-base/bottle.png.svn-base b/lib/html/.svn/text-base/bottle.png.svn-base
new file mode 100644 (file)
index 0000000..1061015
Binary files /dev/null and b/lib/html/.svn/text-base/bottle.png.svn-base differ
diff --git a/lib/html/.svn/text-base/brown_pirate.png.svn-base b/lib/html/.svn/text-base/brown_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..2db25e9
Binary files /dev/null and b/lib/html/.svn/text-base/brown_pirate.png.svn-base differ
diff --git a/lib/html/.svn/text-base/cell.png.svn-base b/lib/html/.svn/text-base/cell.png.svn-base
new file mode 100644 (file)
index 0000000..bc7e2a0
Binary files /dev/null and b/lib/html/.svn/text-base/cell.png.svn-base differ
diff --git a/lib/html/.svn/text-base/complex.html.netbeans-base b/lib/html/.svn/text-base/complex.html.netbeans-base
new file mode 100644 (file)
index 0000000..b8cc34e
--- /dev/null
@@ -0,0 +1,314 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<HTML>
+<HEAD>
+<TITLE>Cartagena player</TITLE>
+</HEAD>
+<BODY>
+    <h1><a href="index.html">Cartagena</a> player, Complex form</h1>
+<form method="post" enctype="application/x-www-form-urlencoded" action="/cgi-bin/cartagena-complex.rb">
+    <p>Game size: <input name="number_of_players" type="text" value="5">
+        <input name="reset_game" type="submit" value="Reset game">
+        <input name="original_number_of_players" type="hidden" value="5">
+    </p>
+
+<table>
+    <tr>
+        <td><!-- Board column -->
+            <table border="1" cellpadding="3">
+                <tr>
+                    <td><input name="move_origin" type="radio" value="0" checked="true"> Cell
+                        <img src="cell.png" alt="Cell">
+                    </td>
+                    <td>
+                        <img src="red_pirate.png" alt="Red">
+                        <img src="red_pirate.png" alt="Red">
+                        <img src="red_pirate.png" alt="Red">
+                        <img src="red_pirate.png" alt="Red">
+                        <img src="red_pirate.png" alt="Red">
+                        <img src="red_pirate.png" alt="Red">
+                        <img src="green_pirate.png" alt="Green">
+                        <img src="green_pirate.png" alt="Green">
+                        <img src="green_pirate.png" alt="Green">
+                        <img src="green_pirate.png" alt="Green">
+                        <img src="green_pirate.png" alt="Green">
+                        <img src="green_pirate.png" alt="Green">
+                        <img src="blue_pirate.png" alt="Blue">
+                        <img src="blue_pirate.png" alt="Blue">
+                        <img src="blue_pirate.png" alt="Blue">
+                        <img src="blue_pirate.png" alt="Blue">
+                        <img src="blue_pirate.png" alt="Blue">
+                        <img src="blue_pirate.png" alt="Blue">
+                        <img src="yellow_pirate.png" alt="Yellow">
+                        <img src="yellow_pirate.png" alt="Yellow">
+                        <img src="yellow_pirate.png" alt="Yellow">
+                        <img src="yellow_pirate.png" alt="Yellow">
+                        <img src="yellow_pirate.png" alt="Yellow">
+                        <img src="yellow_pirate.png" alt="Yellow">
+                        <img src="brown_pirate.png" alt="Brown">
+                        <img src="brown_pirate.png" alt="Brown">
+                        <img src="brown_pirate.png" alt="Brown">
+                        <img src="brown_pirate.png" alt="Brown">
+                        <img src="brown_pirate.png" alt="Brown">
+                        <img src="brown_pirate.png" alt="Brown">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="1"> 1
+                        <img src="gun.png" alt="Gun">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="2"> 2
+                        <img src="keys.png" alt="Keys">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="3"> 3
+                        <img src="dagger.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="4"> 4
+                        <img src="hat.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="5"> 5
+                        <img src="skull.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="6"> 6
+                        <img src="bottle.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="7"> 7
+                        <img src="keys.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="8"> 8
+                        <img src="dagger.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="9"> 9
+                        <img src="skull.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="10"> 10
+                        <img src="hat.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="11"> 11
+                        <img src="gun.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="12"> 12
+                        <img src="bottle.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="13"> 13
+                        <img src="dagger.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="14"> 14
+                        <img src="bottle.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="15"> 15
+                        <img src="keys.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="16"> 16
+                        <img src="gun.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="17"> 17
+                        <img src="hat.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="18"> 18
+                        <img src="skull.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="19"> 19
+                        <img src="dagger.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="20"> 20
+                        <img src="skull.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="21"> 21
+                        <img src="bottle.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="22"> 22
+                        <img src="gun.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="23"> 23
+                        <img src="keys.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="24"> 24
+                        <img src="hat.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="25"> 25
+                        <img src="hat.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="26"> 26
+                        <img src="dagger.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="27"> 27
+                        <img src="keys.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="28"> 28
+                        <img src="bottle.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="29"> 29
+                        <img src="gun.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="30"> 30
+                        <img src="skull.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="31"> 31
+                        <img src="keys.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="32"> 32
+                        <img src="bottle.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="33"> 33
+                        <img src="skull.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="34"> 34
+                        <img src="dagger.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="35"> 35
+                        <img src="hat.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="36"> 36
+                        <img src="gun.png" alt="Cell">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="37"> Boat
+                        <img src="boat.png" alt="Boat">
+                    </td>
+                    <td>&nbsp;</td>
+                </tr>
+            </table>
+        </td>
+        <td><!-- Controls column -->
+        </td>
+        <td><!-- Move history -->
+            <select name="move_history" size="15"></select><br>
+            <input name="show_earlier_state" type="submit" value="Show earlier state">
+            <input name="move_set" type="hidden" value="">
+        </td>
+    </tr>
+</table>   
+
+<TABLE>
+<TR>
+    <TD>
+        <SELECT name="move_history" size="15"></SELECT><br>
+        <INPUT NAME="show_earlier_state" TYPE="submit" VALUE="Show earlier state"><INPUT NAME="move_set" TYPE="hidden" VALUE=""></TD>
+    <TD align="left">
+        <TABLE border="1">
+            <TR><TD></TD><TD><INPUT NAME="move" TYPE="submit" VALUE="T1"></TD><TD><INPUT NAME="move" TYPE="submit" VALUE="T2"></TD><TD><INPUT NAME="move" TYPE="submit" VALUE="T3"></TD><TD><INPUT NAME="move" TYPE="submit" VALUE="T4"></TD><TD></TD></TR>
+            <TR><TD><INPUT NAME="move" TYPE="submit" VALUE="L1"></TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD><INPUT NAME="move" TYPE="submit" VALUE="R1"></TD></TR>
+            <TR><TD><INPUT NAME="move" TYPE="submit" VALUE="L2"></TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD><INPUT NAME="move" TYPE="submit" VALUE="R2"></TD></TR>
+            <TR><TD><INPUT NAME="move" TYPE="submit" VALUE="L3"></TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD><INPUT NAME="move" TYPE="submit" VALUE="R3"></TD></TR>
+            <TR><TD><INPUT NAME="move" TYPE="submit" VALUE="L4"></TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD align="center">&nbsp;</TD><TD><INPUT NAME="move" TYPE="submit" VALUE="R4"></TD></TR>
+            <TR><TD></TD><TD><INPUT NAME="move" TYPE="submit" VALUE="B1"></TD><TD><INPUT NAME="move" TYPE="submit" VALUE="B2"></TD><TD><INPUT NAME="move" TYPE="submit" VALUE="B3"></TD><TD><INPUT NAME="move" TYPE="submit" VALUE="B4"></TD><TD></TD></TR>
+        <INPUT NAME="board_showing_move" TYPE="hidden" VALUE="">
+</TABLE></TD></TR>
+<TR>
+<TD colspan="2">Selected robot <SELECT NAME="robot">
+        <OPTION VALUE="First Possible">First Possible</OPTION>
+        <OPTION VALUE="Random">Random</OPTION>
+    </SELECT>
+<INPUT NAME="robot_move" TYPE="submit" VALUE="Robot move"><INPUT NAME="chosen_robot" TYPE="hidden" VALUE=""></TD>
+</TR>
+</TABLE>
+</form>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/lib/html/.svn/text-base/complex.html.svn-base b/lib/html/.svn/text-base/complex.html.svn-base
new file mode 100644 (file)
index 0000000..c8f41d5
--- /dev/null
@@ -0,0 +1,237 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<HTML>
+<HEAD>
+<TITLE>Cartagena player</TITLE>
+</HEAD>
+<BODY>
+    <h1><a href="index.html">Cartagena</a> player, Complex form</h1>
+<form method="post" enctype="application/x-www-form-urlencoded" action="/cgi-bin/cartagena-complex.rb">
+<p>Number of players: <select name="number_of_players" size="1">
+                        <option>2</option>
+                        <option>3</option>
+                        <option>4</option>
+                        <option selected>5</option>
+                        </select>
+        <input name="reset_game" type="submit" value="Reset game">
+        <input name="original_number_of_players" type="hidden" value="5">
+    </p>
+
+<table>
+    <tr valign="top">
+        <td><!-- Board column -->
+            <table border="1" cellpadding="3">
+                <tr">
+                    <td><input name="move_origin" type="radio" value="0" checked="true"> Cell
+                        <img src="cell.png" alt="Cell">
+                    </td>
+                    <td colspan="5">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="1"> 1
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="2"> 2
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="3"> 3
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="4"> 4
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="5"> 5
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="6"> 6
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td colspan="5">&nbsp;</td>
+                    <td><input name="move_origin" type="radio" value="7"> 7
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="13"> 13
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="12"> 12
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="11"> 11
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="10"> 10
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="9"> 9
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="8"> 8
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="14"> 14
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                    <td colspan="5">&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="15"> 15
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="16"> 16
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="17"> 17
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="18"> 18
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="19"> 19
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="20"> 20
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td colspan="5">&nbsp;</td>
+                    <td><input name="move_origin" type="radio" value="21"> 21
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="27"> 27
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="26"> 26
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="25"> 25
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="24"> 24
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="23"> 23
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="22"> 22
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="28"> 28
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                    <td colspan="5">&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="29"> 29
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="30"> 30
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="31"> 31
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="32"> 32
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="33"> 33
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="34"> 34
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td colspan="5">&nbsp;</td>
+                    <td><input name="move_origin" type="radio" value="35"> 35
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td align="right" colspan="5"><input name="move_origin" type="radio" value="37"> Boat
+                        <img src="boat.png" alt="Boat" width="36" height="59">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="36"> 36
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                </tr>
+            </table>
+        </td>
+        <td><!-- Controls column -->
+        <p>Active player: <img src="red_pirate.png" width="22" height="22">
+        <p>Actions remaining: 3</p>
+        <p>Action to take</p>
+        <table cellpadding="3" border="1">
+            <tr><td><input name="action" type="radio" value="gun">
+                    <img src="gun.png" alt="Gun" width="36" height="57"></td></tr>
+            <tr><td><input name="action" type="radio" value="hat">
+                    <img src="hat.png" alt="Hat" width="36" height="57"></td></tr>
+            <tr><td><input name="action" type="radio" value="skuill">
+                    <img src="skull.png" alt="Skull" width="36" height="57"></td></tr>
+            <tr><td><input name="action" type="radio" value="retreat">
+                    <img src="retreat.png" alt="Retreat"></td></tr>
+        </table>
+        <INPUT NAME="Move" TYPE="submit" VALUE="Manual move">
+        <p>Selected robot <SELECT NAME="robot">
+            <OPTION VALUE="First Possible">First Possible</OPTION>
+            <OPTION VALUE="Random">Random</OPTION>
+            </SELECT>
+            <INPUT NAME="robot_move" TYPE="submit" VALUE="Robot move"><INPUT NAME="chosen_robot" TYPE="hidden" VALUE=""></p>
+        <p>Players' card counts:</p>
+        <table>
+            <tr><td><img src="red_pirate.png" width="22" height="22"> 3 (active)</td></tr>
+            <tr><td><img src="green_pirate.png" width="22" height="22"> 3</td></tr>
+            <tr><td><img src="blue_pirate.png" width="22" height="22"> 3</td></tr>
+            <tr><td><img src="yellow_pirate.png" width="22" height="22"> 3</td></tr>
+            <tr><td><img src="brown_pirate.png" width="22" height="22"> 3</td></tr>
+        </table>
+        <td><!-- Move history -->
+            <p>Game history</p>
+            <select name="move_history" size="15"></select>
+        </td>
+    </tr>
+</table>   
+
+</form>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/lib/html/.svn/text-base/dagger.jpg.svn-base b/lib/html/.svn/text-base/dagger.jpg.svn-base
new file mode 100644 (file)
index 0000000..e8e1f37
Binary files /dev/null and b/lib/html/.svn/text-base/dagger.jpg.svn-base differ
diff --git a/lib/html/.svn/text-base/dagger.png.svn-base b/lib/html/.svn/text-base/dagger.png.svn-base
new file mode 100644 (file)
index 0000000..0d4dabf
Binary files /dev/null and b/lib/html/.svn/text-base/dagger.png.svn-base differ
diff --git a/lib/html/.svn/text-base/green_pirate.png.svn-base b/lib/html/.svn/text-base/green_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..89223d3
Binary files /dev/null and b/lib/html/.svn/text-base/green_pirate.png.svn-base differ
diff --git a/lib/html/.svn/text-base/gun.jpg.svn-base b/lib/html/.svn/text-base/gun.jpg.svn-base
new file mode 100644 (file)
index 0000000..bc8979e
Binary files /dev/null and b/lib/html/.svn/text-base/gun.jpg.svn-base differ
diff --git a/lib/html/.svn/text-base/gun.png.svn-base b/lib/html/.svn/text-base/gun.png.svn-base
new file mode 100644 (file)
index 0000000..b8c8237
Binary files /dev/null and b/lib/html/.svn/text-base/gun.png.svn-base differ
diff --git a/lib/html/.svn/text-base/hat.jpg.svn-base b/lib/html/.svn/text-base/hat.jpg.svn-base
new file mode 100644 (file)
index 0000000..fe8e99b
Binary files /dev/null and b/lib/html/.svn/text-base/hat.jpg.svn-base differ
diff --git a/lib/html/.svn/text-base/hat.png.svn-base b/lib/html/.svn/text-base/hat.png.svn-base
new file mode 100644 (file)
index 0000000..94a7ad5
Binary files /dev/null and b/lib/html/.svn/text-base/hat.png.svn-base differ
diff --git a/lib/html/.svn/text-base/index.html.netbeans-base b/lib/html/.svn/text-base/index.html.netbeans-base
new file mode 100644 (file)
index 0000000..99a592c
--- /dev/null
@@ -0,0 +1,54 @@
+<html>
+<head>
+<title>Cartagena</title>
+</head>
+<body>
+
+<h1>Cartagena</h1>
+
+<h2>Changes</h2>
+<table cellpadding="3">
+
+<tr valign="top">
+<td>24 October 2008</td>
+<td>Added the <a href="libcartagena.rb"><code>libcartagena</code> library</a>.  </td>
+</tr>
+
+<td>25 September 2008</td>
+<td>Slightly changed the <a href="interface.html">interface</a> description: different file format, and program now has 15 seconds to report a single action, rather than 30 seconds for all three actions.  </td>
+</tr>
+
+<tr valign="top">
+<td>4 April 2008</td>
+<td>Initial version</td>
+</tr>
+
+</table>
+
+<p><em>Cartena</em> is a simple race-based board game.  You can <a href="rules.html">read the
+rules</a> and look at the board to see how the game is played.  More details on Cartagena are at <a href="http://www.boardgamegeek.com/game/826">Board Game Geek</a></p>
+
+<p>The challenge is to build a computer program that will play the
+game well.  The structure of the challenge is heavily based on the one
+for <a href="../pousse/index.html">Pousse</a>.  What needs to be fixed
+for the game is <a
+href="interface.html">how your player will be called</a>.</p>
+
+<h2>The Web interface</h2>
+
+<p>Here you can find some simple web forms that will allow you to
+interact with the automatic Pousse players.  There're two forms
+here:</p>
+
+<ul>
+<li>The <a href="simple.html">simple form</a>, intended for your own
+program to interact with; and</li>
+<li>The complex form, intended for a human player.</li>
+</ul>
+
+<h2>The library</h2>
+
+<p>I've written a <a href="http://www.ruby-lang.org/en/">Ruby</a> library <code><a href="libcartagena.rb">libcartagena.rb</a></code>, to act as a reference implementation.  This library is released under the <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public Licence v3</a>.</p>
+
+</body>
+</html>
diff --git a/lib/html/.svn/text-base/index.html.svn-base b/lib/html/.svn/text-base/index.html.svn-base
new file mode 100644 (file)
index 0000000..99a592c
--- /dev/null
@@ -0,0 +1,54 @@
+<html>
+<head>
+<title>Cartagena</title>
+</head>
+<body>
+
+<h1>Cartagena</h1>
+
+<h2>Changes</h2>
+<table cellpadding="3">
+
+<tr valign="top">
+<td>24 October 2008</td>
+<td>Added the <a href="libcartagena.rb"><code>libcartagena</code> library</a>.  </td>
+</tr>
+
+<td>25 September 2008</td>
+<td>Slightly changed the <a href="interface.html">interface</a> description: different file format, and program now has 15 seconds to report a single action, rather than 30 seconds for all three actions.  </td>
+</tr>
+
+<tr valign="top">
+<td>4 April 2008</td>
+<td>Initial version</td>
+</tr>
+
+</table>
+
+<p><em>Cartena</em> is a simple race-based board game.  You can <a href="rules.html">read the
+rules</a> and look at the board to see how the game is played.  More details on Cartagena are at <a href="http://www.boardgamegeek.com/game/826">Board Game Geek</a></p>
+
+<p>The challenge is to build a computer program that will play the
+game well.  The structure of the challenge is heavily based on the one
+for <a href="../pousse/index.html">Pousse</a>.  What needs to be fixed
+for the game is <a
+href="interface.html">how your player will be called</a>.</p>
+
+<h2>The Web interface</h2>
+
+<p>Here you can find some simple web forms that will allow you to
+interact with the automatic Pousse players.  There're two forms
+here:</p>
+
+<ul>
+<li>The <a href="simple.html">simple form</a>, intended for your own
+program to interact with; and</li>
+<li>The complex form, intended for a human player.</li>
+</ul>
+
+<h2>The library</h2>
+
+<p>I've written a <a href="http://www.ruby-lang.org/en/">Ruby</a> library <code><a href="libcartagena.rb">libcartagena.rb</a></code>, to act as a reference implementation.  This library is released under the <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public Licence v3</a>.</p>
+
+</body>
+</html>
diff --git a/lib/html/.svn/text-base/interface.html.svn-base b/lib/html/.svn/text-base/interface.html.svn-base
new file mode 100644 (file)
index 0000000..d9d76ac
--- /dev/null
@@ -0,0 +1,123 @@
+<html>
+<head>
+<title>Cartagena Interface</title>
+</head>
+<body>
+
+<h1><a href="index.html">Cartagena</a> Computer Interface</h1>
+
+<h2>Changes</h2>
+<table cellpadding="3">
+
+<tr valign="top">
+<td>16 October 2008</td>
+<td>Another slight tweak.  Introduced notation for the card played for an advance move.  </td>
+</tr>
+
+<tr valign="top">
+<td>25 September 2008</td>
+<td>Slightly changed the interface description: different file format, and program now has 15 seconds to report a single action, rather than 30 seconds for all three actions.  </td>
+</tr>
+
+<tr valign="top">
+<td>4 April 2008</td>
+<td>Initial version</td>
+</tr>
+</table>
+
+<p>The game will be played between two and six players.  </p>
+
+<p>Each player in Cartagena has up to three actions (advance or retreat moves) during their turn: your program will return just one of these actions.  </p>
+
+<p>Your program will be run afresh for each move it has to make.  Your program will be fed the history of the game so far on standard input: this is likely to end with the previous actions taken during this turn.  Your program  will then have 15 seconds to report its next action on standard output.  </p>
+
+<p>The format of the input is as follows.  The first line will have a single digit, giving the number of players.  The next portion will state the layout of the board, from start to finish.  Subsequent lines will give the moves played so far in the game (maximum of three consecutive actions by any player).  Next will be a single line giving the number of the player whose turn is next.  The final set of lines will be the cards in your hand (in an arbitrary order). </p>
+
+<p>Postions are numbered consequetively from the start, with zero-based numbering.  The cell is location 0.  The boat is always the last position (number 37 in a standard game).</p>
+
+<p>Players are numbered with one-based numbering: the first player to go is Player 1, the second is Player 2, and so on.</p>
+
+<p>Each move will be in the format <br>
+<code>&lt;player number&gt; &lt;location moved from&gt; &lt;location moved to&gt; &lt;card played&gt;</code><br>
+(with a single space between the fields).  The <code>&lt;card played&gt;</code> field is always missing for retreat moves, is required for advance moves into the boat, and is optional for other advance moves.</p>
+
+<p>It may be that a player takes fewer than their three allowed actions in their turn. There are no additional spaces or blank lines in the file.</p>
+
+<p>Here is an example of a valid input file:</p>
+
+<pre>3
+cell
+skull
+hat
+keys
+gun
+bottle
+dagger
+hat
+gun
+keys
+skull
+bottle
+dagger
+dagger
+gun
+hat
+bottle
+skull
+keys
+hat
+keys
+bottle
+gun
+skull
+keys
+keys
+skull
+hat
+bottle
+gun
+dagger
+bottle
+skull
+gun
+dagger
+keys
+hat
+boat
+1 0 4
+1 0 3 keys
+1 4 3
+2 0 4
+2 4 3
+2 0 2
+3 0 5
+3 5 2
+3 0 4
+1 0 5 bottle
+2
+hat
+skull
+hat
+</pre>
+
+<p>(Note that in this example, player 1 elected to take only one of their three actions for their second turn.  It is now player 2's turn.)</p>
+
+<p>The output of your program will be the actions taken in your turn, in the same format as specified in the input file: one action per line, in the format <br>
+<code>&lt;player number&gt; &lt;location moved from&gt; &lt;location moved to&gt; &lt;card played&gt;</code> (<code>&lt;card played&gt;</code> is optional for advance moves that are not into the boat).</p>
+
+<p>Player programs will run on a Linux machine (probably some version of Ubuntu Linux).  Exact specification is unclear at the moment, but it may run on a twin-processor machine.</p>
+
+<p>The player's program must be named <code>runme</code>; it may be a binary executable or a driver shell script that starts up the actual program. It will be invoked as<br>
+
+<pre>    ./runme</pre><br>
+
+with the program's top-level directory as the current working directory; libraries and other support data may thus be accessed with the relative path name <br>
+
+<pre>    support/...</pre></p>
+
+<p>The program is run once for every move and then terminates; the current state will be passed to the program on standard input. Persistent inter-move state may be kept in the <code>support/</code> directory if desired.  When the program has chosen a move, it should print the move to standard output and exit. If the program has not done so after 30 seconds (real or "wall clock" time), the program will be terminated forcibly, and lose the game. </p>
+
+<p>There should be no child processes alive after the top-level process exits; the program must ensure that all its child processes have exited before the top-level process exits itself.</p>
+
+</body>
+</html>
diff --git a/lib/html/.svn/text-base/keys.jpg.svn-base b/lib/html/.svn/text-base/keys.jpg.svn-base
new file mode 100644 (file)
index 0000000..a0f655e
Binary files /dev/null and b/lib/html/.svn/text-base/keys.jpg.svn-base differ
diff --git a/lib/html/.svn/text-base/keys.png.svn-base b/lib/html/.svn/text-base/keys.png.svn-base
new file mode 100644 (file)
index 0000000..df6147f
Binary files /dev/null and b/lib/html/.svn/text-base/keys.png.svn-base differ
diff --git a/lib/html/.svn/text-base/red_pirate.png.svn-base b/lib/html/.svn/text-base/red_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..9668047
Binary files /dev/null and b/lib/html/.svn/text-base/red_pirate.png.svn-base differ
diff --git a/lib/html/.svn/text-base/retreat.png.svn-base b/lib/html/.svn/text-base/retreat.png.svn-base
new file mode 100644 (file)
index 0000000..09ad452
Binary files /dev/null and b/lib/html/.svn/text-base/retreat.png.svn-base differ
diff --git a/lib/html/.svn/text-base/rules.html.svn-base b/lib/html/.svn/text-base/rules.html.svn-base
new file mode 100644 (file)
index 0000000..876e2b1
--- /dev/null
@@ -0,0 +1,53 @@
+<html>
+<head>
+<title>Cartagena rules</title>
+</head>
+<body>
+
+<h1><a href="index.html">Cartagena</a> rules</h1>
+
+<h2>Changes</h2>
+<table cellpadding="3">
+
+<tr valign="top">
+<td>22 April 2008</td>
+<td>Revised deck size: 17 cards of each type.<br>
+Clarified that this is the Jamaica version</td>
+</tr>
+
+<tr valign="top">
+<td>4 April 2008</td>
+<td>Initial version</td>
+</tr>
+
+</table>
+
+<p><em>Cartena</em> is a simple race-based board game for 2-6 players.  It is based on a famous jailbreak by a group of pirates in 1672.  The board is a sequence of 36 positions.  Each player controls a group of six 'pirate' pieces.  Initially, all the pirates are in the jail at one end of the board.  The first person to get all their pirates off the other end of the board (into the 'rescue sloop') is the winner.</p>
+
+<p>These rules describe the Jamaica variant of the game.</p>
+
+<h2>The board</h2>
+<p>Each position on the board is marked with a symbol (one of <em>bottle</em>, <em>dagger</em>, <em>key</em>, <em>hat</em>, <em>gun</em>, and <em>skull</em>).  The board is made from six segments, each of six positions.  Each segment contains each symbol once, in an arbitrary order.  Positions are numbered, 1-36, from the start to the end of the board.  Each position can have zero, one, two, or three pirates on it at any time.  Who owns the pirates is irrelevant: pirates owned by different players can be on the same space with no special effect.  All pirates start the game before the first space (position zero).  After the final space (position 37) is the goal (the rescue sloop).  </p>
+
+<h2>The cards</h2>
+<p>There is a deck of 108 cards, consisting of seventeen each of the six symbols (<em>bottle</em>, <em>dagger</em>, <em>key</em>, <em>hat</em>, <em>gun</em>, and <em>skull</em>).  </p>
+
+<h2>Setup</h2>
+<p>Each players pirates are placed on position zero.  The deck of cards is shuffled and each player is dealt three cards. Players can see the contents of their hands, but not the contents of the other players' hands.</p>
+
+<h2>Taking a turn</h2>
+<p>Each players takes a turn in sequence.  During a turn, each player may take up to three <em>actions</em>.  The actions are either:</p>
+
+<dl>
+<dt><em>Advance a pirate</em></dt>
+<dd>A player plays a card and moves one of their pirates forward on the board to the next empty space with the same symbol as the played card.  The played card is placed on the discard pile.</dd>
+<dt><em>Retreat a pirate</em></dt>
+<dd>A player moves one of their pirates backward on the board to the next space with one or two pirates on it (spaces with zero or three pirates are skipped).  The pirate cannot skip spaces with one or two pirates on it.  The pirate cannot be moved back to the starting position.  A pirate can be moved backard out of the rescue sloop.  The player draws a number of cards equal to the number of pirates that were on the space before the move (i.e. one or two cards).</dd>
+</dl>
+
+<p>If the deck of cards is ever exhausted, the discard pile is shuffled and becomes the deck.</p>
+
+<p>Back to the <a href="index.html">Cartagena page</a>.</p>
+
+</body>
+</html>
diff --git a/lib/html/.svn/text-base/simple.html.netbeans-base b/lib/html/.svn/text-base/simple.html.netbeans-base
new file mode 100644 (file)
index 0000000..4142904
--- /dev/null
@@ -0,0 +1,76 @@
+<html>
+<head>
+<title>Simple Cartagena form </title>
+</head>
+<body>
+
+<h1>Simple <a href="index.html">Cartagena</a> form</h1>
+
+<form action="/cgi-bin/cartagena-simple.rb" method="post">
+
+<table cellpadding=3>
+<tr valign="top">
+<td>Robot input:</td>
+<td><textarea rows="20" cols="10" wrap="off" name="history">5
+cell
+gun
+keys
+dagger
+hat
+skull
+bottle
+keys
+dagger
+skull
+hat
+gun
+bottle
+dagger
+bottle
+keys
+gun
+hat
+skull
+dagger
+skull
+bottle
+gun
+keys
+hat
+hat
+dagger
+keys
+bottle
+gun
+skull
+keys
+bottle
+skull
+dagger
+hat
+gun
+boat
+1
+gun
+hat
+skull</textarea></td>
+</tr>
+
+<tr>
+<td>Robot:</td>
+<td><select name="robot">
+<option>First Possible</option>
+<option>Random</option>
+</select>
+</tr>
+
+<tr>
+<td><input type="submit" value="move"></td>
+<td><input type="reset"></td>
+</tr>
+</table>
+
+</form>
+
+</body>
+</html>
diff --git a/lib/html/.svn/text-base/simple.html.svn-base b/lib/html/.svn/text-base/simple.html.svn-base
new file mode 100644 (file)
index 0000000..4142904
--- /dev/null
@@ -0,0 +1,76 @@
+<html>
+<head>
+<title>Simple Cartagena form </title>
+</head>
+<body>
+
+<h1>Simple <a href="index.html">Cartagena</a> form</h1>
+
+<form action="/cgi-bin/cartagena-simple.rb" method="post">
+
+<table cellpadding=3>
+<tr valign="top">
+<td>Robot input:</td>
+<td><textarea rows="20" cols="10" wrap="off" name="history">5
+cell
+gun
+keys
+dagger
+hat
+skull
+bottle
+keys
+dagger
+skull
+hat
+gun
+bottle
+dagger
+bottle
+keys
+gun
+hat
+skull
+dagger
+skull
+bottle
+gun
+keys
+hat
+hat
+dagger
+keys
+bottle
+gun
+skull
+keys
+bottle
+skull
+dagger
+hat
+gun
+boat
+1
+gun
+hat
+skull</textarea></td>
+</tr>
+
+<tr>
+<td>Robot:</td>
+<td><select name="robot">
+<option>First Possible</option>
+<option>Random</option>
+</select>
+</tr>
+
+<tr>
+<td><input type="submit" value="move"></td>
+<td><input type="reset"></td>
+</tr>
+</table>
+
+</form>
+
+</body>
+</html>
diff --git a/lib/html/.svn/text-base/skull.jpg.svn-base b/lib/html/.svn/text-base/skull.jpg.svn-base
new file mode 100644 (file)
index 0000000..0575e00
Binary files /dev/null and b/lib/html/.svn/text-base/skull.jpg.svn-base differ
diff --git a/lib/html/.svn/text-base/skull.png.svn-base b/lib/html/.svn/text-base/skull.png.svn-base
new file mode 100644 (file)
index 0000000..cad84e3
Binary files /dev/null and b/lib/html/.svn/text-base/skull.png.svn-base differ
diff --git a/lib/html/.svn/text-base/yellow_pirate.png.svn-base b/lib/html/.svn/text-base/yellow_pirate.png.svn-base
new file mode 100644 (file)
index 0000000..b697834
Binary files /dev/null and b/lib/html/.svn/text-base/yellow_pirate.png.svn-base differ
diff --git a/lib/html/blue-pirate.gif b/lib/html/blue-pirate.gif
new file mode 100644 (file)
index 0000000..8630fab
Binary files /dev/null and b/lib/html/blue-pirate.gif differ
diff --git a/lib/html/blue_pirate.png b/lib/html/blue_pirate.png
new file mode 100644 (file)
index 0000000..1625856
Binary files /dev/null and b/lib/html/blue_pirate.png differ
diff --git a/lib/html/boat.gif b/lib/html/boat.gif
new file mode 100644 (file)
index 0000000..849efa0
Binary files /dev/null and b/lib/html/boat.gif differ
diff --git a/lib/html/boat.png b/lib/html/boat.png
new file mode 100644 (file)
index 0000000..a471eb1
Binary files /dev/null and b/lib/html/boat.png differ
diff --git a/lib/html/bottle.jpg b/lib/html/bottle.jpg
new file mode 100644 (file)
index 0000000..f030006
Binary files /dev/null and b/lib/html/bottle.jpg differ
diff --git a/lib/html/bottle.png b/lib/html/bottle.png
new file mode 100644 (file)
index 0000000..1061015
Binary files /dev/null and b/lib/html/bottle.png differ
diff --git a/lib/html/brown_pirate.png b/lib/html/brown_pirate.png
new file mode 100644 (file)
index 0000000..2db25e9
Binary files /dev/null and b/lib/html/brown_pirate.png differ
diff --git a/lib/html/cell.png b/lib/html/cell.png
new file mode 100644 (file)
index 0000000..bc7e2a0
Binary files /dev/null and b/lib/html/cell.png differ
diff --git a/lib/html/complex.html b/lib/html/complex.html
new file mode 100644 (file)
index 0000000..c8f41d5
--- /dev/null
@@ -0,0 +1,237 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<HTML>
+<HEAD>
+<TITLE>Cartagena player</TITLE>
+</HEAD>
+<BODY>
+    <h1><a href="index.html">Cartagena</a> player, Complex form</h1>
+<form method="post" enctype="application/x-www-form-urlencoded" action="/cgi-bin/cartagena-complex.rb">
+<p>Number of players: <select name="number_of_players" size="1">
+                        <option>2</option>
+                        <option>3</option>
+                        <option>4</option>
+                        <option selected>5</option>
+                        </select>
+        <input name="reset_game" type="submit" value="Reset game">
+        <input name="original_number_of_players" type="hidden" value="5">
+    </p>
+
+<table>
+    <tr valign="top">
+        <td><!-- Board column -->
+            <table border="1" cellpadding="3">
+                <tr">
+                    <td><input name="move_origin" type="radio" value="0" checked="true"> Cell
+                        <img src="cell.png" alt="Cell">
+                    </td>
+                    <td colspan="5">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="red_pirate.png" alt="Red" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="green_pirate.png" alt="Green" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="blue_pirate.png" alt="Blue" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="yellow_pirate.png" alt="Yellow" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                        <img src="brown_pirate.png" alt="Brown" width="22" height="22">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="1"> 1
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="2"> 2
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="3"> 3
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="4"> 4
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="5"> 5
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="6"> 6
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td colspan="5">&nbsp;</td>
+                    <td><input name="move_origin" type="radio" value="7"> 7
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="13"> 13
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="12"> 12
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="11"> 11
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="10"> 10
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="9"> 9
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="8"> 8
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="14"> 14
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                    <td colspan="5">&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="15"> 15
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="16"> 16
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="17"> 17
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="18"> 18
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="19"> 19
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="20"> 20
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td colspan="5">&nbsp;</td>
+                    <td><input name="move_origin" type="radio" value="21"> 21
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="27"> 27
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="26"> 26
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="25"> 25
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="24"> 24
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="23"> 23
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="22"> 22
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="28"> 28
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                    <td colspan="5">&nbsp;</td>
+                </tr>
+                <tr>
+                    <td><input name="move_origin" type="radio" value="29"> 29
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="30"> 30
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="31"> 31
+                        <img src="keys.png" alt="Keys" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="32"> 32
+                        <img src="bottle.png" alt="Bottle" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="33"> 33
+                        <img src="skull.png" alt="Skull" width="36" height="57">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="34"> 34
+                        <img src="dagger.png" alt="Dagger" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td colspan="5">&nbsp;</td>
+                    <td><input name="move_origin" type="radio" value="35"> 35
+                        <img src="hat.png" alt="Hat" width="36" height="57">
+                    </td>
+                </tr>
+                <tr>
+                    <td align="right" colspan="5"><input name="move_origin" type="radio" value="37"> Boat
+                        <img src="boat.png" alt="Boat" width="36" height="59">
+                    </td>
+                    <td><input name="move_origin" type="radio" value="36"> 36
+                        <img src="gun.png" alt="Gun" width="36" height="57">
+                    </td>
+                </tr>
+            </table>
+        </td>
+        <td><!-- Controls column -->
+        <p>Active player: <img src="red_pirate.png" width="22" height="22">
+        <p>Actions remaining: 3</p>
+        <p>Action to take</p>
+        <table cellpadding="3" border="1">
+            <tr><td><input name="action" type="radio" value="gun">
+                    <img src="gun.png" alt="Gun" width="36" height="57"></td></tr>
+            <tr><td><input name="action" type="radio" value="hat">
+                    <img src="hat.png" alt="Hat" width="36" height="57"></td></tr>
+            <tr><td><input name="action" type="radio" value="skuill">
+                    <img src="skull.png" alt="Skull" width="36" height="57"></td></tr>
+            <tr><td><input name="action" type="radio" value="retreat">
+                    <img src="retreat.png" alt="Retreat"></td></tr>
+        </table>
+        <INPUT NAME="Move" TYPE="submit" VALUE="Manual move">
+        <p>Selected robot <SELECT NAME="robot">
+            <OPTION VALUE="First Possible">First Possible</OPTION>
+            <OPTION VALUE="Random">Random</OPTION>
+            </SELECT>
+            <INPUT NAME="robot_move" TYPE="submit" VALUE="Robot move"><INPUT NAME="chosen_robot" TYPE="hidden" VALUE=""></p>
+        <p>Players' card counts:</p>
+        <table>
+            <tr><td><img src="red_pirate.png" width="22" height="22"> 3 (active)</td></tr>
+            <tr><td><img src="green_pirate.png" width="22" height="22"> 3</td></tr>
+            <tr><td><img src="blue_pirate.png" width="22" height="22"> 3</td></tr>
+            <tr><td><img src="yellow_pirate.png" width="22" height="22"> 3</td></tr>
+            <tr><td><img src="brown_pirate.png" width="22" height="22"> 3</td></tr>
+        </table>
+        <td><!-- Move history -->
+            <p>Game history</p>
+            <select name="move_history" size="15"></select>
+        </td>
+    </tr>
+</table>   
+
+</form>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/lib/html/dagger.jpg b/lib/html/dagger.jpg
new file mode 100644 (file)
index 0000000..e8e1f37
Binary files /dev/null and b/lib/html/dagger.jpg differ
diff --git a/lib/html/dagger.png b/lib/html/dagger.png
new file mode 100644 (file)
index 0000000..0d4dabf
Binary files /dev/null and b/lib/html/dagger.png differ
diff --git a/lib/html/green_pirate.png b/lib/html/green_pirate.png
new file mode 100644 (file)
index 0000000..89223d3
Binary files /dev/null and b/lib/html/green_pirate.png differ
diff --git a/lib/html/gun.jpg b/lib/html/gun.jpg
new file mode 100644 (file)
index 0000000..bc8979e
Binary files /dev/null and b/lib/html/gun.jpg differ
diff --git a/lib/html/gun.png b/lib/html/gun.png
new file mode 100644 (file)
index 0000000..b8c8237
Binary files /dev/null and b/lib/html/gun.png differ
diff --git a/lib/html/hat.jpg b/lib/html/hat.jpg
new file mode 100644 (file)
index 0000000..fe8e99b
Binary files /dev/null and b/lib/html/hat.jpg differ
diff --git a/lib/html/hat.png b/lib/html/hat.png
new file mode 100644 (file)
index 0000000..94a7ad5
Binary files /dev/null and b/lib/html/hat.png differ
diff --git a/lib/html/index.html b/lib/html/index.html
new file mode 100644 (file)
index 0000000..a046414
--- /dev/null
@@ -0,0 +1,59 @@
+<html>
+<head>
+<title>Cartagena</title>
+</head>
+<body>
+
+<h1>Cartagena</h1>
+
+<h2>Changes</h2>
+<table cellpadding="3">
+
+<tr valign="top">
+<td>5 December 2008</td>
+<td>Updated the <a href="libcartagena.rb"><code>libcartagena</code> library</a>and added the <a href="libcartagena_test.rb">library unit tests</a>.</td>
+</tr>
+
+<tr valign="top">
+<td>24 October 2008</td>
+<td>Added the <a href="libcartagena.rb"><code>libcartagena</code> library</a>.  </td>
+</tr>
+
+<td>25 September 2008</td>
+<td>Slightly changed the <a href="interface.html">interface</a> description: different file format, and program now has 15 seconds to report a single action, rather than 30 seconds for all three actions.  </td>
+</tr>
+
+<tr valign="top">
+<td>4 April 2008</td>
+<td>Initial version</td>
+</tr>
+
+</table>
+
+<p><em>Cartena</em> is a simple race-based board game.  You can <a href="rules.html">read the
+rules</a> and look at the board to see how the game is played.  More details on Cartagena are at <a href="http://www.boardgamegeek.com/game/826">Board Game Geek</a></p>
+
+<p>The challenge is to build a computer program that will play the
+game well.  The structure of the challenge is heavily based on the one
+for <a href="../pousse/index.html">Pousse</a>.  What needs to be fixed
+for the game is <a
+href="interface.html">how your player will be called</a>.</p>
+
+<h2>The Web interface</h2>
+
+<p>Here you can find some simple web forms that will allow you to
+interact with the automatic Pousse players.  There're two forms
+here:</p>
+
+<ul>
+<li>The <a href="simple.html">simple form</a>, intended for your own
+program to interact with; and</li>
+<li>The complex form, intended for a human player.</li>
+</ul>
+
+<h2>The library</h2>
+
+<p>I've written a <a href="http://www.ruby-lang.org/en/">Ruby</a> library <code><a href="libcartagena.rb">libcartagena.rb</a></code>, to act as a reference implementation.  You can also download the <a href="libcartagena_test.rb">unit tests</a> for it.  Both the library and the unit tests are released under the <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public Licence v3</a>.</p>
+
+</body>
+</html>
diff --git a/lib/html/interface.html b/lib/html/interface.html
new file mode 100644 (file)
index 0000000..d9d76ac
--- /dev/null
@@ -0,0 +1,123 @@
+<html>
+<head>
+<title>Cartagena Interface</title>
+</head>
+<body>
+
+<h1><a href="index.html">Cartagena</a> Computer Interface</h1>
+
+<h2>Changes</h2>
+<table cellpadding="3">
+
+<tr valign="top">
+<td>16 October 2008</td>
+<td>Another slight tweak.  Introduced notation for the card played for an advance move.  </td>
+</tr>
+
+<tr valign="top">
+<td>25 September 2008</td>
+<td>Slightly changed the interface description: different file format, and program now has 15 seconds to report a single action, rather than 30 seconds for all three actions.  </td>
+</tr>
+
+<tr valign="top">
+<td>4 April 2008</td>
+<td>Initial version</td>
+</tr>
+</table>
+
+<p>The game will be played between two and six players.  </p>
+
+<p>Each player in Cartagena has up to three actions (advance or retreat moves) during their turn: your program will return just one of these actions.  </p>
+
+<p>Your program will be run afresh for each move it has to make.  Your program will be fed the history of the game so far on standard input: this is likely to end with the previous actions taken during this turn.  Your program  will then have 15 seconds to report its next action on standard output.  </p>
+
+<p>The format of the input is as follows.  The first line will have a single digit, giving the number of players.  The next portion will state the layout of the board, from start to finish.  Subsequent lines will give the moves played so far in the game (maximum of three consecutive actions by any player).  Next will be a single line giving the number of the player whose turn is next.  The final set of lines will be the cards in your hand (in an arbitrary order). </p>
+
+<p>Postions are numbered consequetively from the start, with zero-based numbering.  The cell is location 0.  The boat is always the last position (number 37 in a standard game).</p>
+
+<p>Players are numbered with one-based numbering: the first player to go is Player 1, the second is Player 2, and so on.</p>
+
+<p>Each move will be in the format <br>
+<code>&lt;player number&gt; &lt;location moved from&gt; &lt;location moved to&gt; &lt;card played&gt;</code><br>
+(with a single space between the fields).  The <code>&lt;card played&gt;</code> field is always missing for retreat moves, is required for advance moves into the boat, and is optional for other advance moves.</p>
+
+<p>It may be that a player takes fewer than their three allowed actions in their turn. There are no additional spaces or blank lines in the file.</p>
+
+<p>Here is an example of a valid input file:</p>
+
+<pre>3
+cell
+skull
+hat
+keys
+gun
+bottle
+dagger
+hat
+gun
+keys
+skull
+bottle
+dagger
+dagger
+gun
+hat
+bottle
+skull
+keys
+hat
+keys
+bottle
+gun
+skull
+keys
+keys
+skull
+hat
+bottle
+gun
+dagger
+bottle
+skull
+gun
+dagger
+keys
+hat
+boat
+1 0 4
+1 0 3 keys
+1 4 3
+2 0 4
+2 4 3
+2 0 2
+3 0 5
+3 5 2
+3 0 4
+1 0 5 bottle
+2
+hat
+skull
+hat
+</pre>
+
+<p>(Note that in this example, player 1 elected to take only one of their three actions for their second turn.  It is now player 2's turn.)</p>
+
+<p>The output of your program will be the actions taken in your turn, in the same format as specified in the input file: one action per line, in the format <br>
+<code>&lt;player number&gt; &lt;location moved from&gt; &lt;location moved to&gt; &lt;card played&gt;</code> (<code>&lt;card played&gt;</code> is optional for advance moves that are not into the boat).</p>
+
+<p>Player programs will run on a Linux machine (probably some version of Ubuntu Linux).  Exact specification is unclear at the moment, but it may run on a twin-processor machine.</p>
+
+<p>The player's program must be named <code>runme</code>; it may be a binary executable or a driver shell script that starts up the actual program. It will be invoked as<br>
+
+<pre>    ./runme</pre><br>
+
+with the program's top-level directory as the current working directory; libraries and other support data may thus be accessed with the relative path name <br>
+
+<pre>    support/...</pre></p>
+
+<p>The program is run once for every move and then terminates; the current state will be passed to the program on standard input. Persistent inter-move state may be kept in the <code>support/</code> directory if desired.  When the program has chosen a move, it should print the move to standard output and exit. If the program has not done so after 30 seconds (real or "wall clock" time), the program will be terminated forcibly, and lose the game. </p>
+
+<p>There should be no child processes alive after the top-level process exits; the program must ensure that all its child processes have exited before the top-level process exits itself.</p>
+
+</body>
+</html>
diff --git a/lib/html/keys.jpg b/lib/html/keys.jpg
new file mode 100644 (file)
index 0000000..a0f655e
Binary files /dev/null and b/lib/html/keys.jpg differ
diff --git a/lib/html/keys.png b/lib/html/keys.png
new file mode 100644 (file)
index 0000000..df6147f
Binary files /dev/null and b/lib/html/keys.png differ
diff --git a/lib/html/red_pirate.png b/lib/html/red_pirate.png
new file mode 100644 (file)
index 0000000..9668047
Binary files /dev/null and b/lib/html/red_pirate.png differ
diff --git a/lib/html/retreat.png b/lib/html/retreat.png
new file mode 100644 (file)
index 0000000..09ad452
Binary files /dev/null and b/lib/html/retreat.png differ
diff --git a/lib/html/robots/.svn/all-wcprops b/lib/html/robots/.svn/all-wcprops
new file mode 100644 (file)
index 0000000..7f10177
--- /dev/null
@@ -0,0 +1,5 @@
+K 25
+svn:wc:ra_dav:version-url
+V 53
+/svn/njae/!svn/ver/43/cartagena/trunk/lib/html/robots
+END
diff --git a/lib/html/robots/.svn/entries b/lib/html/robots/.svn/entries
new file mode 100644 (file)
index 0000000..eb63fab
--- /dev/null
@@ -0,0 +1,34 @@
+8
+
+dir
+65
+http://scripts.njae.me.uk/svn/njae/cartagena/trunk/lib/html/robots
+http://scripts.njae.me.uk/svn/njae
+
+
+
+2008-10-17T07:57:36.358504Z
+43
+
+
+
+svn:special svn:externals svn:needs-lock
+
+
+
+
+
+
+
+
+
+
+
+0ada9808-85ab-4924-adda-5bf89eae3818
+\f
+1
+dir
+\f
+2
+dir
+\f
diff --git a/lib/html/robots/.svn/format b/lib/html/robots/.svn/format
new file mode 100644 (file)
index 0000000..45a4fb7
--- /dev/null
@@ -0,0 +1 @@
+8
diff --git a/lib/html/robots/1/.svn/all-wcprops b/lib/html/robots/1/.svn/all-wcprops
new file mode 100644 (file)
index 0000000..287b1c3
--- /dev/null
@@ -0,0 +1,11 @@
+K 25
+svn:wc:ra_dav:version-url
+V 55
+/svn/njae/!svn/ver/42/cartagena/trunk/lib/html/robots/1
+END
+runme.rb
+K 25
+svn:wc:ra_dav:version-url
+V 64
+/svn/njae/!svn/ver/42/cartagena/trunk/lib/html/robots/1/runme.rb
+END
diff --git a/lib/html/robots/1/.svn/entries b/lib/html/robots/1/.svn/entries
new file mode 100644 (file)
index 0000000..2ebdd92
--- /dev/null
@@ -0,0 +1,27 @@
+8
+
+dir
+65
+http://scripts.njae.me.uk/svn/njae/cartagena/trunk/lib/html/robots/1
+http://scripts.njae.me.uk/svn/njae
+
+
+
+2008-10-16T20:38:07.769887Z
+42
+
+
+
+svn:special svn:externals svn:needs-lock
+\f
+runme.rb
+file
+
+
+
+
+2008-10-16T14:42:29.000000Z
+db3d894459b82c2f69b1ddd6b5d66858
+2008-10-16T20:38:07.769887Z
+42
+\f
diff --git a/lib/html/robots/1/.svn/format b/lib/html/robots/1/.svn/format
new file mode 100644 (file)
index 0000000..45a4fb7
--- /dev/null
@@ -0,0 +1 @@
+8
diff --git a/lib/html/robots/1/.svn/text-base/runme.rb.svn-base b/lib/html/robots/1/.svn/text-base/runme.rb.svn-base
new file mode 100644 (file)
index 0000000..4c96457
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/ruby
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# clockwise-p1 
+#  Game state file read on STDIN
+#  Move produced on STDOUT
+#
+# == Author
+# Neil Smith
+
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'libcartagena'
+require 'rdoc/usage'
+
+gamelines = readlines
+game, moves = Game.read_game(gamelines)
+move = game.possible_moves[0]
+puts move.format(game.board, true)
diff --git a/lib/html/robots/1/runme.rb b/lib/html/robots/1/runme.rb
new file mode 100644 (file)
index 0000000..4c96457
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/ruby
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# clockwise-p1 
+#  Game state file read on STDIN
+#  Move produced on STDOUT
+#
+# == Author
+# Neil Smith
+
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'libcartagena'
+require 'rdoc/usage'
+
+gamelines = readlines
+game, moves = Game.read_game(gamelines)
+move = game.possible_moves[0]
+puts move.format(game.board, true)
diff --git a/lib/html/robots/2/.svn/all-wcprops b/lib/html/robots/2/.svn/all-wcprops
new file mode 100644 (file)
index 0000000..9d478f7
--- /dev/null
@@ -0,0 +1,11 @@
+K 25
+svn:wc:ra_dav:version-url
+V 55
+/svn/njae/!svn/ver/43/cartagena/trunk/lib/html/robots/2
+END
+runme.rb
+K 25
+svn:wc:ra_dav:version-url
+V 64
+/svn/njae/!svn/ver/43/cartagena/trunk/lib/html/robots/2/runme.rb
+END
diff --git a/lib/html/robots/2/.svn/entries b/lib/html/robots/2/.svn/entries
new file mode 100644 (file)
index 0000000..7f845d7
--- /dev/null
@@ -0,0 +1,39 @@
+8
+
+dir
+65
+http://scripts.njae.me.uk/svn/njae/cartagena/trunk/lib/html/robots/2
+http://scripts.njae.me.uk/svn/njae
+
+
+
+2008-10-17T07:57:36.358504Z
+43
+
+
+
+svn:special svn:externals svn:needs-lock
+
+
+
+
+
+
+
+
+
+
+
+0ada9808-85ab-4924-adda-5bf89eae3818
+\f
+runme.rb
+file
+
+
+
+
+2008-10-17T07:28:50.000000Z
+9d7aa6b67c047dd18ff05819c78e9a7d
+2008-10-17T07:57:36.358504Z
+43
+\f
diff --git a/lib/html/robots/2/.svn/format b/lib/html/robots/2/.svn/format
new file mode 100644 (file)
index 0000000..45a4fb7
--- /dev/null
@@ -0,0 +1 @@
+8
diff --git a/lib/html/robots/2/.svn/text-base/runme.rb.svn-base b/lib/html/robots/2/.svn/text-base/runme.rb.svn-base
new file mode 100644 (file)
index 0000000..53f6c5f
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/ruby
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# clockwise-p1 
+#  Game state file read on STDIN
+#  Move produced on STDOUT
+#
+# == Author
+# Neil Smith
+
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'libcartagena'
+require 'rdoc/usage'
+
+gamelines = readlines
+game, moves = Game.read_game(gamelines)
+possibles = game.possible_moves
+move = possibles[rand(possibles.length)]
+puts move.format(game.board, true)
diff --git a/lib/html/robots/2/runme.rb b/lib/html/robots/2/runme.rb
new file mode 100644 (file)
index 0000000..53f6c5f
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/ruby
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# clockwise-p1 
+#  Game state file read on STDIN
+#  Move produced on STDOUT
+#
+# == Author
+# Neil Smith
+
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'libcartagena'
+require 'rdoc/usage'
+
+gamelines = readlines
+game, moves = Game.read_game(gamelines)
+possibles = game.possible_moves
+move = possibles[rand(possibles.length)]
+puts move.format(game.board, true)
diff --git a/lib/html/rules.html b/lib/html/rules.html
new file mode 100644 (file)
index 0000000..876e2b1
--- /dev/null
@@ -0,0 +1,53 @@
+<html>
+<head>
+<title>Cartagena rules</title>
+</head>
+<body>
+
+<h1><a href="index.html">Cartagena</a> rules</h1>
+
+<h2>Changes</h2>
+<table cellpadding="3">
+
+<tr valign="top">
+<td>22 April 2008</td>
+<td>Revised deck size: 17 cards of each type.<br>
+Clarified that this is the Jamaica version</td>
+</tr>
+
+<tr valign="top">
+<td>4 April 2008</td>
+<td>Initial version</td>
+</tr>
+
+</table>
+
+<p><em>Cartena</em> is a simple race-based board game for 2-6 players.  It is based on a famous jailbreak by a group of pirates in 1672.  The board is a sequence of 36 positions.  Each player controls a group of six 'pirate' pieces.  Initially, all the pirates are in the jail at one end of the board.  The first person to get all their pirates off the other end of the board (into the 'rescue sloop') is the winner.</p>
+
+<p>These rules describe the Jamaica variant of the game.</p>
+
+<h2>The board</h2>
+<p>Each position on the board is marked with a symbol (one of <em>bottle</em>, <em>dagger</em>, <em>key</em>, <em>hat</em>, <em>gun</em>, and <em>skull</em>).  The board is made from six segments, each of six positions.  Each segment contains each symbol once, in an arbitrary order.  Positions are numbered, 1-36, from the start to the end of the board.  Each position can have zero, one, two, or three pirates on it at any time.  Who owns the pirates is irrelevant: pirates owned by different players can be on the same space with no special effect.  All pirates start the game before the first space (position zero).  After the final space (position 37) is the goal (the rescue sloop).  </p>
+
+<h2>The cards</h2>
+<p>There is a deck of 108 cards, consisting of seventeen each of the six symbols (<em>bottle</em>, <em>dagger</em>, <em>key</em>, <em>hat</em>, <em>gun</em>, and <em>skull</em>).  </p>
+
+<h2>Setup</h2>
+<p>Each players pirates are placed on position zero.  The deck of cards is shuffled and each player is dealt three cards. Players can see the contents of their hands, but not the contents of the other players' hands.</p>
+
+<h2>Taking a turn</h2>
+<p>Each players takes a turn in sequence.  During a turn, each player may take up to three <em>actions</em>.  The actions are either:</p>
+
+<dl>
+<dt><em>Advance a pirate</em></dt>
+<dd>A player plays a card and moves one of their pirates forward on the board to the next empty space with the same symbol as the played card.  The played card is placed on the discard pile.</dd>
+<dt><em>Retreat a pirate</em></dt>
+<dd>A player moves one of their pirates backward on the board to the next space with one or two pirates on it (spaces with zero or three pirates are skipped).  The pirate cannot skip spaces with one or two pirates on it.  The pirate cannot be moved back to the starting position.  A pirate can be moved backard out of the rescue sloop.  The player draws a number of cards equal to the number of pirates that were on the space before the move (i.e. one or two cards).</dd>
+</dl>
+
+<p>If the deck of cards is ever exhausted, the discard pile is shuffled and becomes the deck.</p>
+
+<p>Back to the <a href="index.html">Cartagena page</a>.</p>
+
+</body>
+</html>
diff --git a/lib/html/simple.html b/lib/html/simple.html
new file mode 100644 (file)
index 0000000..4142904
--- /dev/null
@@ -0,0 +1,76 @@
+<html>
+<head>
+<title>Simple Cartagena form </title>
+</head>
+<body>
+
+<h1>Simple <a href="index.html">Cartagena</a> form</h1>
+
+<form action="/cgi-bin/cartagena-simple.rb" method="post">
+
+<table cellpadding=3>
+<tr valign="top">
+<td>Robot input:</td>
+<td><textarea rows="20" cols="10" wrap="off" name="history">5
+cell
+gun
+keys
+dagger
+hat
+skull
+bottle
+keys
+dagger
+skull
+hat
+gun
+bottle
+dagger
+bottle
+keys
+gun
+hat
+skull
+dagger
+skull
+bottle
+gun
+keys
+hat
+hat
+dagger
+keys
+bottle
+gun
+skull
+keys
+bottle
+skull
+dagger
+hat
+gun
+boat
+1
+gun
+hat
+skull</textarea></td>
+</tr>
+
+<tr>
+<td>Robot:</td>
+<td><select name="robot">
+<option>First Possible</option>
+<option>Random</option>
+</select>
+</tr>
+
+<tr>
+<td><input type="submit" value="move"></td>
+<td><input type="reset"></td>
+</tr>
+</table>
+
+</form>
+
+</body>
+</html>
diff --git a/lib/html/skull.jpg b/lib/html/skull.jpg
new file mode 100644 (file)
index 0000000..0575e00
Binary files /dev/null and b/lib/html/skull.jpg differ
diff --git a/lib/html/skull.png b/lib/html/skull.png
new file mode 100644 (file)
index 0000000..cad84e3
Binary files /dev/null and b/lib/html/skull.png differ
diff --git a/lib/html/yellow_pirate.png b/lib/html/yellow_pirate.png
new file mode 100644 (file)
index 0000000..b697834
Binary files /dev/null and b/lib/html/yellow_pirate.png differ
diff --git a/lib/libcartagena.rb b/lib/libcartagena.rb
new file mode 100644 (file)
index 0000000..a0574d8
--- /dev/null
@@ -0,0 +1,722 @@
+# == Synopsis
+#
+# Library to support Cartagena play
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  11 Jun 2008  
+#                * Initial build
+#         1.1::  5 Dec 2008
+# =>             * Now tracks played cards when generating new games
+
+# Symbols
+$SYMBOLS = [:bottle, :dagger, :gun, :hat, :keys, :skull]
+
+# The number of cards of each symbol in the deck
+$CARDS_PER_SYMBOL = 17
+
+# The number of cards initiall dealt to each player
+$INITIAL_CARDS_PER_PLAYER = 3
+
+# Maximum number of pieces on a position
+$MAX_PIECES_PER_POSITION = 3
+
+# Number of actions that can be taken by each player in sequence
+$MAX_MOVES_PER_TURN = 3
+
+# Errors for a game
+
+# Moves can only be [1..6] spaces
+class InvalidMoveError < StandardError
+end
+
+# Game is won when only one player has uncaptured pieces
+class GameWonNotice < StandardError
+end
+
+# A position on the board.
+class Position
+  attr_accessor :symbol
+  attr_accessor :contains
+  
+  def initialize(symbol)
+    @symbol = symbol
+    @contains = []
+  end
+end
+
+# A tile that makes up the board.  Each tile has two sides, each with six
+# positions.  Tiles can be either way up, and either way round.
+class Tile
+  attr_reader :exposed, :front, :back
+  
+  def initialize(front, back)
+    @front = front.collect {|s| Position.new(s)}
+    @back = back.collect {|s| Position.new(s)}
+    @exposed = @front
+    @exposed_side = :front
+  end
+  
+  def flip!
+    if @exposed_side == :front
+      @exposed = @back
+      @exposed_side = :back
+    else
+      @exposed = @front
+      @exposed_side = :front
+    end
+  end
+  
+  def reverse!
+    @exposed = @exposed.reverse
+  end
+end
+
+
+# The game board
+class Board
+
+  attr_accessor :positions
+  attr_reader :tiles
+
+  # A laborious procedure to create all the positions and tie them all together
+  def initialize(tiles_used)
+    # A hash of all positions, indexed by position names
+    @tiles = [Tile.new([:hat, :keys, :gun, :bottle, :skull, :dagger], 
+        [:hat, :keys, :gun, :bottle, :skull, :dagger]),
+      Tile.new([:gun, :hat, :dagger, :skull, :bottle, :keys],
+        [:gun, :hat, :dagger, :skull, :bottle, :keys]),
+      Tile.new([:skull, :gun, :bottle, :keys, :dagger, :hat],
+        [:skull, :gun, :bottle, :keys, :dagger, :hat]),
+      Tile.new([:dagger, :bottle, :keys, :gun, :hat, :skull],
+        [:dagger, :bottle, :keys, :gun, :hat, :skull]),
+      Tile.new([:keys, :dagger, :skull, :hat, :gun, :bottle],
+        [:keys, :dagger, :skull, :hat, :gun, :bottle]),
+      Tile.new([:bottle, :skull, :hat, :dagger, :keys, :gun],
+        [:bottle, :skull, :hat, :dagger, :keys, :gun])                 
+    ].shuffle[0...tiles_used]
+    @tiles = @tiles.each do |t|
+      if rand < 0.5
+        t.reverse!
+      else
+        t
+      end
+    end
+
+    @positions = [Position.new(:cell)]
+    @tiles.each {|t| t.exposed.each {|p| @positions << p}}
+    @positions << Position.new(:boat)
+  end # def
+
+  def to_s
+    layout
+  end
+  
+  def to_str
+    to_s
+  end
+  
+  
+  # For each position, show its name and what it touches
+  def layout
+    out_string = ""
+    @positions.each {|position| out_string << "#{position.symbol}\n"}
+    out_string
+  end
+
+end
+
+
+# Each piece on the board is an object
+class Piece
+  attr_reader :player, :number
+  attr_accessor :position
+
+  def initialize(position, player, number)
+    @position = position
+    @player = player
+    @number = number
+  end
+
+  def to_s
+    "#{@player}:#{@number}"
+  end
+  
+  def to_str
+    to_s
+  end
+
+  def show(convert_to_zero_based_player_number = false)
+    if convert_to_zero_based_player_number
+      "#{@player + 1}:#{@number}"   
+    else
+      "#{@player}:#{@number}"  
+    end
+  end
+  
+  def move_to(new_position)
+    @position = new_position
+  end
+
+end
+
+
+# A move in a game
+class Move
+  attr_reader :piece, :origin, :destination, :card_played
+  
+  def initialize(piece, origin, destination, card_played = :unspecified)
+    @piece = piece
+    @origin = origin
+    @destination = destination
+    @card_played = card_played
+  end
+
+  def show(board, convert_to_zero_based_player_number = false)
+    if @card_played == :unspecified
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)}"
+    else
+      "#{@piece.show(convert_to_zero_based_player_number)}: #{board.positions.index(@origin)} -> #{board.positions.index(@destination)} (#{@card_played})"
+    end
+  end
+  
+  def format(board, convert_to_zero_based_player_number = false)
+    display_player_number = if convert_to_zero_based_player_number then
+      @piece.player + 1
+    else
+      @piece.player
+    end
+    if @card_played ==:unspecified
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)}"
+    else
+      "#{display_player_number} #{board.positions.index(@origin)} #{board.positions.index(@destination)} #{@card_played}"
+    end
+  end
+  
+  # Write a move to a string
+  # Note the inverse, String#to_move, is defined below
+  def to_s
+    "#{@piece.player} #{@origin.to_s}  #{@destination.to_s} #{@card_played}"
+  end
+  
+  def to_str
+    to_s
+  end
+  
+end
+
+
+
+# A class to record each of the states previously found in a game.  
+# Note that this is a deep copy of the pieces and what they've captured, so 
+# you'll need to convert back
+class GameState
+  attr_accessor :move, :player, :board, :pieces_before_move, :deck_before_move, 
+    :players_cards_before_move, :moves_by_current_player
+  #    this_game_state = GameState.new(move, player, @board, @pieces, @deck, @players_cards)
+  
+  
+  def initialize(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    @move, @player, @board, @pieces_before_move, @deck_before_move, 
+      @players_cards_before_move, @moves_by_current_player = 
+      copy_game_state(move, player, board, pieces, deck, players_cards, 
+      moves_by_current_player)
+  end
+  
+  #  def ==(other)
+  #    @move.to_s == other.move.to_s and
+  #      @player == other.player and
+  #      @piece_after_move == other.pieces_after_move
+  #  end
+  
+  def copy_game_state(move, player, board, pieces, deck, players_cards, moves_by_current_player)
+    copy_player = player
+    moving_player = move.piece.player
+
+    copy_board = board.dup
+    copy_board.positions = board.positions.collect {|pos| pos.dup}
+    copy_board.positions.each {|pos| pos.contains = []}
+
+    copy_pieces = pieces.collect do |players_pieces|
+      players_pieces.collect do |piece|
+        new_piece_position = copy_board.positions[board.positions.index(piece.position)]
+        new_piece = Piece.new(new_piece_position, piece.player, piece.number)
+        new_piece_position.contains << new_piece
+        new_piece
+      end
+    end
+
+    piece_index = pieces[moving_player].index(move.piece)
+    origin_index = board.positions.index(move.origin)
+    destination_index = board.positions.index(move.destination)
+    copy_move = Move.new(copy_pieces[moving_player][piece_index], 
+      copy_board.positions[origin_index], 
+      copy_board.positions[destination_index])
+    
+    copy_deck = deck.dup
+    copy_players_cards = players_cards.collect {|p| p.dup}
+    copy_moves_by_current_player = moves_by_current_player
+
+    return copy_move, copy_player, copy_board, copy_pieces, copy_deck, copy_players_cards, copy_moves_by_current_player
+  end
+  
+end
+
+
+# A game of Cartagena.  It keeps a history of all previous states.  
+class Game
+
+  attr_reader :history
+  attr_accessor :current_player
+  attr_reader :players
+  attr_accessor :players_cards
+  attr_accessor :moves_by_current_player
+  attr_reader :board
+  attr_reader :pieces
+  attr_reader :cards
+  attr_accessor :deck
+
+  # Create a new game
+  def initialize(players = 5, number_of_tiles = 6, pieces_each = 6)
+    @board = Board.new(number_of_tiles)
+    @history = []
+    @pieces = []
+    @players = [].fill(0, players) {|i| i}
+    @players_cards = []
+    @current_player = 0
+    @moves_by_current_player = 0
+    @cards = []
+    1.upto($CARDS_PER_SYMBOL) {|x| @cards.concat($SYMBOLS)}
+    @deck = @cards.shuffle
+    @players.each do |p|
+      @pieces[p] = []
+      0.upto(pieces_each - 1) do |count|
+        piece = Piece.new(@board.positions[0], p, count)
+        @pieces[p][count] = piece
+        @board.positions[0].contains << piece
+      end
+      @players_cards[p] = []
+      deal_cards!($INITIAL_CARDS_PER_PLAYER, p)
+    end
+  end
+
+  # Deal some cards to a player.  Remove them from the deck.  Refill the deck
+  # if necessary
+  def deal_cards!(number_of_cards, player = @current_player)
+    1.upto(number_of_cards) do
+      if @deck.empty?
+        @deck = @cards
+        @players_cards.each do |p|
+          p.each do |c|
+            @deck.delete_at(@deck.index(c))
+          end
+        end
+        @deck = @deck.shuffle
+      end
+      @players_cards[player] << @deck.pop
+    end
+  end
+  
+  # Check that a move is valid.  Throw an exception if it's invalid
+  def validate_move(move, player = @current_player)
+    # Check the move is a valid one
+    raise(InvalidMoveError, "Move #{move}: Player #{player} does not exist") unless @players.include?(player)
+    raise(InvalidMoveError, "Move #{move}: None of player #{player}'s pieces on position #{move.origin}") unless move.origin.contains.find {|pc| pc.player == player}
+    raise(InvalidMoveError, "Move #{move}: Origin and destination are the same") if move.origin == move.destination
+  
+    origin_position      = @board.positions.index(move.origin)
+    destination_position = @board.positions.index(move.destination)
+    # Is this move an advance or a retreat?
+    if destination_position > origin_position
+      # Advancing a piece
+      if move.destination == @board.positions[-1] # A move into the boat
+        raise(InvalidMoveError, "Move #{move}: Move into boat and card unspecified") if move.destination == @board.positions[-1] and move.card_played == :unspecified
+        unless @players_cards[player].find {|c| c == move.card_played}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece into the boat")
+        end
+      else
+        if move.card_played != :unspecified and move.destination.symbol != move.card_played
+          raise(InvalidMoveError, "Player #{player} trying to move to #{move.destination}, a #{move.destination.symbol} square with a a #{move.card_played} card")
+        end
+        unless @players_cards[player].find {|c| c == move.destination.symbol}
+          raise(InvalidMoveError, "Player #{player} does not have a card to move a piece to a #{move.destination.symbol} square")
+        end
+        # Check target square is vacant
+        raise(InvalidMoveError, "Advance move #{move}: destination occupied") unless move.destination.contains.empty?
+      end
+      # Check all the intervening squares with this symbol are occupied
+      intervening_empty_position = @board.positions[origin_position...destination_position].index_find do |p| 
+        p.symbol == move.destination.symbol and 
+          p.contains.empty?
+      end
+      raise(InvalidMoveError, "Advance move #{move}: location #{intervening_empty_position} is empty") if intervening_empty_position
+    else
+      # Retreating a piece
+      # Check target position has one or two pieces already on it
+      destination_count = move.destination.contains.length
+      raise(InvalidMoveError, "Retreat move #{move}: destination has no pieces already on it") if destination_count == 0
+      raise(InvalidMoveError, "Retreat move #{move}: destination has too many (#{destination_count}) pieces already on it") if destination_count >= $MAX_PIECES_PER_POSITION
+      # Check none of the intervening squares have any pieces on them
+      # puts "Checking positions #{destination_position} to #{origin_position}"      
+      intervening_target_position = @board.positions[(destination_position + 1)...origin_position].index_find do |p| 
+        # puts "Examining postition #{p} at location #{@board.positions.index(p)} which contains #{p.contains.length} pieces"        
+        p.contains.length > 0 and 
+          p.contains.length < $MAX_PIECES_PER_POSITION
+      end
+      raise(InvalidMoveError, "Retreat move #{move.show(@board)}: location #{intervening_target_position} is a viable target") if intervening_target_position
+    end
+  end
+  
+  # Apply a single move to a game.  
+  def apply_move!(move, player = @current_player, validate = true)
+    
+    validate_move(move, player) if validate
+
+    raise(InvalidMoveError, "Too many consecutive moves by #{@current_player}: has taken #{@moves_by_current_player} and validate is #{validate}") if validate and player == @current_player and @moves_by_current_player >= $MAX_MOVES_PER_TURN
+       
+    # Record the old state
+    this_game_state = GameState.new(move, @current_player, @board, @pieces, @deck, @players_cards, @moves_by_current_player)
+    @history << this_game_state
+    
+    # Apply this move
+    move.origin.contains.delete move.piece
+    move.destination.contains << move.piece
+    move.piece.position = move.destination
+
+    if player == @current_player
+      @moves_by_current_player += 1
+    else
+      @current_player = player
+      @moves_by_current_player = 1
+    end
+   
+    # Update cards
+    if @board.positions.index(move.destination) > @board.positions.index(move.origin)
+      # Advance move
+      if validate
+        card_to_remove = if move.card_played != :unspecified
+          move.card_played
+        else
+          move.destination.symbol
+        end
+        if @players_cards[player].include?(card_to_remove)
+          @players_cards[player].delete_at(@players_cards[player].index(card_to_remove))
+        end
+      end
+    else
+      # Retreat move
+      deal_cards!(move.destination.contains.length - 1, player)
+    end
+    
+    # If this player has all their pieces in the boat, declare a win.
+    if @pieces[player].all? {|pc| pc.position == @board.positions[-1]}
+      raise(GameWonNotice, "Game won by #{player}")
+    end
+  end
+  
+  # Undo a move 
+  def undo_move!
+    state_to_restore = @history[-1]
+    move, @current_player, @board, @pieces, @deck, @players_cards, 
+      @moves_by_current_player = 
+      state_to_restore.copy_game_state(state_to_restore.move, 
+      state_to_restore.player, 
+      state_to_restore.board, 
+      state_to_restore.pieces_before_move, 
+      state_to_restore.deck_before_move, 
+      state_to_restore.players_cards_before_move, 
+      state_to_restore.moves_by_current_player)
+    @history.pop    
+  end
+    
+  # Apply a list of moves in order
+  def apply_moves!(moves)
+    moves.each do |move| 
+      @current_player = move.piece.player
+      apply_move!(move, @current_player)
+      next_player! if @moves_by_current_player >= $MOVES_PER_TURN
+    end
+  end
+
+
+  # Set the current player to be the next player
+  def next_player!
+    if @current_player == @players[-1]
+      @current_player = @players[0]
+    else
+      @current_player = @players[@players.index(@current_player) + 1]
+    end
+    @moves_by_current_player = 0
+    @current_player
+  end
+
+  
+  def reset_current_player(new_current_player)
+    @current_player = new_current_player
+    @moves_by_current_player = 0
+  end
+
+  
+  # Return an array of all possible moves from this state, given the active player
+  def possible_moves(player = @current_player)
+    moves = []
+    @pieces[player].each do |piece|
+      # Do a forward move for each card held
+      unless piece.position == @board.positions[-1]
+        @players_cards[player].each do |card|
+          destination = @board.positions[@board.positions.index(piece.position)..-1].find do |pos|
+            (pos.symbol == card and pos.contains == []) or
+              pos.symbol == :boat
+          end
+          # puts "Player #{player}, card #{card}, piece #{piece.number} at position #{@board.positions.index(piece.position)} to #{@board.positions.index(destination)}, a #{destination.symbol}"        
+          moves << Move.new(piece, piece.position, destination, card)
+        end
+      end
+      # Do a reverse move for the piece
+      unless piece.position == board.positions[0]
+        destination = @board.positions[1...@board.positions.index(piece.position)].reverse.find do |pos|
+          pos.contains.length == 1 or pos.contains.length == 2
+        end
+        if destination
+          # puts "Player #{player}, piece #{piece.number} at position #{@board.positions.index(piece.position)} retreats to #{@board.positions.index(destination)}, a #{destination.symbol} containing #{destination.contains.length} pieces"
+          moves << Move.new(piece, piece.position, destination)
+        end
+      end
+    end
+    # moves.each {|m| puts m.show(@board)}    
+    moves
+  end
+
+  def build_state_string
+    outstr = "Current player = #{@current_player}\n"
+    0.upto((@board.positions.length)-1) do |i|
+      outstr << "#{i}: #{@board.positions[i].symbol}: "
+      @board.positions[i].contains.each do |piece|
+        outstr << "P#{piece.player}:#{piece.number} "
+      end
+      outstr << "\n"
+    end
+    0.upto((@players.length)-1) do |i|
+      outstr << "Player #{i} holds " << (@players_cards[i].sort_by {|c| c.to_s}).join(', ') << "\n"
+    end
+    outstr << "Deck holds " << @deck.join(', ') << "\n"
+    outstr
+  end
+  
+  # Show the state of the board
+  def show_state
+    puts build_state_string
+  end
+
+  def to_s
+    show_state
+  end
+  
+  def to_str
+    to_s
+  end
+  
+
+  def set_testing_game!
+    srand 1234
+    @board = nil
+    initialize(5, 6, 6)
+    board_symbols = [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat]
+    board_symbols.each_index do |i|
+        @board.positions[i].symbol = board_symbols[i]
+      end
+      
+    @players_cards[0] = [:gun,    :hat,   :skull]
+    @players_cards[1] = [:bottle, :keys,  :keys]
+    @players_cards[2] = [:bottle, :hat,   :keys]
+    @players_cards[3] = [:bottle, :skull, :skull]
+    @players_cards[4] = [:bottle, :gun,   :gun]
+    
+    @deck = [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull]
+    
+  end
+  
+  # Given a set of lines from an input file, turn them into a Game object and 
+  # a set of Move objects.
+  # Note the multiple return values.
+  # Class method
+  def Game.read_game(gamelines)
+    gamelines.each {|l| l.chomp!}
+    game = Game.new(gamelines[0].to_i, 6, 6)
+    
+    # create the board
+    2.upto(38) do |i|
+      game.board.positions[i-1] = Position.new(gamelines[i].to_sym)
+    end
+    
+    # read all the moves so far
+    moves = []
+    i = 39
+    current_player_moves = 0
+    current_player = 0
+    player_card_counts = game.players_cards.map {|p| $INITIAL_CARDS_PER_PLAYER}
+    unused_cards = game.cards.map {|c| c}
+    while gamelines[i].in_move_format?
+      move = gamelines[i].to_move(game, true)
+      if move.piece.player == current_player
+        current_player_moves += 1
+      else
+        current_player = move.piece.player
+        current_player_moves = 1
+      end
+      # if move is an advance move, decrement the cards held by the player and remove the card from unused cards
+      #   throw an error if an advance move is made when all the cards of that type have been used
+      #   if unused_cards becomes empty, restock it.  
+      if game.board.positions.index(move.destination) > game.board.positions.index(move.origin)
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move with no cards in hand.  Move is #{gamelines[i]} on line #{i}") if player_card_counts[move.piece.player] == 0
+        card_used = move.destination.symbol
+        if card_used == :boat
+          card_used = move.card_played
+        end
+        raise(InvalidMoveError, "Attempting to move into boat without a card specified") if card_used == :unspecified
+        raise(InvalidMoveError, "Game reading: Player #{move.piece.player} attempting an advance move to a #{move.destination.symbol} place when all those cards have been used. Move is #{gamelines[i]} on line #{i}") unless unused_cards.include? card_used
+        player_card_counts[current_player] -= 1
+        unused_cards.delete_at(unused_cards.index(card_used))
+      # if move is a retreat move, increment the cards held by the player
+      else
+        player_card_counts[current_player] += move.destination.contains.length
+        if unused_cards.length == player_card_counts.inject {|sum, n| sum + n }
+          unused_cards = game.cards.map {|c| c}
+        end
+      end
+      game.apply_move!(move, move.piece.player, false)
+      moves << move
+      i = i + 1
+    end
+    
+    # note the current player
+    game.current_player = gamelines[i].to_i - 1
+    if game.current_player == current_player
+      game.moves_by_current_player = current_player_moves
+    else
+      game.moves_by_current_player = 0
+    end
+    current_player = game.current_player
+    
+    # read the cards
+    game.players_cards = game.players_cards.map {|c| []}
+    (i+1).upto(gamelines.length - 1) do |j|
+      game.players_cards[game.current_player] << gamelines[j].to_sym
+      raise(InvalidMoveError, "Player #{game.current_player} given a #{gamelines[j]} card, but none left in deck") unless unused_cards.index(gamelines[j].to_sym)
+      unused_cards.delete_at(unused_cards.index(gamelines[j].to_sym))
+    end
+    raise(InvalidMoveError, "Player #{game.current_player} given #{game.players_cards[game.current_player].length} cards, but should have #{player_card_counts[game.current_player]} cards from play") if game.players_cards[game.current_player].length != player_card_counts[game.current_player]
+    
+    # Update the game deck
+    game.deck = unused_cards.shuffle
+    player_card_counts[game.current_player] = 0
+    player_card_counts.each_index do |player|
+      if player_card_counts[player] > 0
+        game.deal_cards!(player_card_counts[player], player)
+      end
+    end
+    return game, moves
+  end
+
+end
+  
+
+# Extension to String class to convert a move-description string into a Move object.  
+# This is the inverse of the Move#to_s method
+class String
+  def in_move_format?
+    elements = split
+    return ((elements.length == 3 or elements.length == 4) and
+        elements[0].to_i > 0 and elements[0].to_i < 6 and
+        elements[1].to_i >= 0 and elements[1].to_i <= 37 and
+        elements[2].to_i >= 0 and elements[2].to_i <= 37)
+  end
+  
+  def to_move(game, convert_to_zero_based_player_number = false)
+    move_elements = self.downcase.split
+    player = move_elements[0].to_i
+    player = player - 1 if convert_to_zero_based_player_number
+    origin_index = move_elements[1].to_i
+    destination_index = move_elements[2].to_i
+    raise(InvalidMoveError, "Invalid origin #{origin_index} in move read") unless origin_index >= 0 and origin_index < game.board.positions.length
+    raise(InvalidMoveError, "Invalid destination #{destination_index} in move read") unless destination_index > 0 and destination_index <= game.board.positions.length
+    raise(InvalidMoveError, "Player #{player} does not have a piece on position #{origin_index} in move read") unless game.board.positions[origin_index].contains.any? {|p| p.player == player}
+    piece = game.board.positions[origin_index].contains.find {|p| p.player == player}
+    piece_name = piece.number
+    if destination_index > origin_index
+      if move_elements.length == 4 
+        card_used = move_elements[3].chomp.to_sym
+        raise(InvalidMoveError, "Card used (#{card_used}) does not match destination space (#{game.board.positions[destination_index].symbol})") unless card_used == game.board.positions[destination_index].symbol or destination_index == game.board.positions.length - 1
+        Move.new(game.pieces[player][piece_name], 
+                 game.board.positions[origin_index], 
+                 game.board.positions[destination_index],
+                 card_used)
+      else
+        card_used = :unspecified
+        raise(InvalidMoveError, "Move to boat without specifying card used") if destination_index == game.board.positions.length + 1
+        Move.new(game.pieces[player][piece_name], 
+                 game.board.positions[origin_index], 
+                 game.board.positions[destination_index])
+      end
+    else
+      Move.new(game.pieces[player][piece_name], 
+        game.board.positions[origin_index], 
+        game.board.positions[destination_index])
+    end
+  end
+end
+
+
+# Read a game description file and convert it into a Game object and set of Move objects.
+# Note that Game.read_game method returns multiple values, so this one does too.
+class IO
+  def IO.read_game(filename)
+    gamelines = IO.readlines(filename)
+    return Game.read_game(gamelines)
+  end
+end
+
+# Extension to the Array class to include the Array#shuffle function
+class Array
+  def shuffle
+    sort_by { rand }
+  end
+  
+  def index_find(&block)
+    found = find(&block)
+    if found
+      index found
+    else
+      found
+    end
+  end
+end
diff --git a/lib/main.rb b/lib/main.rb
new file mode 100644 (file)
index 0000000..bf9b84d
--- /dev/null
@@ -0,0 +1,6 @@
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+puts "Hello World"
diff --git a/lib/random-move.rb b/lib/random-move.rb
new file mode 100755 (executable)
index 0000000..53f6c5f
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/ruby
+#
+# == Synopsis
+#
+# Play one move of a Cartagena game
+# 
+# == Usage
+# clockwise-p1 
+#  Game state file read on STDIN
+#  Move produced on STDOUT
+#
+# == Author
+# Neil Smith
+
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+require 'libcartagena'
+require 'rdoc/usage'
+
+gamelines = readlines
+game, moves = Game.read_game(gamelines)
+possibles = game.possible_moves
+move = possibles[rand(possibles.length)]
+puts move.format(game.board, true)
diff --git a/nbproject/.svn/all-wcprops b/nbproject/.svn/all-wcprops
new file mode 100644 (file)
index 0000000..13843cb
--- /dev/null
@@ -0,0 +1,17 @@
+K 25
+svn:wc:ra_dav:version-url
+V 47
+/svn/njae/!svn/ver/33/cartagena/trunk/nbproject
+END
+project.properties
+K 25
+svn:wc:ra_dav:version-url
+V 66
+/svn/njae/!svn/ver/25/cartagena/trunk/nbproject/project.properties
+END
+project.xml
+K 25
+svn:wc:ra_dav:version-url
+V 59
+/svn/njae/!svn/ver/25/cartagena/trunk/nbproject/project.xml
+END
diff --git a/nbproject/.svn/dir-prop-base b/nbproject/.svn/dir-prop-base
new file mode 100644 (file)
index 0000000..a7ce626
--- /dev/null
@@ -0,0 +1,6 @@
+K 10
+svn:ignore
+V 8
+private
+
+END
diff --git a/nbproject/.svn/entries b/nbproject/.svn/entries
new file mode 100644 (file)
index 0000000..82adaef
--- /dev/null
@@ -0,0 +1,52 @@
+8
+
+dir
+65
+http://scripts.njae.me.uk/svn/njae/cartagena/trunk/nbproject
+http://scripts.njae.me.uk/svn/njae
+
+
+
+2008-09-11T15:29:31.198093Z
+33
+neil
+has-props
+
+svn:special svn:externals svn:needs-lock
+
+
+
+
+
+
+
+
+
+
+
+0ada9808-85ab-4924-adda-5bf89eae3818
+\f
+project.properties
+file
+
+
+
+
+2008-08-19T15:48:21.000000Z
+35e60cf760ae7ae20c4dbd368bbaa70c
+2008-06-12T10:24:34.892518Z
+25
+neil
+\f
+project.xml
+file
+
+
+
+
+2008-08-19T15:48:21.000000Z
+7d61aa960131423aec0855b658116b4a
+2008-06-12T10:24:34.892518Z
+25
+neil
+\f
diff --git a/nbproject/.svn/format b/nbproject/.svn/format
new file mode 100644 (file)
index 0000000..45a4fb7
--- /dev/null
@@ -0,0 +1 @@
+8
diff --git a/nbproject/.svn/text-base/project.properties.svn-base b/nbproject/.svn/text-base/project.properties.svn-base
new file mode 100644 (file)
index 0000000..afee2e6
--- /dev/null
@@ -0,0 +1,4 @@
+main.file=main.rb\r
+source.encoding=UTF-8\r
+src.dir=lib\r
+test.src.dir=test\r
diff --git a/nbproject/.svn/text-base/project.xml.svn-base b/nbproject/.svn/text-base/project.xml.svn-base
new file mode 100644 (file)
index 0000000..4646cb9
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project xmlns="http://www.netbeans.org/ns/project/1">\r
+    <type>org.netbeans.modules.ruby.rubyproject</type>\r
+    <configuration>\r
+        <data xmlns="http://www.netbeans.org/ns/ruby-project/1">\r
+            <name>Cartagena</name>\r
+            <source-roots>\r
+                <root id="src.dir"/>\r
+            </source-roots>\r
+            <test-roots>\r
+                <root id="test.src.dir"/>\r
+            </test-roots>\r
+        </data>\r
+    </configuration>\r
+</project>\r
diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml
new file mode 100644 (file)
index 0000000..b27cee1
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
+    <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
+    <open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/1">
+        <file>file:/home/neil/programming/ruby/Cartagena/lib/cgi/cartagena-simple.rb</file>
+    </open-files>
+</project-private>
diff --git a/nbproject/private/rake-d.txt b/nbproject/private/rake-d.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/nbproject/project.properties b/nbproject/project.properties
new file mode 100644 (file)
index 0000000..afee2e6
--- /dev/null
@@ -0,0 +1,4 @@
+main.file=main.rb\r
+source.encoding=UTF-8\r
+src.dir=lib\r
+test.src.dir=test\r
diff --git a/nbproject/project.xml b/nbproject/project.xml
new file mode 100644 (file)
index 0000000..4646cb9
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project xmlns="http://www.netbeans.org/ns/project/1">\r
+    <type>org.netbeans.modules.ruby.rubyproject</type>\r
+    <configuration>\r
+        <data xmlns="http://www.netbeans.org/ns/ruby-project/1">\r
+            <name>Cartagena</name>\r
+            <source-roots>\r
+                <root id="src.dir"/>\r
+            </source-roots>\r
+            <test-roots>\r
+                <root id="test.src.dir"/>\r
+            </test-roots>\r
+        </data>\r
+    </configuration>\r
+</project>\r
diff --git a/test-game-play b/test-game-play
new file mode 100644 (file)
index 0000000..c3998b1
--- /dev/null
@@ -0,0 +1,135 @@
+5
+cell
+gun
+keys
+dagger
+hat
+skull
+bottle
+keys
+dagger
+skull
+hat
+gun
+bottle
+dagger
+bottle
+keys
+gun
+hat
+skull
+dagger
+skull
+bottle
+gun
+keys
+hat
+hat
+dagger
+keys
+bottle
+gun
+skull
+keys
+bottle
+skull
+dagger
+hat
+gun
+boat
+1 0 5
+1 0 4
+1 5 4
+2 0 2
+2 0 7
+2 7 4
+3 0 7
+3 0 10
+3 0 6
+4 0 12
+4 12 10
+4 0 5
+5 0 12
+5 12 10
+5 0 3
+1 0 9
+1 0 1
+1 9 7
+2 0 12
+2 0 14
+2 12 7
+3 6 5
+3 0 11
+3 11 5
+4 0 9
+4 0 18
+4 18 14
+5 0 11
+5 0 16
+5 16 14
+1 0 15
+1 15 11
+1 11 9
+2 0 15
+2 0 17
+2 17 15
+3 0 23
+3 23 15
+3 0 17
+4 10 9
+4 0 24
+4 0 8
+5 0 16
+5 16 11
+5 11 10
+1 0 18
+1 7 12
+1 12 11
+2 14 11
+2 2 23
+2 15 14
+3 17 15
+3 5 27
+3 5 13
+4 8 7
+4 5 17
+4 7 12
+5 0 8
+5 8 7
+5 3 31
+1 18 17
+1 1 16
+1 17 16
+2 4 37 keys
+2 7 4
+2 4 37 keys
+3 7 21
+3 13 12
+3 10 13
+4 17 16
+4 9 28
+4 9 18
+5 7 37 keys
+5 11 10
+5 10 32
+1 11 10
+1 4 8
+1 4 17
+2 11 37 keys
+2 14 37 keys
+2 15 37 keys
+3 13 12
+3 12 22
+3 12 25
+4 16 15
+4 12 35
+4 14 29
+5 10 20
+5 10 30
+5 14 36
+1 17 16
+1 8 33 skull
+1 9 17 hat
+2
+dagger
+hat
\ No newline at end of file
diff --git a/test-game-play-to-win b/test-game-play-to-win
new file mode 100644 (file)
index 0000000..e5b864f
--- /dev/null
@@ -0,0 +1,135 @@
+5
+cell
+gun
+keys
+dagger
+hat
+skull
+bottle
+keys
+dagger
+skull
+hat
+gun
+bottle
+dagger
+bottle
+keys
+gun
+hat
+skull
+dagger
+skull
+bottle
+gun
+keys
+hat
+hat
+dagger
+keys
+bottle
+gun
+skull
+keys
+bottle
+skull
+dagger
+hat
+gun
+boat
+1 0 5
+1 0 4
+1 5 4
+2 0 2
+2 0 7
+2 7 4
+3 0 7
+3 0 10
+3 0 6
+4 0 12
+4 12 10
+4 0 5
+5 0 12
+5 12 10
+5 0 3
+1 0 9
+1 0 1
+1 9 7
+2 0 12
+2 0 14
+2 12 7
+3 6 5
+3 0 11
+3 11 5
+4 0 9
+4 0 18
+4 18 14
+5 0 11
+5 0 16
+5 16 14
+1 0 15
+1 15 11
+1 11 9
+2 0 15
+2 0 17
+2 17 15
+3 0 23
+3 23 15
+3 0 17
+4 10 9
+4 0 24
+4 0 8
+5 0 16
+5 16 11
+5 11 10
+1 0 18
+1 7 12
+1 12 11
+2 14 11
+2 2 23
+2 15 14
+3 17 15
+3 5 27
+3 5 13
+4 8 7
+4 5 17
+4 7 12
+5 0 8
+5 8 7
+5 3 31
+1 18 17
+1 1 16
+1 17 16
+2 4 37 keys
+2 7 4
+2 4 37 keys
+3 7 21
+3 13 12
+3 10 13
+4 17 16
+4 9 28
+4 9 18
+5 7 37 keys
+5 11 10
+5 10 32
+1 11 10
+1 4 8
+1 4 17
+2 11 37 keys
+2 14 37 keys
+2 15 37 keys
+3 13 12
+3 12 22
+3 12 25
+4 16 15
+4 12 35
+4 14 29
+5 10 20
+5 10 30
+5 14 36
+1 17 16
+1 8 33 skull
+1 9 17 hat
+2 23 37 hat
+2
+dagger
\ No newline at end of file
diff --git a/test-load-file-1 b/test-load-file-1
new file mode 100644 (file)
index 0000000..5fc420f
--- /dev/null
@@ -0,0 +1,53 @@
+3
+cell
+skull
+hat
+keys
+gun
+bottle
+dagger
+hat
+gun
+keys
+skull
+bottle
+dagger
+dagger
+gun
+hat
+bottle
+skull
+keys
+hat
+keys
+bottle
+gun
+skull
+keys
+keys
+skull
+hat
+bottle
+gun
+dagger
+bottle
+skull
+gun
+dagger
+keys
+hat
+boat
+1 0 4
+1 0 3
+1 4 3
+2 0 4
+2 4 3
+2 0 2
+3 0 5
+3 5 2
+3 0 4
+1 0 5
+2
+hat
+skull
+hat
\ No newline at end of file
diff --git a/test-load-file-2 b/test-load-file-2
new file mode 100644 (file)
index 0000000..51654c6
--- /dev/null
@@ -0,0 +1,43 @@
+3
+cell
+skull
+hat
+keys
+gun
+bottle
+dagger
+hat
+gun
+keys
+skull
+bottle
+dagger
+dagger
+gun
+hat
+bottle
+skull
+keys
+hat
+keys
+bottle
+gun
+skull
+keys
+keys
+skull
+hat
+bottle
+gun
+dagger
+bottle
+skull
+gun
+dagger
+keys
+hat
+boat
+1
+hat
+skull
+hat
\ No newline at end of file
diff --git a/test-load-file-3 b/test-load-file-3
new file mode 100644 (file)
index 0000000..8835d12
--- /dev/null
@@ -0,0 +1,43 @@
+5\r
+cell\r
+skull\r
+hat\r
+keys\r
+gun\r
+bottle\r
+dagger\r
+hat\r
+gun\r
+keys\r
+skull\r
+bottle\r
+dagger\r
+dagger\r
+gun\r
+hat\r
+bottle\r
+skull\r
+keys\r
+hat\r
+keys\r
+bottle\r
+gun\r
+skull\r
+keys\r
+keys\r
+skull\r
+hat\r
+bottle\r
+gun\r
+dagger\r
+bottle\r
+skull\r
+gun\r
+dagger\r
+keys\r
+hat\r
+boat\r
+1 0 4\r
+1 0 5\r
+1\r
+hat
\ No newline at end of file
diff --git a/test/.svn/all-wcprops b/test/.svn/all-wcprops
new file mode 100644 (file)
index 0000000..2c02335
--- /dev/null
@@ -0,0 +1,11 @@
+K 25
+svn:wc:ra_dav:version-url
+V 42
+/svn/njae/!svn/ver/56/cartagena/trunk/test
+END
+libcartagena_test.rb
+K 25
+svn:wc:ra_dav:version-url
+V 63
+/svn/njae/!svn/ver/56/cartagena/trunk/test/libcartagena_test.rb
+END
diff --git a/test/.svn/entries b/test/.svn/entries
new file mode 100644 (file)
index 0000000..7a70690
--- /dev/null
@@ -0,0 +1,39 @@
+8
+
+dir
+65
+http://scripts.njae.me.uk/svn/njae/cartagena/trunk/test
+http://scripts.njae.me.uk/svn/njae
+
+
+
+2008-12-05T21:50:45.570571Z
+56
+neil
+
+
+svn:special svn:externals svn:needs-lock
+
+
+
+
+
+
+
+
+
+
+
+0ada9808-85ab-4924-adda-5bf89eae3818
+\f
+libcartagena_test.rb
+file
+
+
+
+
+2008-12-21T01:02:45.000000Z
+bc3d0bf47514e1c2583326dda72433f3
+2008-12-05T21:50:45.570571Z
+56
+\f
diff --git a/test/.svn/format b/test/.svn/format
new file mode 100644 (file)
index 0000000..45a4fb7
--- /dev/null
@@ -0,0 +1 @@
+8
diff --git a/test/.svn/text-base/libcartagena_test.rb.netbeans-base b/test/.svn/text-base/libcartagena_test.rb.netbeans-base
new file mode 100644 (file)
index 0000000..1cab850
--- /dev/null
@@ -0,0 +1,2065 @@
+# 
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+$:.unshift File.join(File.dirname(__FILE__),'..','lib', 'libcartagena')
+
+require 'test/unit'
+require File.join(File.dirname(__FILE__), '..', 'lib', 'libcartagena')
+
+class TileTest < Test::Unit::TestCase
+  def test_tile_init
+    front = [:hat, :keys, :gun, :bottle, :skull, :dagger]
+    back  = [:gun, :hat, :dagger, :skull, :bottle, :keys]
+    assert_nothing_raised { Tile.new(front, back) }
+    tile = Tile.new(front, back) 
+    
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front, tile.exposed.map {|p| p.symbol}
+    
+    tile.flip!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal back,  tile.exposed.map {|p| p.symbol}
+    tile.flip!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front, tile.exposed.map {|p| p.symbol}
+    
+    tile.flip!
+    tile.reverse!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal back.reverse, tile.exposed.map {|p| p.symbol}
+    
+    tile.flip!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front, tile.exposed.map {|p| p.symbol}
+    
+    tile.reverse!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front.reverse, tile.exposed.map {|p| p.symbol}
+    
+  end
+end
+
+class BoardTest < Test::Unit::TestCase
+  def test_board_init
+    assert_nothing_raised { Board.new(6) }
+    board = Board.new(1)
+    assert_equal 1, board.tiles.length
+    assert_equal 8, board.positions.length
+
+    board = Board.new(6)
+    assert_equal 6, board.tiles.length
+    assert_equal 38, board.positions.length
+
+    0.upto(5) do |i|
+      start = i * 6 + 1
+      slice = board.positions[start..(start+5)]
+      $SYMBOLS.each do |tile|
+        assert_equal 1, (slice.find_all {|p| p.symbol == tile}).length
+      end
+    end
+  end
+end
+
+
+class GameTest < Test::Unit::TestCase
+  def test_game_init
+    assert_nothing_raised { Game.new }
+    
+    game = Game.new(6)
+    assert_equal 6, game.players.length
+    0.upto(5) do |i|
+      assert_equal $INITIAL_CARDS_PER_PLAYER, game.players_cards[i].length
+    end
+    assert_equal $SYMBOLS.length * $CARDS_PER_SYMBOL - 6 * $INITIAL_CARDS_PER_PLAYER, game.deck.length
+  end
+  
+  def test_game_setting
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+#    assert_equal [:bottle, :keys,  :skull], game.players_cards[5].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 18, game.possible_moves.length
+    game.apply_move!(game.possible_moves[0])
+    assert_equal [:hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 87, game.deck.length
+
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 12, game.possible_moves.length
+    game.apply_move!(game.possible_moves[3])
+    assert_equal [:hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 5, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 87, game.deck.length
+
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 7, game.possible_moves.length
+    game.apply_move!(game.possible_moves[2])
+    assert_equal [:hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 86, game.deck.length
+      
+    game.reset_current_player 0  # Needed to prevent errors below
+      
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 12, game.possible_moves.length
+    game.apply_move!(game.possible_moves[-1])
+    assert_equal [:hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 5, game.board.positions.index(game.pieces[0][5].position)
+    assert_equal 86, game.deck.length
+
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 7, game.possible_moves.length
+    game.apply_move!(game.possible_moves[-1])
+    assert_equal [:bottle, :hat, :keys], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][5].position)
+    assert_equal 84, game.deck.length
+  end
+  
+  def test_game_termination
+    game = Game.new(6)
+    game.set_testing_game!
+    assert_equal 0, game.moves_by_current_player
+
+    game.apply_move!(Move.new(game.pieces[0][0], game.board.positions[0], game.board.positions[36]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[0].contains.include?(game.pieces[0][1])
+    assert game.board.positions[0].contains.include?(game.pieces[0][2])
+    assert game.board.positions[0].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 18, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][1], game.board.positions[0], game.board.positions[35]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[0].contains.include?(game.pieces[0][2])
+    assert game.board.positions[0].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 19, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][2], game.board.positions[0], game.board.positions[34]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[0].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 20, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][3], game.board.positions[0], game.board.positions[33]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 21, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][4], game.board.positions[0], game.board.positions[32]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[32].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 22, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][5], game.board.positions[0], game.board.positions[31]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[32].contains.include?(game.pieces[0][4])
+    assert game.board.positions[31].contains.include?(game.pieces[0][5])
+    assert_equal 23, game.possible_moves.length
+    
+    # Now start moving pices onto the boat
+    game.apply_move!(Move.new(game.pieces[0][5], game.board.positions[31], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[32].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 20, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][4], game.board.positions[32], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 17, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][3], game.board.positions[33], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[37].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 14, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][2], game.board.positions[34], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[37].contains.include?(game.pieces[0][2])
+    assert game.board.positions[37].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 11, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][1], game.board.positions[35], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[37].contains.include?(game.pieces[0][1])
+    assert game.board.positions[37].contains.include?(game.pieces[0][2])
+    assert game.board.positions[37].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 8, game.possible_moves.length
+
+    assert_raise(GameWonNotice) {game.apply_move!(Move.new(game.pieces[0][0], game.board.positions[36], game.board.positions[37]), 0, false)}
+  
+  end
+
+  def test_retreats
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+#    assert_equal [:bottle, :keys,  :skull], game.players_cards[5].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 18, game.possible_moves.length
+    game.apply_move!(game.possible_moves[0])
+    assert_equal [:hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 87, game.deck.length
+
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 12, game.possible_moves.length
+    game.apply_move!(game.possible_moves[3])
+    assert_equal [:hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 5, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 87, game.deck.length
+
+    # Test can't retreat into the cell
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 7, game.possible_moves.length
+    assert_raise(InvalidMoveError) do
+      game.apply_move!(Move.new(game.pieces[0][1], game.board.positions[5], game.board.positions[0]))
+    end
+  end
+  # Test invalid move error trapping
+  def test_invalid_move_error_trapping
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    assert_equal 0, game.moves_by_current_player
+    
+    # Test if can move to a space without the right card
+    assert_raise(InvalidMoveError) {game.apply_move!(Move.new(game.pieces[0][0], game.board.positions[2], game.board.positions[0]))}
+    
+    
+    # Apply a series of moves to get an interesting game state
+    game.apply_move!('0 0 5'.to_move(game))
+      assert_equal [:gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 0 4'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 5 4'.to_move(game))
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 0 2'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 0 7'.to_move(game))
+      assert_equal [:bottle], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 0 7'.to_move(game))
+      assert_equal [:bottle, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 10'.to_move(game))
+      assert_equal [:bottle], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 6'.to_move(game))
+      assert_equal [], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 0 12'.to_move(game))
+      assert_equal [:skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 12 10'.to_move(game))
+      assert_equal [:skull, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 5'.to_move(game))
+      assert_equal [:skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 12'.to_move(game))
+      assert_equal [:gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 12 10'.to_move(game))
+      assert_equal [:dagger, :gun, :gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 0 3'.to_move(game))
+      assert_equal [:gun, :gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal [], game.players_cards[2].sort_by {|c| c.to_s}
+      assert_equal [:skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+      assert_equal [:gun, :gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 0 9'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 0 1'.to_move(game))
+      assert_equal [], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 9 7'.to_move(game))
+      assert_equal [:keys], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 0 12'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 0 14'.to_move(game))
+      assert_equal [:keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 12 7'.to_move(game))
+      assert_equal [:hat, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 6 5'.to_move(game))
+      assert_equal [:gun], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 11'.to_move(game))
+      assert_equal [], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 11 5'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 0 9'.to_move(game))
+      assert_equal [:skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 18'.to_move(game))
+      assert_equal [], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 18 14'.to_move(game))
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 11'.to_move(game))
+      assert_equal [:gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 0 16'.to_move(game))
+      assert_equal [:gun], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 16 14'.to_move(game))
+      assert_equal [:gun, :keys, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+   
+      assert_equal [:keys], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal [:hat, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal [:bottle, :keys], game.players_cards[2].sort_by {|c| c.to_s}
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+      assert_equal [:gun, :keys, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+  
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 0 15'.to_move(game))
+      assert_equal [], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 15 11'.to_move(game))
+      assert_equal [:bottle], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 11 9'.to_move(game))
+      assert_equal [:bottle, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 0 15'.to_move(game))
+      assert_equal [:hat, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 0 17'.to_move(game))
+      assert_equal [:keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 17 15'.to_move(game))
+      assert_equal [:keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 0 23'.to_move(game))
+      assert_equal [:bottle], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 23 15'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 17'.to_move(game))
+      assert_equal [:bottle, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 10 9'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 24'.to_move(game))
+      assert_equal [:bottle, :dagger], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 8'.to_move(game))
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 16'.to_move(game))
+      assert_equal [:keys, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 16 11'.to_move(game))
+      assert_equal [:keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 11 10'.to_move(game))
+      assert_equal [:dagger, :dagger, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+
+    assert_equal 2, game.board.positions[ 0].contains.length
+    assert_equal 1, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 1, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 3, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 3, game.board.positions[ 7].contains.length
+    assert_equal 1, game.board.positions[ 8].contains.length
+    assert_equal 3, game.board.positions[ 9].contains.length
+    assert_equal 3, game.board.positions[10].contains.length
+    assert_equal 1, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 3, game.board.positions[14].contains.length
+    assert_equal 3, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 1, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 1, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+    
+    assert_raise(InvalidMoveError) {game.apply_move!('0 7 6'.to_move(game))} # can't retreat into an empty space
+    assert_raise(InvalidMoveError) {game.apply_move!('0 7 5'.to_move(game))} # can't retreat into a space with three men
+    assert_raise(InvalidMoveError) {game.apply_move!('0 1 0'.to_move(game))} # can't retreat into the boat
+    assert_raise(InvalidMoveError) {game.apply_move!('0 1 12'.to_move(game))} # can't skip a vacant space of this type
+    assert_raise(InvalidMoveError) {game.apply_move!('0 1 5'.to_move(game))} # can't advance to a space with three pirates
+    assert_raise(InvalidMoveError) {game.apply_move!('0 2 6'.to_move(game))} # can't move another's piece
+    assert_raise(InvalidMoveError) {game.apply_move!('0 9 9'.to_move(game))} # can't advance the same square
+    assert_raise(InvalidMoveError) {game.apply_move!('0 9 20'.to_move(game))} # can't skip a vacant space of this type
+
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 0 18'.to_move(game))
+      assert_equal [:bottle], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 7 12'.to_move(game))
+      assert_equal [], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 12 11'.to_move(game))
+      assert_equal [:dagger], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 14 11'.to_move(game))
+      assert_equal [:dagger, :keys, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 2 23'.to_move(game))
+      assert_equal [:dagger, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 15 14'.to_move(game))
+      assert_equal [:bottle, :bottle, :dagger, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 17 15'.to_move(game))
+      assert_equal [:bottle, :dagger, :dagger, :keys], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 5 27'.to_move(game))
+      assert_equal [:bottle, :dagger, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 5 13'.to_move(game))
+      assert_equal [:bottle, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 8 7'.to_move(game))
+      assert_equal [:bottle, :bottle, :hat], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 5 17'.to_move(game))
+      assert_equal [:bottle, :bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 7 12'.to_move(game))
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 8'.to_move(game))
+      assert_equal [:dagger, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 8 7'.to_move(game))
+      assert_equal [:bottle, :dagger, :keys, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 3 31'.to_move(game))
+      assert_equal [:bottle, :dagger, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+
+
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 18 17'.to_move(game))
+      assert_equal [:dagger, :gun], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 1 16'.to_move(game))
+      assert_equal [:dagger], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 17 16'.to_move(game))
+      assert_equal [:dagger, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 4 37 keys'.to_move(game))
+      assert_equal [:bottle, :bottle, :dagger, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :bottle, :dagger, :hat, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 4 37 keys'.to_move(game))
+      assert_equal [:bottle, :bottle, :bottle, :dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 7 21'.to_move(game))
+      assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 13 12'.to_move(game))
+      assert_equal [:dagger, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 10 13'.to_move(game))
+      assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 17 16'.to_move(game))
+      assert_equal [:bottle, :hat, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 9 28'.to_move(game))
+      assert_equal [:hat, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 9 18'.to_move(game))
+      assert_equal [:hat], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 7 37 keys'.to_move(game))
+      assert_equal [:bottle, :dagger, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 11 10'.to_move(game))
+      assert_equal [:bottle, :dagger, :dagger, :gun, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 10 32'.to_move(game))
+      assert_equal [:dagger, :dagger, :gun, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+
+    
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 11 10'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 4 8'.to_move(game))
+      assert_equal [:bottle, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 4 17'.to_move(game))
+      assert_equal [:bottle, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 11 37 bottle'.to_move(game))
+      assert_equal [:bottle, :bottle, :dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 14 37 bottle'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 15 37 bottle'.to_move(game))
+      assert_equal [:dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 13 12'.to_move(game))
+      assert_equal [:dagger, :gun, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 12 22 gun'.to_move(game))
+      assert_equal [:dagger, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 12 25 hat'.to_move(game))
+      assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 16 15'.to_move(game))
+      assert_equal [:dagger, :gun, :hat], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 12 35 hat'.to_move(game))
+      assert_equal [:dagger, :gun], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 14 29 gun'.to_move(game))
+      assert_equal [:dagger], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 10 20 skull'.to_move(game))
+      assert_equal [:dagger, :dagger, :gun, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 10 30 skull'.to_move(game))
+      assert_equal [:dagger, :dagger, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 14 36 gun'.to_move(game))
+      assert_equal [:dagger, :dagger], game.players_cards[4].sort_by {|c| c.to_s}
+
+    
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 17 16'.to_move(game))
+      assert_equal [:bottle, :gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 8 33 skull'.to_move(game))
+      assert_equal [:bottle, :gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 9 17 hat'.to_move(game))
+      assert_equal [:bottle, :gun], game.players_cards[0].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 1, game.current_player
+    assert_raise(GameWonNotice) {game.apply_move!('1 23 37 hat'.to_move(game))}
+      assert_equal [:dagger], game.players_cards[1].sort_by {|c| c.to_s}
+
+    assert_equal 0, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 0, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 0, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 0, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 1, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 3, game.board.positions[15].contains.length
+    assert_equal 3, game.board.positions[16].contains.length
+    assert_equal 1, game.board.positions[17].contains.length
+    assert_equal 1, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 1, game.board.positions[20].contains.length
+    assert_equal 1, game.board.positions[21].contains.length
+    assert_equal 1, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 1, game.board.positions[24].contains.length
+    assert_equal 1, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 1, game.board.positions[27].contains.length
+    assert_equal 1, game.board.positions[28].contains.length
+    assert_equal 1, game.board.positions[29].contains.length
+    assert_equal 1, game.board.positions[30].contains.length
+    assert_equal 1, game.board.positions[31].contains.length
+    assert_equal 1, game.board.positions[32].contains.length
+    assert_equal 1, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 1, game.board.positions[35].contains.length
+    assert_equal 1, game.board.positions[36].contains.length
+    assert_equal 7, game.board.positions[37].contains.length
+
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun],
+      game.deck
+
+    assert_equal [:bottle, :gun], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:dagger], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:dagger], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:dagger, :dagger], game.players_cards[4].sort_by {|c| c.to_s}
+    
+  end
+  # 
+  # : move without a card
+  # move to an occupied space
+  # retreat to an emtpy space
+  # retreat to an overfull space
+  # retreat to the cell with one pirate already in it
+  
+  
+  # Test current player switching
+
+  # Test undo moves
+  
+  # Test file reading
+  # (need a full game file to read)
+  
+  def test_undo
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+
+    assert_equal 0, game.history.length
+    
+    game.apply_move!('0 0 5'.to_move(game))
+      assert_equal [:gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 1, game.history.length
+    game.apply_move!('0 0 4'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 2, game.history.length
+    game.apply_move!('0 5 4'.to_move(game))
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 3, game.history.length
+
+    game.next_player!
+      assert_equal 1, game.current_player
+      assert_equal 3, game.history.length
+    game.apply_move!('1 0 2'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal 4, game.history.length
+    game.apply_move!('1 0 7'.to_move(game))
+      assert_equal [:bottle], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal 5, game.history.length
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal 6, game.history.length
+      assert_equal 1, game.current_player
+      assert_equal 3, game.moves_by_current_player
+
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 0, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+
+    game.undo_move! # player 1's third move
+    
+    assert_equal 5, game.history.length
+    
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 2, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle],                 game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle], 
+      game.deck
+    assert_equal 1, game.current_player 
+    assert_equal 2, game.moves_by_current_player
+
+    # Re-apply player 1's third move
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 0 7'.to_move(game))
+      assert_equal [:bottle, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 10'.to_move(game))
+      assert_equal [:bottle], game.players_cards[2].sort_by {|c| c.to_s}
+
+    assert_equal 8, game.history.length
+    assert_equal 24, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 1, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle],                 game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+    assert_equal 2, game.current_player 
+    assert_equal 2, game.moves_by_current_player
+    
+    game.undo_move! # player 2's second move
+    
+    assert_equal 7, game.history.length
+    assert_equal 25, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat],           game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+    assert_equal 2, game.current_player 
+    assert_equal 1, game.moves_by_current_player
+    
+    
+    game.undo_move! # Player 2's first move
+    
+    assert_equal 6, game.history.length
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 0, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,    :keys], game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+    assert_equal 2, game.current_player 
+    assert_equal 0, game.moves_by_current_player
+    
+    
+    game.undo_move! # Player 1's third move
+    
+    assert_equal 5, game.history.length
+    
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 2, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle],                 game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle], 
+      game.deck
+    assert_equal 1, game.current_player 
+    assert_equal 2, game.moves_by_current_player
+  
+  end
+
+  def test_apply_moves
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+
+    assert_equal 0, game.history.length
+    
+    game.apply_move!('0 0 5'.to_move(game))
+      assert_equal [:gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 1, game.history.length
+    game.apply_move!('0 0 4'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 2, game.history.length
+    game.apply_move!('0 5 4'.to_move(game))
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 3, game.history.length
+
+    game.next_player!
+      assert_equal 1, game.current_player
+      assert_equal 3, game.history.length
+    game.apply_move!('1 0 2'.to_move(game))
+  end
+  
+  def test_read_game_lines
+    gamelines1 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5 skull", "1 0 4 hat", 
+      "1",
+      "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines1)}
+    game, moves = Game.read_game(gamelines1)
+    assert_equal 2, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 1, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 15, game.deck.length
+    
+    game.apply_move!('0 5 4'.to_move(game))
+    assert_equal 3, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    gamelines2 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", 
+      "2",
+      "dagger", "hat", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines2)}
+    game, moves = Game.read_game(gamelines2)
+    assert_equal 3, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    game.apply_move!('1 0 2'.to_move(game))
+    assert_equal 4, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 2, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    gamelines3 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",
+      "2",
+      "dagger", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines3)}
+    game, moves = Game.read_game(gamelines3)
+    assert_equal 4, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 2, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    game.apply_move!('1 0 7'.to_move(game))
+    assert_equal 5, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 1, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    gamelines4 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",  "2 0 7",
+      "2",
+      "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines4)}
+    game, moves = Game.read_game(gamelines4)
+    assert_equal 5, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 1, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+
+    game.apply_move!('1 7 4'.to_move(game))
+    assert_equal 6, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+    
+    gamelines5 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",  "2 0 7",  "2 7 4",
+      "2",
+      "bottle", "hat", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines5)}
+    game, moves = Game.read_game(gamelines5)
+    assert_equal 6, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+
+    game.players_cards[2] << :keys
+    game.players_cards[2] << :hat
+    game.apply_move!('3 0 7'.to_move(game, true), 2)
+    assert_equal 7, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    game.apply_move!('3 0 10'.to_move(game, true))
+    assert_equal 8, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 2, game.moves_by_current_player   
+    
+    gamelines6 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",  "2 0 7",  "2 7 4",
+      "3",
+      "bottle", "hat", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines6)}
+    game, moves = Game.read_game(gamelines6)
+    assert_equal 6, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+
+    game.apply_move!('2 0 7'.to_move(game))
+    assert_equal 7, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 2, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+
+    game.apply_move!('2 0 10'.to_move(game))
+    assert_equal 8, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 2, game.moves_by_current_player   
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 1, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+    
+    # Need to do a test with a very long sequence of moves, 
+    # to test that the card dealing works properly when the deck is exhausted
+    
+  end
+  
+  def test_dealing
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    0.upto(4) do |i|
+      assert_equal $INITIAL_CARDS_PER_PLAYER, game.players_cards[i].length
+    end
+    assert_equal $SYMBOLS.length * $CARDS_PER_SYMBOL - 5 * $INITIAL_CARDS_PER_PLAYER, game.deck.length
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    game.deal_cards!(86, 0)
+    assert_equal 89, game.players_cards[0].length
+    assert_equal 1,  game.deck.length
+    assert_equal [:hat], game.deck
+
+    game.players_cards[0] = [:hat, :hat, :hat, :hat, :hat]
+    game.players_cards[1] = [:keys, :keys, :keys]
+    game.players_cards[2] = [:bottle, :dagger, :skull]
+    game.players_cards[3] = []
+    game.players_cards[4] = []
+    game.deal_cards!(1, 3)
+    
+    assert_equal [:hat, :hat, :hat, :hat, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:keys, :keys, :keys],          game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :dagger, :skull],     game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:hat],                         game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [],                             game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [], game.deck
+    
+    game.deal_cards!(1, 4)
+    assert_equal [:keys], game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal $CARDS_PER_SYMBOL - 6, game.deck.select {|c| c == :hat}.length
+    assert_equal $CARDS_PER_SYMBOL - 4, game.deck.select {|c| c == :keys}.length
+    assert_equal $CARDS_PER_SYMBOL - 1, game.deck.select {|c| c == :bottle}.length
+    assert_equal $CARDS_PER_SYMBOL - 1, game.deck.select {|c| c == :dagger}.length
+    assert_equal $CARDS_PER_SYMBOL - 1, game.deck.select {|c| c == :skull}.length
+    assert_equal $CARDS_PER_SYMBOL,     game.deck.select {|c| c == :gun}.length
+    assert_equal $CARDS_PER_SYMBOL * 6 - 13, game.deck.length
+
+  end
+  
+  def test_long_deal
+    gamelines0 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1",
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines0)}
+    game, moves = Game.read_game(gamelines0)
+    assert_equal 0, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 3, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 9, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 9, game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines1 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1 gun", "1 0 2 keys", "1 2 1",
+      "2",
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines1)}
+    game, moves = Game.read_game(gamelines1)
+    assert_equal 3, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 10, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 2, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 10, game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 1, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 1, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines2 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1", "1 0 2", "1 2 1",
+      "2 0 2", "2 0 3", "2 3 2",
+      "3 0 3", "3 0 4", "3 4 3",
+      "1 0 4", "1 0 5", "1 5 4",
+      "2 0 5", "2 0 6", "2 6 5",
+      "3 0 6", "3 0 11", "3 11 6",
+      "1",
+      "hat"]
+    assert_nothing_raised {Game.read_game(gamelines2)}
+    game, moves = Game.read_game(gamelines2)
+    assert_equal 18, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 1, game.players_cards[0].length
+    assert_equal 1, game.players_cards[1].length
+    assert_equal 1, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 6), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 12, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 6), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines2 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1",
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines2)}
+    game, moves = Game.read_game(gamelines2)
+    assert_equal 30, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 3, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 18), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 18), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines3 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3",
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines3)}
+    game, moves = Game.read_game(gamelines3)
+    assert_equal 42, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 5, game.players_cards[0].length
+    assert_equal 5, game.players_cards[1].length
+    assert_equal 5, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 30), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 24, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 30), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines4 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", "hat"]
+    assert_nothing_raised {Game.read_game(gamelines4)}
+    game, moves = Game.read_game(gamelines4)
+    assert_equal 54, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 7, game.players_cards[0].length
+    assert_equal 7, game.players_cards[1].length
+    assert_equal 7, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 42), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 30, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 42), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :skull}).length
+
+    
+    gamelines5 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", "hat", "skull", "bottle", "gun", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines5)}
+    game, moves = Game.read_game(gamelines5)
+    assert_equal 78, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 11, game.players_cards[0].length
+    assert_equal 11, game.players_cards[1].length
+    assert_equal 11, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 66), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 42, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 66), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines6 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines6)}
+    game, moves = Game.read_game(gamelines6)
+    assert_equal 102, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 90), game.deck.length
+    assert_equal 3, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 54, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 90), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines7 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines7)}
+    game, moves = Game.read_game(gamelines7)
+    assert_equal 104, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 16, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 55, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines8 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "skull", "bottle", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines8)}
+    game, moves = Game.read_game(gamelines8)
+    assert_equal 105, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 56, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 10,  (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :skull}).length
+
+
+    gamelines9 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 12", "2 12 10",
+      "2",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines9)}
+    game, moves = Game.read_game(gamelines9)
+    assert_equal 107, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal 0, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 56, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 10,  (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10,  (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :skull}).length
+
+    
+  end
+  
+end
\ No newline at end of file
diff --git a/test/.svn/text-base/libcartagena_test.rb.svn-base b/test/.svn/text-base/libcartagena_test.rb.svn-base
new file mode 100644 (file)
index 0000000..7825154
--- /dev/null
@@ -0,0 +1,2920 @@
+# == Synopsis
+#
+# Unit tests for the libcartagena library
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  5 Dec 2008
+# =>             * Initial build
+
+
+$:.unshift File.join(File.dirname(__FILE__),'..','lib', 'libcartagena')
+
+require 'test/unit'
+require File.join(File.dirname(__FILE__), '..', 'lib', 'libcartagena')
+
+class TileTest < Test::Unit::TestCase
+  def test_tile_init
+    front = [:hat, :keys, :gun, :bottle, :skull, :dagger]
+    back  = [:gun, :hat, :dagger, :skull, :bottle, :keys]
+    assert_nothing_raised { Tile.new(front, back) }
+    tile = Tile.new(front, back) 
+    
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front, tile.exposed.map {|p| p.symbol}
+    
+    tile.flip!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal back,  tile.exposed.map {|p| p.symbol}
+    tile.flip!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front, tile.exposed.map {|p| p.symbol}
+    
+    tile.flip!
+    tile.reverse!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal back.reverse, tile.exposed.map {|p| p.symbol}
+    
+    tile.flip!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front, tile.exposed.map {|p| p.symbol}
+    
+    tile.reverse!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front.reverse, tile.exposed.map {|p| p.symbol}
+    
+  end
+end
+
+class BoardTest < Test::Unit::TestCase
+  def test_board_init
+    assert_nothing_raised { Board.new(6) }
+    board = Board.new(1)
+    assert_equal 1, board.tiles.length
+    assert_equal 8, board.positions.length
+
+    board = Board.new(6)
+    assert_equal 6, board.tiles.length
+    assert_equal 38, board.positions.length
+
+    0.upto(5) do |i|
+      start = i * 6 + 1
+      slice = board.positions[start..(start+5)]
+      $SYMBOLS.each do |tile|
+        assert_equal 1, (slice.find_all {|p| p.symbol == tile}).length
+      end
+    end
+  end
+end
+
+
+class GameTest < Test::Unit::TestCase
+  def test_game_init
+    assert_nothing_raised { Game.new }
+    
+    game = Game.new(6)
+    assert_equal 6, game.players.length
+    0.upto(5) do |i|
+      assert_equal $INITIAL_CARDS_PER_PLAYER, game.players_cards[i].length
+    end
+    assert_equal $SYMBOLS.length * $CARDS_PER_SYMBOL - 6 * $INITIAL_CARDS_PER_PLAYER, game.deck.length
+  end
+  
+  def test_game_setting
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+#    assert_equal [:bottle, :keys,  :skull], game.players_cards[5].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 18, game.possible_moves.length
+    game.apply_move!(game.possible_moves[0])
+    assert_equal [:hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 87, game.deck.length
+
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 12, game.possible_moves.length
+    game.apply_move!(game.possible_moves[3])
+    assert_equal [:hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 5, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 87, game.deck.length
+
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 7, game.possible_moves.length
+    game.apply_move!(game.possible_moves[2])
+    assert_equal [:hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 86, game.deck.length
+      
+    game.reset_current_player 0  # Needed to prevent errors below
+      
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 12, game.possible_moves.length
+    game.apply_move!(game.possible_moves[-1])
+    assert_equal [:hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 5, game.board.positions.index(game.pieces[0][5].position)
+    assert_equal 86, game.deck.length
+
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 7, game.possible_moves.length
+    game.apply_move!(game.possible_moves[-1])
+    assert_equal [:bottle, :hat, :keys], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][5].position)
+    assert_equal 84, game.deck.length
+  end
+  
+  def test_game_termination
+    game = Game.new(6)
+    game.set_testing_game!
+    assert_equal 0, game.moves_by_current_player
+
+    game.apply_move!(Move.new(game.pieces[0][0], game.board.positions[0], game.board.positions[36]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[0].contains.include?(game.pieces[0][1])
+    assert game.board.positions[0].contains.include?(game.pieces[0][2])
+    assert game.board.positions[0].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 18, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][1], game.board.positions[0], game.board.positions[35]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[0].contains.include?(game.pieces[0][2])
+    assert game.board.positions[0].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 19, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][2], game.board.positions[0], game.board.positions[34]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[0].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 20, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][3], game.board.positions[0], game.board.positions[33]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 21, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][4], game.board.positions[0], game.board.positions[32]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[32].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 22, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][5], game.board.positions[0], game.board.positions[31]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[32].contains.include?(game.pieces[0][4])
+    assert game.board.positions[31].contains.include?(game.pieces[0][5])
+    assert_equal 23, game.possible_moves.length
+    
+    # Now start moving pices onto the boat
+    game.apply_move!(Move.new(game.pieces[0][5], game.board.positions[31], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[32].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 20, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][4], game.board.positions[32], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 17, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][3], game.board.positions[33], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[37].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 14, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][2], game.board.positions[34], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[37].contains.include?(game.pieces[0][2])
+    assert game.board.positions[37].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 11, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][1], game.board.positions[35], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[37].contains.include?(game.pieces[0][1])
+    assert game.board.positions[37].contains.include?(game.pieces[0][2])
+    assert game.board.positions[37].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 8, game.possible_moves.length
+
+    assert_raise(GameWonNotice) {game.apply_move!(Move.new(game.pieces[0][0], game.board.positions[36], game.board.positions[37]), 0, false)}
+  
+  end
+
+  def test_retreats
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+#    assert_equal [:bottle, :keys,  :skull], game.players_cards[5].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 18, game.possible_moves.length
+    game.apply_move!(game.possible_moves[0])
+    assert_equal [:hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 87, game.deck.length
+
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 12, game.possible_moves.length
+    game.apply_move!(game.possible_moves[3])
+    assert_equal [:hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 5, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 87, game.deck.length
+
+    # Test can't retreat into the cell
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 7, game.possible_moves.length
+    assert_raise(InvalidMoveError) do
+      game.apply_move!(Move.new(game.pieces[0][1], game.board.positions[5], game.board.positions[0]))
+    end
+  end
+  # Test invalid move error trapping
+  def test_invalid_move_error_trapping
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    assert_equal 0, game.moves_by_current_player
+    
+    # Test if can move to a space without the right card
+    assert_raise(InvalidMoveError) {game.apply_move!(Move.new(game.pieces[0][0], game.board.positions[2], game.board.positions[0]))}
+    
+    
+    # Apply a series of moves to get an interesting game state
+    game.apply_move!('0 0 5'.to_move(game))
+      assert_equal [:gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 0 4'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 5 4'.to_move(game))
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 0 2'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 0 7'.to_move(game))
+      assert_equal [:bottle], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 0 7'.to_move(game))
+      assert_equal [:bottle, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 10'.to_move(game))
+      assert_equal [:bottle], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 6'.to_move(game))
+      assert_equal [], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 0 12'.to_move(game))
+      assert_equal [:skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 12 10'.to_move(game))
+      assert_equal [:skull, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 5'.to_move(game))
+      assert_equal [:skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 12'.to_move(game))
+      assert_equal [:gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 12 10'.to_move(game))
+      assert_equal [:dagger, :gun, :gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 0 3'.to_move(game))
+      assert_equal [:gun, :gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal [], game.players_cards[2].sort_by {|c| c.to_s}
+      assert_equal [:skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+      assert_equal [:gun, :gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 0 9'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 0 1'.to_move(game))
+      assert_equal [], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 9 7'.to_move(game))
+      assert_equal [:keys], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 0 12'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 0 14'.to_move(game))
+      assert_equal [:keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 12 7'.to_move(game))
+      assert_equal [:hat, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 6 5'.to_move(game))
+      assert_equal [:gun], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 11'.to_move(game))
+      assert_equal [], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 11 5'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 0 9'.to_move(game))
+      assert_equal [:skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 18'.to_move(game))
+      assert_equal [], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 18 14'.to_move(game))
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 11'.to_move(game))
+      assert_equal [:gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 0 16'.to_move(game))
+      assert_equal [:gun], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 16 14'.to_move(game))
+      assert_equal [:gun, :keys, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+   
+      assert_equal [:keys], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal [:hat, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal [:bottle, :keys], game.players_cards[2].sort_by {|c| c.to_s}
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+      assert_equal [:gun, :keys, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+  
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 0 15'.to_move(game))
+      assert_equal [], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 15 11'.to_move(game))
+      assert_equal [:bottle], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 11 9'.to_move(game))
+      assert_equal [:bottle, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 0 15'.to_move(game))
+      assert_equal [:hat, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 0 17'.to_move(game))
+      assert_equal [:keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 17 15'.to_move(game))
+      assert_equal [:keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 0 23'.to_move(game))
+      assert_equal [:bottle], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 23 15'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 17'.to_move(game))
+      assert_equal [:bottle, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 10 9'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 24'.to_move(game))
+      assert_equal [:bottle, :dagger], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 8'.to_move(game))
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 16'.to_move(game))
+      assert_equal [:keys, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 16 11'.to_move(game))
+      assert_equal [:keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 11 10'.to_move(game))
+      assert_equal [:dagger, :dagger, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+
+    assert_equal 2, game.board.positions[ 0].contains.length
+    assert_equal 1, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 1, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 3, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 3, game.board.positions[ 7].contains.length
+    assert_equal 1, game.board.positions[ 8].contains.length
+    assert_equal 3, game.board.positions[ 9].contains.length
+    assert_equal 3, game.board.positions[10].contains.length
+    assert_equal 1, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 3, game.board.positions[14].contains.length
+    assert_equal 3, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 1, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 1, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+    
+    assert_raise(InvalidMoveError) {game.apply_move!('0 7 6'.to_move(game))} # can't retreat into an empty space
+    assert_raise(InvalidMoveError) {game.apply_move!('0 7 5'.to_move(game))} # can't retreat into a space with three men
+    assert_raise(InvalidMoveError) {game.apply_move!('0 1 0'.to_move(game))} # can't retreat into the boat
+    assert_raise(InvalidMoveError) {game.apply_move!('0 1 12'.to_move(game))} # can't skip a vacant space of this type
+    assert_raise(InvalidMoveError) {game.apply_move!('0 1 5'.to_move(game))} # can't advance to a space with three pirates
+    assert_raise(InvalidMoveError) {game.apply_move!('0 2 6'.to_move(game))} # can't move another's piece
+    assert_raise(InvalidMoveError) {game.apply_move!('0 9 9'.to_move(game))} # can't advance the same square
+    assert_raise(InvalidMoveError) {game.apply_move!('0 9 20'.to_move(game))} # can't skip a vacant space of this type
+
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 0 18'.to_move(game))
+      assert_equal [:bottle], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 7 12'.to_move(game))
+      assert_equal [], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 12 11'.to_move(game))
+      assert_equal [:dagger], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 14 11'.to_move(game))
+      assert_equal [:dagger, :keys, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 2 23'.to_move(game))
+      assert_equal [:dagger, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 15 14'.to_move(game))
+      assert_equal [:bottle, :bottle, :dagger, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 17 15'.to_move(game))
+      assert_equal [:bottle, :dagger, :dagger, :keys], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 5 27'.to_move(game))
+      assert_equal [:bottle, :dagger, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 5 13'.to_move(game))
+      assert_equal [:bottle, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 8 7'.to_move(game))
+      assert_equal [:bottle, :bottle, :hat], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 5 17'.to_move(game))
+      assert_equal [:bottle, :bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 7 12'.to_move(game))
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 8'.to_move(game))
+      assert_equal [:dagger, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 8 7'.to_move(game))
+      assert_equal [:bottle, :dagger, :keys, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 3 31'.to_move(game))
+      assert_equal [:bottle, :dagger, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+
+
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 18 17'.to_move(game))
+      assert_equal [:dagger, :gun], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 1 16'.to_move(game))
+      assert_equal [:dagger], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 17 16'.to_move(game))
+      assert_equal [:dagger, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 4 37 keys'.to_move(game))
+      assert_equal [:bottle, :bottle, :dagger, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :bottle, :dagger, :hat, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 4 37 keys'.to_move(game))
+      assert_equal [:bottle, :bottle, :bottle, :dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 7 21'.to_move(game))
+      assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 13 12'.to_move(game))
+      assert_equal [:dagger, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 10 13'.to_move(game))
+      assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 17 16'.to_move(game))
+      assert_equal [:bottle, :hat, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 9 28'.to_move(game))
+      assert_equal [:hat, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 9 18'.to_move(game))
+      assert_equal [:hat], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 7 37 keys'.to_move(game))
+      assert_equal [:bottle, :dagger, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 11 10'.to_move(game))
+      assert_equal [:bottle, :dagger, :dagger, :gun, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 10 32'.to_move(game))
+      assert_equal [:dagger, :dagger, :gun, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+
+    
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 11 10'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 4 8'.to_move(game))
+      assert_equal [:bottle, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 4 17'.to_move(game))
+      assert_equal [:bottle, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 11 37 bottle'.to_move(game))
+      assert_equal [:bottle, :bottle, :dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 14 37 bottle'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 15 37 bottle'.to_move(game))
+      assert_equal [:dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 13 12'.to_move(game))
+      assert_equal [:dagger, :gun, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 12 22 gun'.to_move(game))
+      assert_equal [:dagger, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 12 25 hat'.to_move(game))
+      assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 16 15'.to_move(game))
+      assert_equal [:dagger, :gun, :hat], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 12 35 hat'.to_move(game))
+      assert_equal [:dagger, :gun], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 14 29 gun'.to_move(game))
+      assert_equal [:dagger], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 10 20 skull'.to_move(game))
+      assert_equal [:dagger, :dagger, :gun, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 10 30 skull'.to_move(game))
+      assert_equal [:dagger, :dagger, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 14 36 gun'.to_move(game))
+      assert_equal [:dagger, :dagger], game.players_cards[4].sort_by {|c| c.to_s}
+
+    
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 17 16'.to_move(game))
+      assert_equal [:bottle, :gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 8 33 skull'.to_move(game))
+      assert_equal [:bottle, :gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 9 17 hat'.to_move(game))
+      assert_equal [:bottle, :gun], game.players_cards[0].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 1, game.current_player
+    assert_raise(GameWonNotice) {game.apply_move!('1 23 37 hat'.to_move(game))}
+      assert_equal [:dagger], game.players_cards[1].sort_by {|c| c.to_s}
+
+    assert_equal 0, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 0, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 0, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 0, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 1, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 3, game.board.positions[15].contains.length
+    assert_equal 3, game.board.positions[16].contains.length
+    assert_equal 1, game.board.positions[17].contains.length
+    assert_equal 1, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 1, game.board.positions[20].contains.length
+    assert_equal 1, game.board.positions[21].contains.length
+    assert_equal 1, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 1, game.board.positions[24].contains.length
+    assert_equal 1, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 1, game.board.positions[27].contains.length
+    assert_equal 1, game.board.positions[28].contains.length
+    assert_equal 1, game.board.positions[29].contains.length
+    assert_equal 1, game.board.positions[30].contains.length
+    assert_equal 1, game.board.positions[31].contains.length
+    assert_equal 1, game.board.positions[32].contains.length
+    assert_equal 1, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 1, game.board.positions[35].contains.length
+    assert_equal 1, game.board.positions[36].contains.length
+    assert_equal 7, game.board.positions[37].contains.length
+
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun],
+      game.deck
+
+    assert_equal [:bottle, :gun], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:dagger], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:dagger], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:dagger, :dagger], game.players_cards[4].sort_by {|c| c.to_s}
+    
+  end
+  # 
+  # : move without a card
+  # move to an occupied space
+  # retreat to an emtpy space
+  # retreat to an overfull space
+  # retreat to the cell with one pirate already in it
+  
+  
+  # Test current player switching
+
+  # Test undo moves
+  
+  # Test file reading
+  # (need a full game file to read)
+  
+  def test_undo
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+
+    assert_equal 0, game.history.length
+    
+    game.apply_move!('0 0 5'.to_move(game))
+      assert_equal [:gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 1, game.history.length
+    game.apply_move!('0 0 4'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 2, game.history.length
+    game.apply_move!('0 5 4'.to_move(game))
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 3, game.history.length
+
+    game.next_player!
+      assert_equal 1, game.current_player
+      assert_equal 3, game.history.length
+    game.apply_move!('1 0 2'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal 4, game.history.length
+    game.apply_move!('1 0 7'.to_move(game))
+      assert_equal [:bottle], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal 5, game.history.length
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal 6, game.history.length
+      assert_equal 1, game.current_player
+      assert_equal 3, game.moves_by_current_player
+
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 0, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+
+    game.undo_move! # player 1's third move
+    
+    assert_equal 5, game.history.length
+    
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 2, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle],                 game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle], 
+      game.deck
+    assert_equal 1, game.current_player 
+    assert_equal 2, game.moves_by_current_player
+
+    # Re-apply player 1's third move
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 0 7'.to_move(game))
+      assert_equal [:bottle, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 10'.to_move(game))
+      assert_equal [:bottle], game.players_cards[2].sort_by {|c| c.to_s}
+
+    assert_equal 8, game.history.length
+    assert_equal 24, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 1, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle],                 game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+    assert_equal 2, game.current_player 
+    assert_equal 2, game.moves_by_current_player
+    
+    game.undo_move! # player 2's second move
+    
+    assert_equal 7, game.history.length
+    assert_equal 25, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat],           game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+    assert_equal 2, game.current_player 
+    assert_equal 1, game.moves_by_current_player
+    
+    
+    game.undo_move! # Player 2's first move
+    
+    assert_equal 6, game.history.length
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 0, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,    :keys], game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+    assert_equal 2, game.current_player 
+    assert_equal 0, game.moves_by_current_player
+    
+    
+    game.undo_move! # Player 1's third move
+    
+    assert_equal 5, game.history.length
+    
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 2, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle],                 game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle], 
+      game.deck
+    assert_equal 1, game.current_player 
+    assert_equal 2, game.moves_by_current_player
+  
+  end
+
+  def test_apply_moves
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+
+    assert_equal 0, game.history.length
+    
+    game.apply_move!('0 0 5'.to_move(game))
+      assert_equal [:gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 1, game.history.length
+    game.apply_move!('0 0 4'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 2, game.history.length
+    game.apply_move!('0 5 4'.to_move(game))
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 3, game.history.length
+
+    game.next_player!
+      assert_equal 1, game.current_player
+      assert_equal 3, game.history.length
+    game.apply_move!('1 0 2'.to_move(game))
+  end
+  
+  def test_read_game_lines
+    gamelines1 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5 skull", "1 0 4 hat", 
+      "1",
+      "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines1)}
+    game, moves = Game.read_game(gamelines1)
+    assert_equal 2, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 1, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 15, game.deck.length
+    
+    game.apply_move!('0 5 4'.to_move(game))
+    assert_equal 3, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    gamelines2 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", 
+      "2",
+      "dagger", "hat", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines2)}
+    game, moves = Game.read_game(gamelines2)
+    assert_equal 3, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    game.apply_move!('1 0 2'.to_move(game))
+    assert_equal 4, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 2, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    gamelines3 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",
+      "2",
+      "dagger", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines3)}
+    game, moves = Game.read_game(gamelines3)
+    assert_equal 4, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 2, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    game.apply_move!('1 0 7'.to_move(game))
+    assert_equal 5, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 1, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    gamelines4 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",  "2 0 7",
+      "2",
+      "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines4)}
+    game, moves = Game.read_game(gamelines4)
+    assert_equal 5, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 1, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+
+    game.apply_move!('1 7 4'.to_move(game))
+    assert_equal 6, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+    
+    gamelines5 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",  "2 0 7",  "2 7 4",
+      "2",
+      "bottle", "hat", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines5)}
+    game, moves = Game.read_game(gamelines5)
+    assert_equal 6, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+
+    game.players_cards[2] << :keys
+    game.players_cards[2] << :hat
+    game.apply_move!('3 0 7'.to_move(game, true), 2)
+    assert_equal 7, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    game.apply_move!('3 0 10'.to_move(game, true))
+    assert_equal 8, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 2, game.moves_by_current_player   
+    
+    gamelines6 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",  "2 0 7",  "2 7 4",
+      "3",
+      "bottle", "hat", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines6)}
+    game, moves = Game.read_game(gamelines6)
+    assert_equal 6, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+
+    game.apply_move!('2 0 7'.to_move(game))
+    assert_equal 7, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 2, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+
+    game.apply_move!('2 0 10'.to_move(game))
+    assert_equal 8, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 2, game.moves_by_current_player   
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 1, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+    
+    # Need to do a test with a very long sequence of moves, 
+    # to test that the card dealing works properly when the deck is exhausted
+    
+  end
+  
+  def test_dealing
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    0.upto(4) do |i|
+      assert_equal $INITIAL_CARDS_PER_PLAYER, game.players_cards[i].length
+    end
+    assert_equal $SYMBOLS.length * $CARDS_PER_SYMBOL - 5 * $INITIAL_CARDS_PER_PLAYER, game.deck.length
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    game.deal_cards!(86, 0)
+    assert_equal 89, game.players_cards[0].length
+    assert_equal 1,  game.deck.length
+    assert_equal [:hat], game.deck
+
+    game.players_cards[0] = [:hat, :hat, :hat, :hat, :hat]
+    game.players_cards[1] = [:keys, :keys, :keys]
+    game.players_cards[2] = [:bottle, :dagger, :skull]
+    game.players_cards[3] = []
+    game.players_cards[4] = []
+    game.deal_cards!(1, 3)
+    
+    assert_equal [:hat, :hat, :hat, :hat, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:keys, :keys, :keys],          game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :dagger, :skull],     game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:hat],                         game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [],                             game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [], game.deck
+    
+    game.deal_cards!(1, 4)
+    assert_equal [:keys], game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal $CARDS_PER_SYMBOL - 6, game.deck.select {|c| c == :hat}.length
+    assert_equal $CARDS_PER_SYMBOL - 4, game.deck.select {|c| c == :keys}.length
+    assert_equal $CARDS_PER_SYMBOL - 1, game.deck.select {|c| c == :bottle}.length
+    assert_equal $CARDS_PER_SYMBOL - 1, game.deck.select {|c| c == :dagger}.length
+    assert_equal $CARDS_PER_SYMBOL - 1, game.deck.select {|c| c == :skull}.length
+    assert_equal $CARDS_PER_SYMBOL,     game.deck.select {|c| c == :gun}.length
+    assert_equal $CARDS_PER_SYMBOL * 6 - 13, game.deck.length
+
+  end
+  
+  def test_long_deal
+    gamelines0 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1",
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines0)}
+    game, moves = Game.read_game(gamelines0)
+    assert_equal 0, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 3, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 9, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 9, game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines1 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1 gun", "1 0 2 keys", "1 2 1",
+      "2",
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines1)}
+    game, moves = Game.read_game(gamelines1)
+    assert_equal 3, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 10, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 2, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 10, game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 1, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 1, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines2 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1", "1 0 2", "1 2 1",
+      "2 0 2", "2 0 3", "2 3 2",
+      "3 0 3", "3 0 4", "3 4 3",
+      "1 0 4", "1 0 5", "1 5 4",
+      "2 0 5", "2 0 6", "2 6 5",
+      "3 0 6", "3 0 11", "3 11 6",
+      "1",
+      "hat"]
+    assert_nothing_raised {Game.read_game(gamelines2)}
+    game, moves = Game.read_game(gamelines2)
+    assert_equal 18, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 1, game.players_cards[0].length
+    assert_equal 1, game.players_cards[1].length
+    assert_equal 1, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 6), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 12, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 6), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines2 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1",
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines2)}
+    game, moves = Game.read_game(gamelines2)
+    assert_equal 30, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 3, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 18), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 18), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines3 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3",
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines3)}
+    game, moves = Game.read_game(gamelines3)
+    assert_equal 42, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 5, game.players_cards[0].length
+    assert_equal 5, game.players_cards[1].length
+    assert_equal 5, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 30), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 24, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 30), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines4 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", "hat"]
+    assert_nothing_raised {Game.read_game(gamelines4)}
+    game, moves = Game.read_game(gamelines4)
+    assert_equal 54, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 7, game.players_cards[0].length
+    assert_equal 7, game.players_cards[1].length
+    assert_equal 7, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 42), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 30, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 42), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :skull}).length
+
+    
+    gamelines5 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", "hat", "skull", "bottle", "gun", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines5)}
+    game, moves = Game.read_game(gamelines5)
+    assert_equal 78, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 11, game.players_cards[0].length
+    assert_equal 11, game.players_cards[1].length
+    assert_equal 11, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 66), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 42, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 66), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines6 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines6)}
+    game, moves = Game.read_game(gamelines6)
+    assert_equal 102, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 90), game.deck.length
+    assert_equal 3, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 54, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 90), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines7 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines7)}
+    game, moves = Game.read_game(gamelines7)
+    assert_equal 104, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 16, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 55, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines8 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "skull", "bottle", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines8)}
+    game, moves = Game.read_game(gamelines8)
+    assert_equal 105, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 56, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :skull}).length
+
+
+    gamelines9 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 12", "2 12 10",
+      "2",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines9)}
+    game, moves = Game.read_game(gamelines9)
+    assert_equal 107, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 45, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 0, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :skull}).length
+  
+  gamelines10 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "hat", "hat", "hat", "hat", "hat", 
+      "hat", "hat", "hat"]
+    assert_raise(InvalidMoveError) {Game.read_game(gamelines10)} # Not enough skull cards to go round
+    
+    gamelines11 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "skull", "bottle", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines11)}
+    game, moves = Game.read_game(gamelines11)
+    assert_equal 111, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 12, game.players_cards[1].length
+    assert_equal 12, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 62, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines12 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines12)}
+    game, moves = Game.read_game(gamelines12)
+    assert_equal 114, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 12, game.players_cards[0].length
+    assert_equal 12, game.players_cards[1].length
+    assert_equal 12, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 65, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 14, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines13 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3",
+      "skull", "skull", "bottle", "gun", "dagger", "keys", 
+      "bottle", "skull", "bottle", "gun", "dagger", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines13)}
+    game, moves = Game.read_game(gamelines13)
+    assert_equal 117, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 12, game.players_cards[0].length
+    assert_equal 9,  game.players_cards[1].length
+    assert_equal 12, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 68, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines14 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 37",
+      "3",
+      "skull", "skull", "bottle", "gun", "dagger", "keys", 
+      "bottle", "skull", "bottle", "gun", "dagger"]
+    assert_raise(InvalidMoveError) {Game.read_game(gamelines14)}
+
+    gamelines15 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1",
+      "skull", "skull", "bottle", "gun", "dagger", "keys", 
+      "bottle", "skull", "bottle", "gun", "dagger", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines15)}
+    game, moves = Game.read_game(gamelines15)
+    assert_equal 120, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 12, game.players_cards[0].length
+    assert_equal 9,  game.players_cards[1].length
+    assert_equal 9,  game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 71, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 13, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines16 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2",
+      "skull", "skull", "keys", "gun", "dagger", "keys", 
+      "skull", "skull", "skull"]
+    assert_nothing_raised {Game.read_game(gamelines16)}
+    game, moves = Game.read_game(gamelines16)
+    assert_equal 123, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 9, game.players_cards[0].length
+    assert_equal 9, game.players_cards[1].length
+    assert_equal 9, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 74, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 16, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines17 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3",
+      "skull", "skull", "keys", "gun", "dagger", "keys", 
+      "skull", "skull", "skull"]
+    assert_nothing_raised {Game.read_game(gamelines17)}
+    game, moves = Game.read_game(gamelines17)
+    assert_equal 126, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 9, game.players_cards[0].length
+    assert_equal 6, game.players_cards[1].length
+    assert_equal 9, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 77, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 13, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines18 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1",
+      "skull", "skull", "keys", "gun", "dagger", "keys", 
+      "skull", "skull", "skull"]
+    assert_nothing_raised {Game.read_game(gamelines18)}
+    game, moves = Game.read_game(gamelines18)
+    assert_equal 129, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 9, game.players_cards[0].length
+    assert_equal 6, game.players_cards[1].length
+    assert_equal 6, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 80, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 15, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines19 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2",
+      "skull", "skull", "gun", "gun", "dagger", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines19)}
+    game, moves = Game.read_game(gamelines19)
+    assert_equal 132, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 6, game.players_cards[0].length
+    assert_equal 6, game.players_cards[1].length
+    assert_equal 6, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 83, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines20 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3",
+      "skull", "skull", "gun", "gun", "dagger", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines20)}
+    game, moves = Game.read_game(gamelines20)
+    assert_equal 135, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 6, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 6, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 86, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 14, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines21 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3 18 30 skull", "3 20 37 skull", "3 21 37 skull",
+      "1",
+      "gun", "gun", "gun", "gun", "gun", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines21)}
+    game, moves = Game.read_game(gamelines21)
+    assert_equal 138, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 6, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 89, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines22 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3 18 30 skull", "3 20 37 skull", "3 21 37 skull",
+      "1 35 37 gun",
+      "1",
+      "gun", "gun", "gun", "gun", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines22)}
+    game, moves = Game.read_game(gamelines22)
+    assert_equal 139, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 5, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 90, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 12, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :skull}).length
+    
+    assert_raise(GameWonNotice) {game.apply_move!("1 36 37 gun".to_move(game, true))}
+    assert_raise(InvalidMoveError) {game.apply_move!("1 36 37".to_move(game, true))}
+
+    gamelines23 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3 18 30 skull", "3 20 37 skull", "3 21 37 skull",
+      "1 35 37 gun", "1 36 37 gun",
+      "1",
+      "gun", "gun", "gun", "gun"]
+    assert_raise(GameWonNotice) {Game.read_game(gamelines23)}
+
+    gamelines24 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3 18 30 skull", "3 20 37 skull", "3 21 37 skull",
+      "1 35 37 gun", "1 36 37 gun",
+      "2 33 34 dagger",
+      "2",
+      "dagger", "dagger"]
+    assert_raise(GameWonNotice) {Game.read_game(gamelines24)}
+  end
+  
+end
\ No newline at end of file
diff --git a/test/libcartagena_test.rb b/test/libcartagena_test.rb
new file mode 100644 (file)
index 0000000..7825154
--- /dev/null
@@ -0,0 +1,2920 @@
+# == Synopsis
+#
+# Unit tests for the libcartagena library
+# 
+# == Author
+# Neil Smith
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# == Change history
+# Version 1.0::  5 Dec 2008
+# =>             * Initial build
+
+
+$:.unshift File.join(File.dirname(__FILE__),'..','lib', 'libcartagena')
+
+require 'test/unit'
+require File.join(File.dirname(__FILE__), '..', 'lib', 'libcartagena')
+
+class TileTest < Test::Unit::TestCase
+  def test_tile_init
+    front = [:hat, :keys, :gun, :bottle, :skull, :dagger]
+    back  = [:gun, :hat, :dagger, :skull, :bottle, :keys]
+    assert_nothing_raised { Tile.new(front, back) }
+    tile = Tile.new(front, back) 
+    
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front, tile.exposed.map {|p| p.symbol}
+    
+    tile.flip!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal back,  tile.exposed.map {|p| p.symbol}
+    tile.flip!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front, tile.exposed.map {|p| p.symbol}
+    
+    tile.flip!
+    tile.reverse!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal back.reverse, tile.exposed.map {|p| p.symbol}
+    
+    tile.flip!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front, tile.exposed.map {|p| p.symbol}
+    
+    tile.reverse!
+    assert_equal 6, tile.front.length
+    assert_equal 6, tile.back.length
+    assert_equal 6, tile.exposed.length
+    assert_equal front, tile.front.map {|p| p.symbol}
+    assert_equal back,  tile.back.map {|p| p.symbol}
+    assert_equal front.reverse, tile.exposed.map {|p| p.symbol}
+    
+  end
+end
+
+class BoardTest < Test::Unit::TestCase
+  def test_board_init
+    assert_nothing_raised { Board.new(6) }
+    board = Board.new(1)
+    assert_equal 1, board.tiles.length
+    assert_equal 8, board.positions.length
+
+    board = Board.new(6)
+    assert_equal 6, board.tiles.length
+    assert_equal 38, board.positions.length
+
+    0.upto(5) do |i|
+      start = i * 6 + 1
+      slice = board.positions[start..(start+5)]
+      $SYMBOLS.each do |tile|
+        assert_equal 1, (slice.find_all {|p| p.symbol == tile}).length
+      end
+    end
+  end
+end
+
+
+class GameTest < Test::Unit::TestCase
+  def test_game_init
+    assert_nothing_raised { Game.new }
+    
+    game = Game.new(6)
+    assert_equal 6, game.players.length
+    0.upto(5) do |i|
+      assert_equal $INITIAL_CARDS_PER_PLAYER, game.players_cards[i].length
+    end
+    assert_equal $SYMBOLS.length * $CARDS_PER_SYMBOL - 6 * $INITIAL_CARDS_PER_PLAYER, game.deck.length
+  end
+  
+  def test_game_setting
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+#    assert_equal [:bottle, :keys,  :skull], game.players_cards[5].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 18, game.possible_moves.length
+    game.apply_move!(game.possible_moves[0])
+    assert_equal [:hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 87, game.deck.length
+
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 12, game.possible_moves.length
+    game.apply_move!(game.possible_moves[3])
+    assert_equal [:hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 5, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 87, game.deck.length
+
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 7, game.possible_moves.length
+    game.apply_move!(game.possible_moves[2])
+    assert_equal [:hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 86, game.deck.length
+      
+    game.reset_current_player 0  # Needed to prevent errors below
+      
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 12, game.possible_moves.length
+    game.apply_move!(game.possible_moves[-1])
+    assert_equal [:hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 5, game.board.positions.index(game.pieces[0][5].position)
+    assert_equal 86, game.deck.length
+
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 7, game.possible_moves.length
+    game.apply_move!(game.possible_moves[-1])
+    assert_equal [:bottle, :hat, :keys], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 1, game.board.positions.index(game.pieces[0][5].position)
+    assert_equal 84, game.deck.length
+  end
+  
+  def test_game_termination
+    game = Game.new(6)
+    game.set_testing_game!
+    assert_equal 0, game.moves_by_current_player
+
+    game.apply_move!(Move.new(game.pieces[0][0], game.board.positions[0], game.board.positions[36]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[0].contains.include?(game.pieces[0][1])
+    assert game.board.positions[0].contains.include?(game.pieces[0][2])
+    assert game.board.positions[0].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 18, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][1], game.board.positions[0], game.board.positions[35]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[0].contains.include?(game.pieces[0][2])
+    assert game.board.positions[0].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 19, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][2], game.board.positions[0], game.board.positions[34]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[0].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 20, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][3], game.board.positions[0], game.board.positions[33]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[0].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 21, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][4], game.board.positions[0], game.board.positions[32]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[32].contains.include?(game.pieces[0][4])
+    assert game.board.positions[0].contains.include?(game.pieces[0][5])
+    assert_equal 22, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][5], game.board.positions[0], game.board.positions[31]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[32].contains.include?(game.pieces[0][4])
+    assert game.board.positions[31].contains.include?(game.pieces[0][5])
+    assert_equal 23, game.possible_moves.length
+    
+    # Now start moving pices onto the boat
+    game.apply_move!(Move.new(game.pieces[0][5], game.board.positions[31], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[32].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 20, game.possible_moves.length
+
+    game.apply_move!(Move.new(game.pieces[0][4], game.board.positions[32], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[33].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 17, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][3], game.board.positions[33], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[34].contains.include?(game.pieces[0][2])
+    assert game.board.positions[37].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 14, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][2], game.board.positions[34], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[35].contains.include?(game.pieces[0][1])
+    assert game.board.positions[37].contains.include?(game.pieces[0][2])
+    assert game.board.positions[37].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 11, game.possible_moves.length
+    
+    game.apply_move!(Move.new(game.pieces[0][1], game.board.positions[35], game.board.positions[37]), 0, false)
+    assert_equal [:gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert game.board.positions[36].contains.include?(game.pieces[0][0])
+    assert game.board.positions[37].contains.include?(game.pieces[0][1])
+    assert game.board.positions[37].contains.include?(game.pieces[0][2])
+    assert game.board.positions[37].contains.include?(game.pieces[0][3])
+    assert game.board.positions[37].contains.include?(game.pieces[0][4])
+    assert game.board.positions[37].contains.include?(game.pieces[0][5])
+    assert_equal 8, game.possible_moves.length
+
+    assert_raise(GameWonNotice) {game.apply_move!(Move.new(game.pieces[0][0], game.board.positions[36], game.board.positions[37]), 0, false)}
+  
+  end
+
+  def test_retreats
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+#    assert_equal [:bottle, :keys,  :skull], game.players_cards[5].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 18, game.possible_moves.length
+    game.apply_move!(game.possible_moves[0])
+    assert_equal [:hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 87, game.deck.length
+
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 12, game.possible_moves.length
+    game.apply_move!(game.possible_moves[3])
+    assert_equal [:hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal 1, game.board.positions.index(game.pieces[0][0].position)
+    assert_equal 5, game.board.positions.index(game.pieces[0][1].position)
+    assert_equal 87, game.deck.length
+
+    # Test can't retreat into the cell
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 7, game.possible_moves.length
+    assert_raise(InvalidMoveError) do
+      game.apply_move!(Move.new(game.pieces[0][1], game.board.positions[5], game.board.positions[0]))
+    end
+  end
+  # Test invalid move error trapping
+  def test_invalid_move_error_trapping
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    assert_equal 0, game.moves_by_current_player
+    
+    # Test if can move to a space without the right card
+    assert_raise(InvalidMoveError) {game.apply_move!(Move.new(game.pieces[0][0], game.board.positions[2], game.board.positions[0]))}
+    
+    
+    # Apply a series of moves to get an interesting game state
+    game.apply_move!('0 0 5'.to_move(game))
+      assert_equal [:gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 0 4'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 5 4'.to_move(game))
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 0 2'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 0 7'.to_move(game))
+      assert_equal [:bottle], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 0 7'.to_move(game))
+      assert_equal [:bottle, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 10'.to_move(game))
+      assert_equal [:bottle], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 6'.to_move(game))
+      assert_equal [], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 0 12'.to_move(game))
+      assert_equal [:skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 12 10'.to_move(game))
+      assert_equal [:skull, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 5'.to_move(game))
+      assert_equal [:skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 12'.to_move(game))
+      assert_equal [:gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 12 10'.to_move(game))
+      assert_equal [:dagger, :gun, :gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 0 3'.to_move(game))
+      assert_equal [:gun, :gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal [], game.players_cards[2].sort_by {|c| c.to_s}
+      assert_equal [:skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+      assert_equal [:gun, :gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 0 9'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 0 1'.to_move(game))
+      assert_equal [], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 9 7'.to_move(game))
+      assert_equal [:keys], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 0 12'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 0 14'.to_move(game))
+      assert_equal [:keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 12 7'.to_move(game))
+      assert_equal [:hat, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 6 5'.to_move(game))
+      assert_equal [:gun], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 11'.to_move(game))
+      assert_equal [], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 11 5'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 0 9'.to_move(game))
+      assert_equal [:skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 18'.to_move(game))
+      assert_equal [], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 18 14'.to_move(game))
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 11'.to_move(game))
+      assert_equal [:gun, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 0 16'.to_move(game))
+      assert_equal [:gun], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 16 14'.to_move(game))
+      assert_equal [:gun, :keys, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+   
+      assert_equal [:keys], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal [:hat, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal [:bottle, :keys], game.players_cards[2].sort_by {|c| c.to_s}
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+      assert_equal [:gun, :keys, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+  
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 0 15'.to_move(game))
+      assert_equal [], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 15 11'.to_move(game))
+      assert_equal [:bottle], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 11 9'.to_move(game))
+      assert_equal [:bottle, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 0 15'.to_move(game))
+      assert_equal [:hat, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 0 17'.to_move(game))
+      assert_equal [:keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 17 15'.to_move(game))
+      assert_equal [:keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 0 23'.to_move(game))
+      assert_equal [:bottle], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 23 15'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 17'.to_move(game))
+      assert_equal [:bottle, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 10 9'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 24'.to_move(game))
+      assert_equal [:bottle, :dagger], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 0 8'.to_move(game))
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 16'.to_move(game))
+      assert_equal [:keys, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 16 11'.to_move(game))
+      assert_equal [:keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 11 10'.to_move(game))
+      assert_equal [:dagger, :dagger, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+
+    assert_equal 2, game.board.positions[ 0].contains.length
+    assert_equal 1, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 1, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 3, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 3, game.board.positions[ 7].contains.length
+    assert_equal 1, game.board.positions[ 8].contains.length
+    assert_equal 3, game.board.positions[ 9].contains.length
+    assert_equal 3, game.board.positions[10].contains.length
+    assert_equal 1, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 3, game.board.positions[14].contains.length
+    assert_equal 3, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 1, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 1, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+    
+    assert_raise(InvalidMoveError) {game.apply_move!('0 7 6'.to_move(game))} # can't retreat into an empty space
+    assert_raise(InvalidMoveError) {game.apply_move!('0 7 5'.to_move(game))} # can't retreat into a space with three men
+    assert_raise(InvalidMoveError) {game.apply_move!('0 1 0'.to_move(game))} # can't retreat into the boat
+    assert_raise(InvalidMoveError) {game.apply_move!('0 1 12'.to_move(game))} # can't skip a vacant space of this type
+    assert_raise(InvalidMoveError) {game.apply_move!('0 1 5'.to_move(game))} # can't advance to a space with three pirates
+    assert_raise(InvalidMoveError) {game.apply_move!('0 2 6'.to_move(game))} # can't move another's piece
+    assert_raise(InvalidMoveError) {game.apply_move!('0 9 9'.to_move(game))} # can't advance the same square
+    assert_raise(InvalidMoveError) {game.apply_move!('0 9 20'.to_move(game))} # can't skip a vacant space of this type
+
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 0 18'.to_move(game))
+      assert_equal [:bottle], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 7 12'.to_move(game))
+      assert_equal [], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 12 11'.to_move(game))
+      assert_equal [:dagger], game.players_cards[0].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 14 11'.to_move(game))
+      assert_equal [:dagger, :keys, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 2 23'.to_move(game))
+      assert_equal [:dagger, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 15 14'.to_move(game))
+      assert_equal [:bottle, :bottle, :dagger, :keys, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 17 15'.to_move(game))
+      assert_equal [:bottle, :dagger, :dagger, :keys], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 5 27'.to_move(game))
+      assert_equal [:bottle, :dagger, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 5 13'.to_move(game))
+      assert_equal [:bottle, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 8 7'.to_move(game))
+      assert_equal [:bottle, :bottle, :hat], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 5 17'.to_move(game))
+      assert_equal [:bottle, :bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 7 12'.to_move(game))
+      assert_equal [:bottle], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 0 8'.to_move(game))
+      assert_equal [:dagger, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 8 7'.to_move(game))
+      assert_equal [:bottle, :dagger, :keys, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 3 31'.to_move(game))
+      assert_equal [:bottle, :dagger, :keys, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+
+
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 18 17'.to_move(game))
+      assert_equal [:dagger, :gun], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 1 16'.to_move(game))
+      assert_equal [:dagger], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 17 16'.to_move(game))
+      assert_equal [:dagger, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 4 37 keys'.to_move(game))
+      assert_equal [:bottle, :bottle, :dagger, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :bottle, :dagger, :hat, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 4 37 keys'.to_move(game))
+      assert_equal [:bottle, :bottle, :bottle, :dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 7 21'.to_move(game))
+      assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 13 12'.to_move(game))
+      assert_equal [:dagger, :dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 10 13'.to_move(game))
+      assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 17 16'.to_move(game))
+      assert_equal [:bottle, :hat, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 9 28'.to_move(game))
+      assert_equal [:hat, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 9 18'.to_move(game))
+      assert_equal [:hat], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 7 37 keys'.to_move(game))
+      assert_equal [:bottle, :dagger, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 11 10'.to_move(game))
+      assert_equal [:bottle, :dagger, :dagger, :gun, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 10 32'.to_move(game))
+      assert_equal [:dagger, :dagger, :gun, :skull, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+
+    
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 11 10'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 4 8'.to_move(game))
+      assert_equal [:bottle, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 4 17'.to_move(game))
+      assert_equal [:bottle, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 1, game.current_player
+    game.apply_move!('1 11 37 bottle'.to_move(game))
+      assert_equal [:bottle, :bottle, :dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 14 37 bottle'.to_move(game))
+      assert_equal [:bottle, :dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+    game.apply_move!('1 15 37 bottle'.to_move(game))
+      assert_equal [:dagger, :hat], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 13 12'.to_move(game))
+      assert_equal [:dagger, :gun, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 12 22 gun'.to_move(game))
+      assert_equal [:dagger, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 12 25 hat'.to_move(game))
+      assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 3, game.current_player
+    game.apply_move!('3 16 15'.to_move(game))
+      assert_equal [:dagger, :gun, :hat], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 12 35 hat'.to_move(game))
+      assert_equal [:dagger, :gun], game.players_cards[3].sort_by {|c| c.to_s}
+    game.apply_move!('3 14 29 gun'.to_move(game))
+      assert_equal [:dagger], game.players_cards[3].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 4, game.current_player
+    game.apply_move!('4 10 20 skull'.to_move(game))
+      assert_equal [:dagger, :dagger, :gun, :skull], game.players_cards[4].sort_by {|c| c.to_s}
+     game.apply_move!('4 10 30 skull'.to_move(game))
+      assert_equal [:dagger, :dagger, :gun], game.players_cards[4].sort_by {|c| c.to_s}
+    game.apply_move!('4 14 36 gun'.to_move(game))
+      assert_equal [:dagger, :dagger], game.players_cards[4].sort_by {|c| c.to_s}
+
+    
+    game.next_player!
+      assert_equal 0, game.current_player
+      
+    game.apply_move!('0 17 16'.to_move(game))
+      assert_equal [:bottle, :gun, :hat, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 8 33 skull'.to_move(game))
+      assert_equal [:bottle, :gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+    game.apply_move!('0 9 17 hat'.to_move(game))
+      assert_equal [:bottle, :gun], game.players_cards[0].sort_by {|c| c.to_s}
+    
+    game.next_player!
+      assert_equal 1, game.current_player
+    assert_raise(GameWonNotice) {game.apply_move!('1 23 37 hat'.to_move(game))}
+      assert_equal [:dagger], game.players_cards[1].sort_by {|c| c.to_s}
+
+    assert_equal 0, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 0, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 0, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 0, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 1, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 3, game.board.positions[15].contains.length
+    assert_equal 3, game.board.positions[16].contains.length
+    assert_equal 1, game.board.positions[17].contains.length
+    assert_equal 1, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 1, game.board.positions[20].contains.length
+    assert_equal 1, game.board.positions[21].contains.length
+    assert_equal 1, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 1, game.board.positions[24].contains.length
+    assert_equal 1, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 1, game.board.positions[27].contains.length
+    assert_equal 1, game.board.positions[28].contains.length
+    assert_equal 1, game.board.positions[29].contains.length
+    assert_equal 1, game.board.positions[30].contains.length
+    assert_equal 1, game.board.positions[31].contains.length
+    assert_equal 1, game.board.positions[32].contains.length
+    assert_equal 1, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 1, game.board.positions[35].contains.length
+    assert_equal 1, game.board.positions[36].contains.length
+    assert_equal 7, game.board.positions[37].contains.length
+
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun],
+      game.deck
+
+    assert_equal [:bottle, :gun], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:dagger], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:dagger], game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:dagger], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:dagger, :dagger], game.players_cards[4].sort_by {|c| c.to_s}
+    
+  end
+  # 
+  # : move without a card
+  # move to an occupied space
+  # retreat to an emtpy space
+  # retreat to an overfull space
+  # retreat to the cell with one pirate already in it
+  
+  
+  # Test current player switching
+
+  # Test undo moves
+  
+  # Test file reading
+  # (need a full game file to read)
+  
+  def test_undo
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+
+    assert_equal 0, game.history.length
+    
+    game.apply_move!('0 0 5'.to_move(game))
+      assert_equal [:gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 1, game.history.length
+    game.apply_move!('0 0 4'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 2, game.history.length
+    game.apply_move!('0 5 4'.to_move(game))
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 3, game.history.length
+
+    game.next_player!
+      assert_equal 1, game.current_player
+      assert_equal 3, game.history.length
+    game.apply_move!('1 0 2'.to_move(game))
+      assert_equal [:bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal 4, game.history.length
+    game.apply_move!('1 0 7'.to_move(game))
+      assert_equal [:bottle], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal 5, game.history.length
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+      assert_equal 6, game.history.length
+      assert_equal 1, game.current_player
+      assert_equal 3, game.moves_by_current_player
+
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 0, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+
+    game.undo_move! # player 1's third move
+    
+    assert_equal 5, game.history.length
+    
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 2, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle],                 game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle], 
+      game.deck
+    assert_equal 1, game.current_player 
+    assert_equal 2, game.moves_by_current_player
+
+    # Re-apply player 1's third move
+    game.apply_move!('1 7 4'.to_move(game))
+      assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+
+    game.next_player!
+      assert_equal 2, game.current_player
+    game.apply_move!('2 0 7'.to_move(game))
+      assert_equal [:bottle, :hat], game.players_cards[2].sort_by {|c| c.to_s}
+    game.apply_move!('2 0 10'.to_move(game))
+      assert_equal [:bottle], game.players_cards[2].sort_by {|c| c.to_s}
+
+    assert_equal 8, game.history.length
+    assert_equal 24, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 1, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle],                 game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+    assert_equal 2, game.current_player 
+    assert_equal 2, game.moves_by_current_player
+    
+    game.undo_move! # player 2's second move
+    
+    assert_equal 7, game.history.length
+    assert_equal 25, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat],           game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+    assert_equal 2, game.current_player 
+    assert_equal 1, game.moves_by_current_player
+    
+    
+    game.undo_move! # Player 2's first move
+    
+    assert_equal 6, game.history.length
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 3, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 0, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :bottle, :keys], game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,    :keys], game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull], 
+      game.deck
+    assert_equal 2, game.current_player 
+    assert_equal 0, game.moves_by_current_player
+    
+    
+    game.undo_move! # Player 1's third move
+    
+    assert_equal 5, game.history.length
+    
+    assert_equal 26, game.board.positions[ 0].contains.length
+    assert_equal 0, game.board.positions[ 1].contains.length
+    assert_equal 1, game.board.positions[ 2].contains.length
+    assert_equal 0, game.board.positions[ 3].contains.length
+    assert_equal 2, game.board.positions[ 4].contains.length
+    assert_equal 0, game.board.positions[ 5].contains.length
+    assert_equal 0, game.board.positions[ 6].contains.length
+    assert_equal 1, game.board.positions[ 7].contains.length
+    assert_equal 0, game.board.positions[ 8].contains.length
+    assert_equal 0, game.board.positions[ 9].contains.length
+    assert_equal 0, game.board.positions[10].contains.length
+    assert_equal 0, game.board.positions[11].contains.length
+    assert_equal 0, game.board.positions[12].contains.length
+    assert_equal 0, game.board.positions[13].contains.length
+    assert_equal 0, game.board.positions[14].contains.length
+    assert_equal 0, game.board.positions[15].contains.length
+    assert_equal 0, game.board.positions[16].contains.length
+    assert_equal 0, game.board.positions[17].contains.length
+    assert_equal 0, game.board.positions[18].contains.length
+    assert_equal 0, game.board.positions[19].contains.length
+    assert_equal 0, game.board.positions[20].contains.length
+    assert_equal 0, game.board.positions[21].contains.length
+    assert_equal 0, game.board.positions[22].contains.length
+    assert_equal 0, game.board.positions[23].contains.length
+    assert_equal 0, game.board.positions[24].contains.length
+    assert_equal 0, game.board.positions[25].contains.length
+    assert_equal 0, game.board.positions[26].contains.length
+    assert_equal 0, game.board.positions[27].contains.length
+    assert_equal 0, game.board.positions[28].contains.length
+    assert_equal 0, game.board.positions[29].contains.length
+    assert_equal 0, game.board.positions[30].contains.length
+    assert_equal 0, game.board.positions[31].contains.length
+    assert_equal 0, game.board.positions[32].contains.length
+    assert_equal 0, game.board.positions[33].contains.length
+    assert_equal 0, game.board.positions[34].contains.length
+    assert_equal 0, game.board.positions[35].contains.length
+    assert_equal 0, game.board.positions[36].contains.length
+    assert_equal 0, game.board.positions[37].contains.length
+
+    assert_equal [:gun,    :skull],         game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle],                 game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle], 
+      game.deck
+    assert_equal 1, game.current_player 
+    assert_equal 2, game.moves_by_current_player
+  
+  end
+
+  def test_apply_moves
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    game.pieces.each do |player|
+      player.each do |piece|
+        assert_equal game.board.positions[0], piece.position
+      end
+    end
+    assert_equal [:cell, :gun, :keys, :dagger, :hat, :skull, :bottle,
+      :keys, :dagger, :skull, :hat, :gun, :bottle, 
+      :dagger, :bottle, :keys, :gun, :hat, :skull, 
+      :dagger, :skull, :bottle, :gun, :keys, :hat, 
+      :hat, :dagger, :keys, :bottle, :gun, :skull, 
+      :keys, :bottle, :skull, :dagger, :hat, :gun, :boat], 
+      game.board.positions.collect {|p| p.symbol}   
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+
+    assert_equal 0, game.history.length
+    
+    game.apply_move!('0 0 5'.to_move(game))
+      assert_equal [:gun, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 1, game.history.length
+    game.apply_move!('0 0 4'.to_move(game))
+      assert_equal [:gun], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 2, game.history.length
+    game.apply_move!('0 5 4'.to_move(game))
+      assert_equal [:gun, :skull], game.players_cards[0].sort_by {|c| c.to_s}
+      assert_equal 3, game.history.length
+
+    game.next_player!
+      assert_equal 1, game.current_player
+      assert_equal 3, game.history.length
+    game.apply_move!('1 0 2'.to_move(game))
+  end
+  
+  def test_read_game_lines
+    gamelines1 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5 skull", "1 0 4 hat", 
+      "1",
+      "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines1)}
+    game, moves = Game.read_game(gamelines1)
+    assert_equal 2, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 1, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 15, game.deck.length
+    
+    game.apply_move!('0 5 4'.to_move(game))
+    assert_equal 3, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    gamelines2 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", 
+      "2",
+      "dagger", "hat", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines2)}
+    game, moves = Game.read_game(gamelines2)
+    assert_equal 3, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    game.apply_move!('1 0 2'.to_move(game))
+    assert_equal 4, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 2, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    gamelines3 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",
+      "2",
+      "dagger", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines3)}
+    game, moves = Game.read_game(gamelines3)
+    assert_equal 4, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 2, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    game.apply_move!('1 0 7'.to_move(game))
+    assert_equal 5, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 1, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+    
+    gamelines4 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",  "2 0 7",
+      "2",
+      "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines4)}
+    game, moves = Game.read_game(gamelines4)
+    assert_equal 5, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 1, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 16, game.deck.length
+
+    game.apply_move!('1 7 4'.to_move(game))
+    assert_equal 6, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+    
+    gamelines5 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",  "2 0 7",  "2 7 4",
+      "2",
+      "bottle", "hat", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines5)}
+    game, moves = Game.read_game(gamelines5)
+    assert_equal 6, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+
+    game.players_cards[2] << :keys
+    game.players_cards[2] << :hat
+    game.apply_move!('3 0 7'.to_move(game, true), 2)
+    assert_equal 7, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    game.apply_move!('3 0 10'.to_move(game, true))
+    assert_equal 8, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 2, game.moves_by_current_player   
+    
+    gamelines6 = ["5", "cell", "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 5", "1 0 4", "1 5 4", "2 0 2",  "2 0 7",  "2 7 4",
+      "3",
+      "bottle", "hat", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines6)}
+    game, moves = Game.read_game(gamelines6)
+    assert_equal 6, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+
+    game.apply_move!('2 0 7'.to_move(game))
+    assert_equal 7, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 2, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+
+    game.apply_move!('2 0 10'.to_move(game))
+    assert_equal 8, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 2, game.moves_by_current_player   
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 1, game.players_cards[2].length
+    assert_equal 3, game.players_cards[3].length
+    assert_equal 3, game.players_cards[4].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, game.deck.length
+    
+    # Need to do a test with a very long sequence of moves, 
+    # to test that the card dealing works properly when the deck is exhausted
+    
+  end
+  
+  def test_dealing
+    game = Game.new(5,6,6)
+    game.set_testing_game!
+    0.upto(4) do |i|
+      assert_equal $INITIAL_CARDS_PER_PLAYER, game.players_cards[i].length
+    end
+    assert_equal $SYMBOLS.length * $CARDS_PER_SYMBOL - 5 * $INITIAL_CARDS_PER_PLAYER, game.deck.length
+    assert_equal [:gun,    :hat,   :skull], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :keys,  :keys],  game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :hat,   :keys],  game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :skull, :skull], game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :gun,   :gun],   game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal 5, game.players_cards.length
+    assert_equal 5, game.players.length
+    assert_equal [:hat, :dagger, :hat, :hat, :skull, :bottle, :bottle, :bottle, :dagger, 
+      :keys, :hat, :dagger, :skull, :skull, :dagger, :gun, :skull, :gun, 
+      :gun, :skull, :keys, :keys, :gun, :hat, :dagger, :keys, :dagger,
+      :hat, :gun, :skull, :keys, :gun, :skull, :gun, :hat, :gun, :dagger,
+      :gun, :gun, :hat, :hat, :bottle, :gun, :dagger, :hat, :skull, :dagger,
+      :bottle, :hat, :skull, :gun, :bottle, :keys, :hat, :bottle, :keys,
+      :dagger, :bottle, :bottle, :dagger, :keys, :dagger, :dagger, :dagger,
+      :skull, :hat, :dagger, :dagger, :hat, :keys, :skull, :bottle, :skull,
+      :keys, :bottle, :keys, :bottle, :gun, :keys, :hat, :keys, :dagger,
+      :gun, :skull, :keys, :bottle, :skull], 
+      game.deck
+    game.deal_cards!(86, 0)
+    assert_equal 89, game.players_cards[0].length
+    assert_equal 1,  game.deck.length
+    assert_equal [:hat], game.deck
+
+    game.players_cards[0] = [:hat, :hat, :hat, :hat, :hat]
+    game.players_cards[1] = [:keys, :keys, :keys]
+    game.players_cards[2] = [:bottle, :dagger, :skull]
+    game.players_cards[3] = []
+    game.players_cards[4] = []
+    game.deal_cards!(1, 3)
+    
+    assert_equal [:hat, :hat, :hat, :hat, :hat], game.players_cards[0].sort_by {|c| c.to_s}
+    assert_equal [:keys, :keys, :keys],          game.players_cards[1].sort_by {|c| c.to_s}
+    assert_equal [:bottle, :dagger, :skull],     game.players_cards[2].sort_by {|c| c.to_s}
+    assert_equal [:hat],                         game.players_cards[3].sort_by {|c| c.to_s}
+    assert_equal [],                             game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal [], game.deck
+    
+    game.deal_cards!(1, 4)
+    assert_equal [:keys], game.players_cards[4].sort_by {|c| c.to_s}
+    assert_equal $CARDS_PER_SYMBOL - 6, game.deck.select {|c| c == :hat}.length
+    assert_equal $CARDS_PER_SYMBOL - 4, game.deck.select {|c| c == :keys}.length
+    assert_equal $CARDS_PER_SYMBOL - 1, game.deck.select {|c| c == :bottle}.length
+    assert_equal $CARDS_PER_SYMBOL - 1, game.deck.select {|c| c == :dagger}.length
+    assert_equal $CARDS_PER_SYMBOL - 1, game.deck.select {|c| c == :skull}.length
+    assert_equal $CARDS_PER_SYMBOL,     game.deck.select {|c| c == :gun}.length
+    assert_equal $CARDS_PER_SYMBOL * 6 - 13, game.deck.length
+
+  end
+  
+  def test_long_deal
+    gamelines0 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1",
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines0)}
+    game, moves = Game.read_game(gamelines0)
+    assert_equal 0, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 3, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 9, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 9, game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines1 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1 gun", "1 0 2 keys", "1 2 1",
+      "2",
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines1)}
+    game, moves = Game.read_game(gamelines1)
+    assert_equal 3, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 2, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 10, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 2, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 10, game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 1, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 1, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 0, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines2 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1", "1 0 2", "1 2 1",
+      "2 0 2", "2 0 3", "2 3 2",
+      "3 0 3", "3 0 4", "3 4 3",
+      "1 0 4", "1 0 5", "1 5 4",
+      "2 0 5", "2 0 6", "2 6 5",
+      "3 0 6", "3 0 11", "3 11 6",
+      "1",
+      "hat"]
+    assert_nothing_raised {Game.read_game(gamelines2)}
+    game, moves = Game.read_game(gamelines2)
+    assert_equal 18, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 1, game.players_cards[0].length
+    assert_equal 1, game.players_cards[1].length
+    assert_equal 1, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 6), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 12, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 6), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 2, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines2 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1",
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines2)}
+    game, moves = Game.read_game(gamelines2)
+    assert_equal 30, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 3, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 18), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 18, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 18), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 3, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines3 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3",
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines3)}
+    game, moves = Game.read_game(gamelines3)
+    assert_equal 42, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 5, game.players_cards[0].length
+    assert_equal 5, game.players_cards[1].length
+    assert_equal 5, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 30), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 24, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 30), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 4, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines4 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", "hat"]
+    assert_nothing_raised {Game.read_game(gamelines4)}
+    game, moves = Game.read_game(gamelines4)
+    assert_equal 54, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 7, game.players_cards[0].length
+    assert_equal 7, game.players_cards[1].length
+    assert_equal 7, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 42), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 30, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 42), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 5, (unplayed_cards.select {|c| c == :skull}).length
+
+    
+    gamelines5 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", "hat", "skull", "bottle", "gun", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines5)}
+    game, moves = Game.read_game(gamelines5)
+    assert_equal 78, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 11, game.players_cards[0].length
+    assert_equal 11, game.players_cards[1].length
+    assert_equal 11, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 66), game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 42, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 66), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 7, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines6 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines6)}
+    game, moves = Game.read_game(gamelines6)
+    assert_equal 102, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 90), game.deck.length
+    assert_equal 3, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 54, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - (9 + 90), game.deck.length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines7 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines7)}
+    game, moves = Game.read_game(gamelines7)
+    assert_equal 104, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 16, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 55, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines8 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "skull", "bottle", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines8)}
+    game, moves = Game.read_game(gamelines8)
+    assert_equal 105, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 3, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 56, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 9,  (unplayed_cards.select {|c| c == :skull}).length
+
+
+    gamelines9 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 12", "2 12 10",
+      "2",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle"]
+    assert_nothing_raised {Game.read_game(gamelines9)}
+    game, moves = Game.read_game(gamelines9)
+    assert_equal 107, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 2, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 15, game.players_cards[1].length
+    assert_equal 15, game.players_cards[2].length
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 45, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 0, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 0,  (unplayed_cards.select {|c| c == :skull}).length
+  
+  gamelines10 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "hat", "hat", "hat", "hat", "hat", 
+      "hat", "hat", "hat"]
+    assert_raise(InvalidMoveError) {Game.read_game(gamelines10)} # Not enough skull cards to go round
+    
+    gamelines11 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "skull", "bottle", "dagger"]
+    assert_nothing_raised {Game.read_game(gamelines11)}
+    game, moves = Game.read_game(gamelines11)
+    assert_equal 111, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 15, game.players_cards[0].length
+    assert_equal 12, game.players_cards[1].length
+    assert_equal 12, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 62, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines12 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2",
+      "hat", "skull", "bottle", "gun", "dagger", "keys", 
+      "hat", "skull", "bottle", "gun", "dagger", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines12)}
+    game, moves = Game.read_game(gamelines12)
+    assert_equal 114, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 12, game.players_cards[0].length
+    assert_equal 12, game.players_cards[1].length
+    assert_equal 12, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 65, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 14, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines13 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3",
+      "skull", "skull", "bottle", "gun", "dagger", "keys", 
+      "bottle", "skull", "bottle", "gun", "dagger", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines13)}
+    game, moves = Game.read_game(gamelines13)
+    assert_equal 117, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 12, game.players_cards[0].length
+    assert_equal 9,  game.players_cards[1].length
+    assert_equal 12, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 68, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines14 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 37",
+      "3",
+      "skull", "skull", "bottle", "gun", "dagger", "keys", 
+      "bottle", "skull", "bottle", "gun", "dagger"]
+    assert_raise(InvalidMoveError) {Game.read_game(gamelines14)}
+
+    gamelines15 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1",
+      "skull", "skull", "bottle", "gun", "dagger", "keys", 
+      "bottle", "skull", "bottle", "gun", "dagger", "keys"]
+    assert_nothing_raised {Game.read_game(gamelines15)}
+    game, moves = Game.read_game(gamelines15)
+    assert_equal 120, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 12, game.players_cards[0].length
+    assert_equal 9,  game.players_cards[1].length
+    assert_equal 9,  game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 71, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 13, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines16 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2",
+      "skull", "skull", "keys", "gun", "dagger", "keys", 
+      "skull", "skull", "skull"]
+    assert_nothing_raised {Game.read_game(gamelines16)}
+    game, moves = Game.read_game(gamelines16)
+    assert_equal 123, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 9, game.players_cards[0].length
+    assert_equal 9, game.players_cards[1].length
+    assert_equal 9, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 74, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 16, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines17 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3",
+      "skull", "skull", "keys", "gun", "dagger", "keys", 
+      "skull", "skull", "skull"]
+    assert_nothing_raised {Game.read_game(gamelines17)}
+    game, moves = Game.read_game(gamelines17)
+    assert_equal 126, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 9, game.players_cards[0].length
+    assert_equal 6, game.players_cards[1].length
+    assert_equal 9, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 77, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 13, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines18 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1",
+      "skull", "skull", "keys", "gun", "dagger", "keys", 
+      "skull", "skull", "skull"]
+    assert_nothing_raised {Game.read_game(gamelines18)}
+    game, moves = Game.read_game(gamelines18)
+    assert_equal 129, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 9, game.players_cards[0].length
+    assert_equal 6, game.players_cards[1].length
+    assert_equal 6, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 80, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 15, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines19 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2",
+      "skull", "skull", "gun", "gun", "dagger", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines19)}
+    game, moves = Game.read_game(gamelines19)
+    assert_equal 132, game.history.length
+    assert_equal 1, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 6, game.players_cards[0].length
+    assert_equal 6, game.players_cards[1].length
+    assert_equal 6, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 83, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines20 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3",
+      "skull", "skull", "gun", "gun", "dagger", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines20)}
+    game, moves = Game.read_game(gamelines20)
+    assert_equal 135, game.history.length
+    assert_equal 2, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 6, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 6, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 86, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 14, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines21 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3 18 30 skull", "3 20 37 skull", "3 21 37 skull",
+      "1",
+      "gun", "gun", "gun", "gun", "gun", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines21)}
+    game, moves = Game.read_game(gamelines21)
+    assert_equal 138, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 0, game.moves_by_current_player
+    assert_equal 6, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 89, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 11, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :skull}).length
+
+    gamelines22 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3 18 30 skull", "3 20 37 skull", "3 21 37 skull",
+      "1 35 37 gun",
+      "1",
+      "gun", "gun", "gun", "gun", "gun"]
+    assert_nothing_raised {Game.read_game(gamelines22)}
+    game, moves = Game.read_game(gamelines22)
+    assert_equal 139, game.history.length
+    assert_equal 0, game.current_player
+    assert_equal 1, game.moves_by_current_player
+    assert_equal 5, game.players_cards[0].length
+    assert_equal 3, game.players_cards[1].length
+    assert_equal 3, game.players_cards[2].length
+    assert_equal 1, game.deck.length
+    unplayed_cards = game.players_cards.inject(game.deck.dup) {|acc, cs| acc.concat cs}
+    assert_equal $CARDS_PER_SYMBOL * $SYMBOLS.length - 90, unplayed_cards.length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :bottle}).length
+    assert_equal $CARDS_PER_SYMBOL - 10, (unplayed_cards.select {|c| c == :dagger}).length
+    assert_equal $CARDS_PER_SYMBOL - 12, (unplayed_cards.select {|c| c == :gun}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :hat}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :keys}).length
+    assert_equal $CARDS_PER_SYMBOL - 17, (unplayed_cards.select {|c| c == :skull}).length
+    
+    assert_raise(GameWonNotice) {game.apply_move!("1 36 37 gun".to_move(game, true))}
+    assert_raise(InvalidMoveError) {game.apply_move!("1 36 37".to_move(game, true))}
+
+    gamelines23 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3 18 30 skull", "3 20 37 skull", "3 21 37 skull",
+      "1 35 37 gun", "1 36 37 gun",
+      "1",
+      "gun", "gun", "gun", "gun"]
+    assert_raise(GameWonNotice) {Game.read_game(gamelines23)}
+
+    gamelines24 = ["3", "cell", 
+      "gun", "keys", "dagger", "hat", "skull", "bottle",
+      "keys", "dagger", "skull", "hat", "gun", "bottle", 
+      "dagger", "bottle", "keys", "gun", "hat", "skull", 
+      "dagger", "skull", "bottle", "gun", "keys", "hat", 
+      "hat", "dagger", "keys", "bottle", "gun", "skull", 
+      "keys", "bottle", "skull", "dagger", "hat", "gun", "boat",
+      "1 0 1",  "1 0 2",  "1 2 1",
+      "2 0 2",  "2 0 3",  "2 3 2",
+      "3 0 3",  "3 0 4",  "3 4 3",
+      "1 0 4",  "1 0 5",  "1 5 4",
+      "2 0 5",  "2 0 6",  "2 6 5",
+      "3 0 6",  "3 0 11", "3 11 6",
+      "1 0 7",  "1 7 6",
+      "2 0 8",  "2 8 5",
+      "3 0 9",  "3 9 4",
+      "1 0 10", "1 10 3",
+      "2 0 11", "2 11 2",
+      "3 0 12", "3 12 1",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "2 2 8",  "2 8 2",  "3 3 9",  "3 9 3", 
+      "1 1 10", "1 10 1", "2 2 11", "2 11 2", "3 3 12", "3 12 3",
+      "1 1 7",  "1 7 1",  "1 1 10",
+      "2 2 13 dagger", "2 2 14 bottle", "2 2 15 keys",
+      "3 1 16 gun", "3 3 17 hat", "3 3 18 skull",
+      "1 1 24 hat", "1 3 25 hat", "1 4 35 hat",
+      "2 5 37 hat", "2 5 37 hat", "2 5 37 hat",
+      "3 4 12 bottle", "3 6 21 bottle", "3 6 28 bottle",
+      "1 4 32 bottle", "1 6 37 bottle", "1 10 37 bottle",
+      "2 13 37 bottle", "2 14 23 keys", "2 15 27 keys",
+      "3 12 20 skull", "3 16 31 keys", "3 17 37 keys",
+      "1 24 37 keys", "1 25 37 keys", "1 32 36 gun",
+      "2 23 30 skull", "2 27 33 skull", "2 30 37 skull",
+      "3 18 30 skull", "3 20 37 skull", "3 21 37 skull",
+      "1 35 37 gun", "1 36 37 gun",
+      "2 33 34 dagger",
+      "2",
+      "dagger", "dagger"]
+    assert_raise(GameWonNotice) {Game.read_game(gamelines24)}
+  end
+  
+end
\ No newline at end of file