diff --git a/CuckooChessEngine/.classpath b/CuckooChessEngine/.classpath
new file mode 100644
index 0000000..b0e3db7
--- /dev/null
+++ b/CuckooChessEngine/.classpath
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch b/CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch
new file mode 100644
index 0000000..c2dcb75
--- /dev/null
+++ b/CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CuckooChessEngine/.project b/CuckooChessEngine/.project
new file mode 100644
index 0000000..36bf309
--- /dev/null
+++ b/CuckooChessEngine/.project
@@ -0,0 +1,27 @@
+
+
+ CuckooChessEngine
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.ui.externaltools.ExternalToolBuilder
+ auto,full,incremental,
+
+
+ LaunchConfigHandle
+ <project>/.externalToolBuilders/BinBook_Builder.launch
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs b/CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..12a57ed
--- /dev/null
+++ b/CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Sun May 09 18:17:45 CEST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/CuckooChessEngine/src/book.bin b/CuckooChessEngine/src/book.bin
new file mode 100644
index 0000000..b53cf37
Binary files /dev/null and b/CuckooChessEngine/src/book.bin differ
diff --git a/CuckooChessEngine/src/book.txt b/CuckooChessEngine/src/book.txt
new file mode 100644
index 0000000..2bc0569
--- /dev/null
+++ b/CuckooChessEngine/src/book.txt
@@ -0,0 +1,228 @@
+# Philidors defense
+e4 e5 Nf3 d6 d4 exd4 Nxd4 Nf6 Nc3 Be7 Bf4 O-O Qd2 d5 exd5 Nxd5 Nxd5 Qxd5 Nb5 Qe4+
+e4 e5 Nf3 d6? d4 exd4 Qxd4 Nf6 Nc3 Be7 Bg5 Nc6 Bb5 O-O
+e4 e5 Nf3 d6? d4 Nd7 Bc4 c6 Ng5 Nh6 O-O Nb6
+
+# Nordic gambit
+e4 e5 d4 exd4 c3 dxc3 Bc4 cxb2 Bxb2 d5 Bxd5 Nf6 Bxf7+ Kxf7 Qxd8 Bb4+
+e4 e5 d4 exd4 c3? d5 exd5 Qxd5 cxd4 Nc6 Nf3 Bg4 Be2 Bb4 Nc3 Bxf3 Bxf3 Qc4
+e4 e5 d4 exd4 c3? d5 exd5 Qxd5 Nf3 Nc6 cxd4
+e4 e5 d4? exd4 c3? d5 exd5 Qxd5 Nf3 Nc6 Be2 Nf6
+e4 e5 d4? exd4 c3? d5 exd5 Qxd5 Nf3 Nc6 Be2 Bg4 O-O O-O-O cxd4
+e4 e5 d4? exd4 Qxd4 Nc6 Qe3 Nf6 Nc3 Bb4 Bd2 O-O O-O-O Re8 Bc4 d6
+
+# Scottish game
+e4 e5 Nf3 Nc6 d4 exd4 Nxd4 Nf6 Nc3 Bb4 Nxc6 bxc6 Bd3 d5 exd5
+e4 e5 Nf3 Nc6 d4 exd4 Nxd4 Bc5 Be3 Qf6 c3 Nge7
+e4 e5 Nf3 Nc6 d4 exd4 Nxd4 Bc5 Nb3 Bb6 a4 a5 Nc3 Qf6 Qe2 Nge7
+e4 e5 d4 exd4 Nf3 Nc6 Nxd4 Nf6 Nxc6 bxc6 e5 Qe7 Qe2 Nd5 c4 Ba6 b3 g6 f4 f6 Ba3 Qf7 Qd2 Nb6
+
+# Italian game
+e4 e5 Nf3 Nc6 Bc4 Bc5 d3 d6 Nc3 Nf6 Bg5 h6
+e4 e5 Nf3 Nc6 Bc4 Bc5 c3 Nf6 d4 exd4 cxd4 Bb4+ Bd2 Bxd2+ Nbxd2 d5 exd5 Nxd5 Qb3 Nce7
+e4 e5 Nf3 Nc6 Bc4 Bc5 c3 Qe7 d4 Bb6 O-O d6 h3 Nf6 Re1 O-O Na3
+e4 e5 Nf3 Nc6 Bc4 Bc5 c3 Nf6 d3 d6 O-O Qe7 b4 Bb6 a4 a6
+
+# Two knights defense
+e4 e5 Nf3 Nc6 Bc4 Nf6 Nc3 Nxe4 Nxe4 d5 Bd3
+e4 e5 Nf3 Nc6 Bc4 Nf6 Ng5 d5 exd5 Na5 Bb5+ c6 dxc6 bxc6 Be2 h6 Nf3 e4 Ne5 Qd4
+e4 e5 Nf3 Nc6 Bc4 Nf6 Ng5 d5 exd5 Na5 Bb5+ c6 dxc6 bxc6 Be2 h6 Nf3 e4 Ne5 Qc7
+e4 e5 Nf3 Nc6 Bc4 Nf6 d3 Bc5 Nc3 d6 Bg5 h6
+
+# Max Lange attack
+e4 e5 Nf3 Nc6 Bc4 Nf6 d4 exd4 O-O Bc5 e5 Ng4 Bf4 d6 exd6 Bxd6 Re1+ Kf8 Bxd6+ Qxd6 c3 Qc5
+e4 e5 Nf3 Nc6 Bc4 Bc5 O-O Nf6 d4 exd4 e5 d5 exf6 dxc4 Re1+ Be6 Ng5 Qd5 Nc3 Qf5
+e4 e5 Nf3 Nc6 d4 exd4 Bc4 Nf6 O-O Bc5
+e4 e5 Nf3 Nc6 d4 exd4 Bc4 Bc5 O-O Nf6
+
+# Skottish gambit
+e4 e5 Nf3 Nc6 Bc4 Nf6 d4 exd4 O-O Nxe4 Re1 d5 Bxd5 Qxd5 Nc3 Qa5 Nxe4 Be6
+e4 e5 Nf3 Nc6 d4 exd4 Bc4 Nf6 e5 d5 Bb5 Ne4 Nxd4 Bd7 Bxc6 bxc6 O-O Be7 f3 Nc5 f4 Ne4
+e4 e5 Nf3 Nc6 d4 exd4 Bc4 Nf6 e5 d5 Bb5 Ne4 Nxd4 Bd7 Bxc6 bxc6 O-O Bc5 Be3 O-O f3 Ng5 Qd2 f6 Kh1 Ne6
+e4 e5 Nf3 Nc6 d4 exd4 Bc4 Bc5 O-O d6 c3 Bg4 Qb3 Bxf3 Bxf7 Kf8 gxf3 Ne5 cxd4 Bxd4
+e4 e5 Nf3 Nc6 d4 exd4 c3 d5 exd5 Qxd5 cxd4 Bg4 Be2 Bb4 Nc3 Bxf3 Bxf3 Qc4 Qb3
+
+# Hungarian
+e4 e5 Nf3 Nc6 Bc4 Be7 d4 d6 dxe5 dxe5 Qxd8+ Bxd8
+
+# Three and four knights game
+e4 e5 Nf3 Nc6 Nc3 Nf6 Bb5 Nd4 Nxe5 Qe7 Nf3 Nxb5 Nxb5 Qxe4+ Qe2 Qxe2+ Kxe2 Nd5
+e4 e5 Nf3 Nf6 Nc3? Nc6 d4 exd4 Nxd4
+
+# Russian defense
+e4 e5 Nf3 Nf6 Nxe5 d6 Nf3 Nxe4 Qe2 Qe7 d3 Nf6 Bg5 Nbd7 Nc3 Qxe2+ Bxe2 h6 Bh4 g6
+e4 e5 Nf3 Nf6 Nxe5 d6 Nf3 Nxe4 d4 d5 Bd3 Be7 O-O Nc6 Re1 Bg4 c4 Nf6 cxd5 Nxd5 Nc3 O-O Be4 Be6
+e4 e5 Nf3 Nf6? d4 Nxe4 dxe5 d5 Nbd2 Nc6
+
+# Kings gambit
+e4 e5 f4 exf4 Nf3 d5 exd5 Nf6 Nc3 Nxd5 Nxd5 Qxd5 d4 Be7 c4 Qe4+ Be2 Nc6
+e4 e5 f4 exf4 Nf3 Be7 Bc4 Nf6 e5 Ng4 O-O Nc6 d4 d5 exd6 Qxd6
+
+# Spanish
+e4 e5 Nf3 Nc6 Bb5 d6 d4 Bd7 Nc3 Nf6 O-O Be7 Re1 exd4 Nxd4 O-O
+e4 e5 Nf3 Nc6 Bb5 Nf6 O-O Nxe4 Re1 Nd6 Nxe5 Be7 Bd3 O-O
+e4 e5 Nf3 Nc6 Bb5 Nf6 O-O Nxe4 d4 Nd6
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 h3 h6 d4 Re8 Nbd2 Bf8 Nf1 Bb7 Ng3 Na5 Bc2 Nc4 a4 d5
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 d6 c3 O-O d4 Bg4 d5 Na5 Bc2 c6 h3 Bc8 dxc6 Qc7 Nbd2 Qxc6 Nf1 Nc4
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 d6 c3 Na5 Bc2 c5 d4 Nc6 d5
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 d6 c3 Na5 Bc2 c5 d4 Nc6 h3 Qc7 d5
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 d6 c3 Na5 Bc2 c5 d4 cxd4 cxd4 Qc7
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 d6 c3 Na5 Bc2 c5 d3 Nc6 Nbd2 O-O Nf1 Re8 h3 h6 Ne3 Bf8
+Nf3? Nc6 e4 e5 Bb5 a6 Ba4 Nf6 O-O Nxe4 d4 b5 Bb3 d5 dxe5 Be6 c3 Bc5
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 d6 O-O Bd7 c3 g6 d4 Bg7 Re1 Nge7 Be3 O-O Nbd2 h6 dxe5 dxe5 Bb3 b6 a4
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 d6 c3 Bd7 d4 Nge7 Bb3 h6 Nbd2 Ng6 Nc4 Be7 Ne3 O-O
+e4 e5 Nf3 Nc6 Bb5 Nf6 O-O Bc5 c3 O-O d4 Bb6 Bg5 h6 Bh4 d6 a4 a5 Re1 exd4 Bxc6 bxc6 Nxd4
+e4 e5 Nf3 Nc6 Bb5 Nf6? O-O Bc5 Nxe5 Nxe5 d4 a6 Ba4 Nxe4 Qe2 Be7 Qxe4 Ng6
+e4 e5 Nf3 Nc6 Bb5 Nf6? O-O Bc5? Nc3? O-O d3 d6
+
+# Scandinavian
+e4 d5 exd5 Qxd5 Nc3 Qa5 d4 Nf6 Nf3 Bf5 Bc4 e6 Bd2 c6 Qe2 Bb4 Ne5 Nbd7 Nxd7 Nxd7 a3
+e4 d5 exd5 Qxd5 Nc3 Qa5 d4 c6 Nf3 Nf6 Bc4 Bg4 h3 Bh5 g4 Bg6 Bd2 Qb6 Qe2
+e4 d5 exd5 Nf6 d4 Nxd5 c4 Nb6 Nf3 g6 Nc3 Bg7 Be3 O-O h3 Nc6 Qd2 e5 d5
+e4 d5? exd5 Nf6 d4 Nxd5 Nf3 g6 c4 Nb6
+e4 d5? exd5 Nf6 d4 Nxd5 Nf3 g6 Be2 Bg7 O-O O-O c4 Nb6 Nc3 Nc6 d5 Ne5
+
+# Queens gambit accepted
+d4 d5 c4 dxc4 Nf3 Nf6 e3 Bg4 Bxc4 e6 h3 Bh5 Nc3
+d4 d5 c4 dxc4? e3 Nf6 Nf3 Bg4
+d4 d5 c4 dxc4? e3? Nf6 Nf3 e6 Bxc4 c5 O-O a6
+
+# Queens gambit declined
+d4 d5 c4 e6 Nc3 Nf6 Bg5 Nbd7 e3 Be7 Nf3 O-O Rc1 c6
+c4 e6 d4 d5 Nf3 Be7 Nc3 Nf6 Bg5 O-O e3 h6
+c4 Nf6 Nc3 e6 Nf3 d5 d4 Be7 Bg5 O-O e3 Nbd7 Qc2 c5
+c4? Nf6 Nc3 e6 Nf3 d5 d4 Be7 e3 O-O Bd3 c5
+Nf3? d5 d4 Nf6 c4 e6 Nc3 Be7 Bf4 O-O e3 c5 dxc5 Bxc5 Qc2 Nc6
+d4 d5 c4 c6 Nf3 Nf6 Nc3 e6 e3 Nbd7 Bd3 dxc4 Bxc4 b5 Bd3 a6 O-O
+d4 d5 c4 c6? cxd5 cxd5 Nc3 Nf6 Bf4 Nc6 e3 a6
+d4 d5 c4 c6? Nc3 Nf6 e3 e6 Nf3
+
+# Tarrasch defense
+d4 d5 c4 e6 Nc3 c5 cxd5 exd5 Nf3 Nc6 g3 Nf6 Bg2 Be7 O-O O-O Bg5 cxd4 Nxd4 h6
+
+# Buddapest defense
+d4 Nf6 c4 e5 dxe5 Ng4 Nf3 Bc5 e3 Nc6 Be2 Ngxe5 O-O d6
+
+# Sicilian
+e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 e6 Nc3 Qc7
+e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 Nf6 Nc3 e5 Ndb5 d6 Bg5 a6 Na3 b5
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 f4 e5 Nf3 Qc7 Bd3
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 f4 e6 Qf3 Qb6 Nb3 Qc7
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 f4 Nbd7? Be2
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Be2 e5 Nb3 Be7 O-O O-O Be3
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Be3 e5 Nb3 Be6 Qd2 Nbd7 f3 b5
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Bg5 e6 f4 Be7 Qf3 Qc7 O-O-O Nbd7 g4 b5
+e4 c5 Nf3 e6 d4 cxd4 Nxd4 a6 Bd3 Nf6 O-O Qc7 Qe2 d6 c4 g6 Nc3 Bg7 Rd1 O-O
+e4 c5 Nf3 e6 d4 cxd4 Nxd4 Nf6 Nc3 d6 Be2 a6 O-O Be7 f4 O-O
+e4 c5 Nf3 e6 d4 cxd4 Nxd4 Nc6 Nc3 Qc7 Be3 a6 Bd3 Nf6 O-O Ne5 h3 Bc5 Qe2 d6
+e4 c5 Nf3 e6? Nc3 Nc6 d4 cxd4 Nxd4 Qc7 Be3 a6 Qd2 Nf6 O-O-O Be7
+e4 c5 Nc3 Nc6 Nge2 g6 d4 cxd4 Nxd4 Bg7 Be3 Nf6 Bc4 O-O Bb3 d6
+e4 c5 Nc3? Nc6 g3 g6 Bg2 Bg7 d3 d6 f4 e6 Nf3 Nge7 O-O O-O
+e4 c5 Nc3? e6 Nf3 Nc6 d4 cxd4 Nxd4 Qc7 Be2 a6 O-O Nf6 Be3 Bb4
+e4 c5 Nc3? a6 Nf3 d6 d4 cxd4 Nxd4 Nf6
+Nc3? c5 Nf3 Nc6 d4 cxd4 Nxd4 Nf6 e4 d6 Bg5 e6 Qd2 a6 O-O-O Bd7 f4 b5
+e4 c5 d4 cxd4 c3 dxc3 Nxc3 Nc6 Nf3 d6 Bc4 e6 O-O Nf6 Qe2 Be7 Rd1 e5
+e4 c5 c3 d5 exd5 Qxd5 d4 Nf6 Nf3 Bg4 Be2 e6 O-O Nc6 Be3 cxd4 cxd4 Be7
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3 Bg7 f3 O-O Qd2 Nc6 Bc4 Bd7 O-O-O Rc8 Bb3 Ne5 h4 Nc4 Bxc4 Rxc4 g4 Qa5
+
+# French defense
+d4 e6 e4 d5 exd5 exd5 Nf3 Nf6 Bd3 Bd6 O-O O-O Bg5 Bg4 Nbd2 Nbd7 c3 c6 Qc2 Qc7
+e4 e6 d4 d5 exd5 exd5 Bd3 Bd6 Nf3 Nf6
+e4 e6 d4 d5 exd5 exd5 Bd3 Bd6 Nf3 Ne7 O-O O-O Bg5 f6 Bd2 Bf5
+e4 e6 d4 d5 e5 c5 c3 Nc6 Nf3 Qb6 Be2 cxd4 cxd4 Nge7 Nc3 Nf5 Na4 Qa5+ Bd2 Bb4 Bc3
+e4 e6 d4 d5 e5 c5 c3 Nc6 Nf3 Qb6 Bd3 cxd4 cxd4 Bd7
+e4 e6 d4 d5 e5 c5 c3 Nc6 Nf3 Qb6 a3 c4 Nbd2 Na5 Be2 Bd7
+e4 e6 d4 d5 Nc3 Nf6 Bg5 Be7 e5 Nfd7 Bxe7
+e4 e6 d4 d5 Nd2 Nf6 e5 Nfd7 Bd3 c5 c3 Nc6 Ne2 cxd4 cxd4 f6 exf6 Nxf6
+e4 e6? d4 d5 Nc3 Nf6 e5 Nfd7 f4 c5 Nf3 Nc6 Be3 cxd4 Nxd4 Bc5 Qd2 O-O O-O-O a6
+e4 e6? d4 d5 Nc3 Bb4 e5 c5 a3 Bxc3 bxc3 Ne7 Qg4 Qc7 Qxg7 Rg8 Qxh7 cxd4 Ne2 Nbc6 f4 Bd7
+e4 e6? d4 d5 Nc3 Bb4 e5 c5 Qg4
+e4 e6? d4 c5? d5 exd5 exd5 d6
+e4 e6 d3 d5 Nd2 c5 Ngf3 Nc6 g3 Nf6 Bg2 Be7 O-O O-O
+
+# Caro Kann defense
+e4 c6 d4 d5 Nc3 dxe4 Nxe4 Bf5 Ng3 Bg6 h4 h6 Nf3 Nd7
+e4 c6 d4 d5 Nd2 dxe4 Nxe4
+e4 c6 d4 d5 exd5 cxd5 c4 Nf6 Nc3 e6 Nf3 Be7
+e4 c6 d4 d5 e5 Bf5 Nf3 e6 Be2 c5 O-O Nc6 c3 cxd4 cxd4 Nge7 Nc3 Nc8 Be3 Nb6 Rc1 Be7
+e4 c6? d3 d5 Nd2 e5 Ngf3 Bd6 g3 Nf6 Bg2 O-O O-O
+e4 c6? d4 d5 Nc3 dxe4 Nxe4 Bf5 Ng3 Bg6 Nf3 Nd7 h4 h6 h5 Bh7 Bd3 Bxd3 Qxd3 e6
+
+# Aljechins defense
+e4 Nf6 e5 Nd5 d4 d6 c4 Nb6 exd6 cxd6 Be3 g6
+e4 Nf6 e5 Nd5 c4 Nb6 d4 d6 exd6 cxd6 Nf3 g6 Be2 Bg7 O-O O-O Nc3 Nc6 Be3 Bg4 b3 d5
+e4 Nf6 e5 Nd5 d4 d6 Nf3 Bg4 Be2 e6 c4 Nb6 exd6 cxd6
+e4 Nf6? Nc3 d5 e5 Nfd7 d4 e6 f4 c5 Nf3 Nc6 Be3 a6 Qd2 b5 dxc5 Bxc5 Bxc5 Nxc5
+e4 Nf6? Nc3 d5 e5 Nfd7 d4 e6 f4 c5 Nf3 Nc6 Be3 cxd4 Nxd4 Bc5 Qd2 O-O O-O-O a6
+e4 Nf6? e5 Nd5 c4 Nb6 d4 d6 Nf3 Bg4 exd6 exd6 Be2 Be7 O-O O-O Nc3 Nc6 b3 Bf6 Be3 d5
+
+# Kings indian
+d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5 O-O Nc6
+d4 Nf6 c4 g6 Nc3 Bg7 g3 O-O Bg2 d6 Nf3 Nbd7 O-O e5
+c4 Nf6 Nf3 g6 d4 Bg7 Nc3 O-O e4 d6 Be2
+c4 Nf6 Nc3 g6 d4 d5 cxd5 Nxd5 e4 Nxc3 bxc3 Bg7 Bc4 c5
+d4 Nf6 c4 g6 Nc3 d5 cxd5 Nxd5 e4 Nxc3 bxc3 Bg7 Nf3 c5
+Nf3? Nf6 c4 g6 Nc3 Bg7 d4 O-O
+
+# Queen indian
+d4 Nf6 c4 e6 Nf3 b6 g3 Bb7 Bg2 Be7
+c4 Nf6 d4 e6 Nf3 b6 Nc3 Bb7 a3 d5 cxd5 Nxd5 Qc2
+d4 e6 Nf3 Nf6 c4 b6 g3 Bb7 Bg2 Bb4 Bd2 Bxd2 Qxd2
+
+# Nimzo indian
+d4 e6 c4 Nf6 Nc3 Bb4 Bg5 h6 Bh4 c5 d5 d6
+c4 e6 d4 Nf6 Nc3 Bb4 a3 Bxc3+ bxc3 c5 f3 d5
+d4 Nf6 c4 e6 Nc3 Bb4 Qc2 d5 a3 Bxc3+ Qxc3 Ne4 Qc2 Nc6 e3 e5
+d4 Nf6 c4 e6 Nc3 Bb4 Nf3 O-O Bg5 c5 Rc1 cxd4
+d4 Nf6 c4 e6 Nc3 Bb4 f3 d5
+d4 Nf6 c4 e6 Nc3 Bb4 e3 c5 Bd3 d5 Nf3 O-O O-O Nc6
+d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Bd3 d5 Nf3 c5 O-O
+c4 Nf6 Nc3 e6 d4
+
+# Benoni
+d4 Nf6 Nf3 e6 c4 c5 d5 exd5 cxd5 d6 Nc3 g6 e4 Bg7
+d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 f4 Bg7 Bb5 Nfd7 a4 O-O Nf3 Na6 O-O Nc7
+d4 c5 d5 Nf6 c4 e6 Nc3 exd5 cxd5 d6 Nf3 g6 Bf4 a6 a4 Bg7 e4 O-O
+c4 Nf6 d4 g6 Nc3 Bg7 g3 O-O Bg2 c5 d5 e6 Nf3 exd5 cxd5 d6 O-O
+d4 c5? d5 Nf6 c4 e6 g3 exd5 cxd5 d6 Nc3 g6 Nf3 Bg7 Bg2 O-O
+
+# Reti's opening
+Nf3 d5 g3 g6 Bg2 Bg7 O-O e5 d3 Ne7 Nbd2 O-O c4 c6
+Nf3 Nf6 g3 g6 c4 Bg7 Bg2 O-O O-O c5 d4 d6 d5 Na6 Nc3 Nc7
+Nf3? Nf6 g3 d5 d4 c5 Bg2 Nc6 O-O g6
+Nf3? Nf6 g3 d5 Bg2 c6 d4 Bf5 O-O g6
+Nf3? d5 d4 Nf6 c4 e6 g3 dxc4 Bg2 Nc6 Qa4 Bb4 Bd2 Nd5 Bxb4 Nxb4 O-O Rb8
+Nf3? c5 c4 Nf6 Nc3 e6 g3 Be7 Bg2 O-O O-O a6 d4 cxd4 Nxd4 Qc7
+g3 g6 Bg2 Bg7 c4 Nf6 Nc3 O-O Nf3 d6 d4 Nbd7 O-O e5 e4
+g3? d5 Bg2 Nf6 Nf3 c6 O-O Bf5 d3 e6 Nbd2 h6 b3 Be7 Bb2 O-O
+g3? Nf6 Bg2 d5 d3 c6 Nd2 e5 e4 Bd6 Ngf3 O-O O-O
+g3? d5 Nf3 Nf6 Bg2 e6 O-O Be7 d3 O-O Nbd2 c5 e4 Nc6
+g3? e5 Bg2 d5 d3 Nf6 Nf3 Nc6 O-O Be7 c4 O-O cxd5 Nxd5 Nc3 Be6
+
+# Dutch
+d4 f5 g3 Nf6 Bg2 g6 Nf3 Bg7 O-O O-O c4 d6 Nc3 Qe8 d5 Na6
+c4 f5? d4 Nf6 Nc3 g6 Nf3 Bg7 e3 O-O Be2 d6 O-O Nc6
+d4 f5? Nf3 Nf6 g3 g6 Bg2 Bg7 c4 O-O Nc3 d6 O-O Nc6 d5 Ne5
+
+# Less usual openings
+Nc3? d5 e4 d4 Nce2 e5 Ng3 Be6 Nf3 Nd7 c3 c5 Bb5 Bd6 O-O a6
+Nc3? d5 d4 Nf6 Bg5 Nbd7 Nf3 h6 Bh4 c6 e3 e6 Bd3 Be7 O-O O-O
+Nc3? d5 e4 c6 Nf3 Bg4 h3 Bh5 d4
+Nc3? e5 e4 Nf6 Bc4 Nc6 d3 Bb4 Bg5 h6 Bxf6 Bxc3 bxc3 Qxf6 Ne2 d6
+c4 e5 Nc3 Nf6 Nf3 Nc6 g3 d5 cxd5 Nxd5 Bg2 Nb6 O-O Be7 d3 O-O a3 Be6 b4
+c4 e5 Nc3 Nf6 g3 d5 cxd5 Nxd5 Bg2 Nb6 Nf3 Nc6 O-O Be7 a3 O-O b4 Be6 d3
+f4 d5 Nf3 Nf6 e3 g6 Be2 Bg7 O-O O-O d3 c5 Qe1 Nc6 Nc3 Re8
+f4 d5 Nf3 g6 g3 Bg7 Bg2 Nf6 O-O O-O d3 c5 Nc3 d4 Ne4
+f4? d5 Nf3 g6 e3 Bg7 Be2 Nf6
+f4? d5 e3 Nf6 Nf3 Bg4 Be2 Nbd7 Ne5
+b3? e5 Bb2 Nc6 e3 d5 Bb5 Bd6 Nf3 Qe7 c4 Nf6
+e4 g6? d4 Bg7 Nc3 d6 f4 Nf6 Nf3 O-O Bd3 Nc6 O-O e5
+e4 d6 d4 Nf6 Nc3 g6 Nf3 Bg7
+e4 d6? d4 g6 Nc3 Bg7 Nf3 Nf6
+d4 d6? e4 Nf6 Nc3 g6 Be3 Bg7 Qd2 c6
+d4 g6 e4 Bg7 Nf3 d6 Nc3 Nf6 Be2 O-O O-O c6 h3 Qc7 Bf4 Nbd7 e5 dxe5 Nxe5 Nxe5 Bxe5 Qb6
+d4 g6 c4 Bg7 Nc3 d6 e4 Nf6 Nf3 O-O Be2 e5 O-O
+d4 g6? e4 Bg7 c4 d6 Nc3 Nc6 Be3 e5 d5 Nce7
+c4 Nf6 g3 g6 Bg2 Bg7 Nc3 O-O e4 d6 Nge2 c5 O-O Nc6 d3 a6 h3 Rb8 a4
+c4 Nf6 g3? e6 d4 d5 Nf3 dxc4 Bg2 a6 O-O Nc6
+b3? e6 Bb2 Nf6 e3 c5 Nf3 Be7 d4 O-O Bd3 d5 O-O Nc6
diff --git a/CuckooChessEngine/src/chess/BitBoard.java b/CuckooChessEngine/src/chess/BitBoard.java
new file mode 100644
index 0000000..15e7e26
--- /dev/null
+++ b/CuckooChessEngine/src/chess/BitBoard.java
@@ -0,0 +1,359 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+public class BitBoard {
+
+ /** Squares attacked by a king on a given square. */
+ public static final long[] kingAttacks;
+ public static final long[] knightAttacks;
+ public static final long[] wPawnAttacks, bPawnAttacks;
+
+ // Squares preventing a pawn from being a passed pawn, if occupied by enemy pawn
+ static final long[] wPawnBlockerMask, bPawnBlockerMask;
+
+ public static final long maskAToGFiles = 0x7F7F7F7F7F7F7F7FL;
+ public static final long maskBToHFiles = 0xFEFEFEFEFEFEFEFEL;
+ public static final long maskAToFFiles = 0x3F3F3F3F3F3F3F3FL;
+ public static final long maskCToHFiles = 0xFCFCFCFCFCFCFCFCL;
+
+ public static final long[] maskFile = {
+ 0x0101010101010101L,
+ 0x0202020202020202L,
+ 0x0404040404040404L,
+ 0x0808080808080808L,
+ 0x1010101010101010L,
+ 0x2020202020202020L,
+ 0x4040404040404040L,
+ 0x8080808080808080L
+ };
+
+ public static final long maskRow1 = 0x00000000000000FFL;
+ public static final long maskRow2 = 0x000000000000FF00L;
+ public static final long maskRow3 = 0x0000000000FF0000L;
+ public static final long maskRow4 = 0x00000000FF000000L;
+ public static final long maskRow5 = 0x000000FF00000000L;
+ public static final long maskRow6 = 0x0000FF0000000000L;
+ public static final long maskRow7 = 0x00FF000000000000L;
+ public static final long maskRow8 = 0xFF00000000000000L;
+ public static final long maskRow1Row8 = 0xFF000000000000FFL;
+
+ public static final long maskDarkSq = 0xAA55AA55AA55AA55L;
+ public static final long maskLightSq = 0x55AA55AA55AA55AAL;
+
+ public static final long maskCorners = 0x8100000000000081L;
+
+ static {
+ // Compute king attacks
+ kingAttacks = new long[64];
+
+ for (int sq = 0; sq < 64; sq++) {
+ long m = 1L << sq;
+ long mask = (((m >>> 1) | (m << 7) | (m >>> 9)) & maskAToGFiles) |
+ (((m << 1) | (m << 9) | (m >>> 7)) & maskBToHFiles) |
+ (m << 8) | (m >>> 8);
+ kingAttacks[sq] = mask;
+ }
+
+ // Compute knight attacks
+ knightAttacks = new long[64];
+ for (int sq = 0; sq < 64; sq++) {
+ long m = 1L << sq;
+ long mask = (((m << 6) | (m >>> 10)) & maskAToFFiles) |
+ (((m << 15) | (m >>> 17)) & maskAToGFiles) |
+ (((m << 17) | (m >>> 15)) & maskBToHFiles) |
+ (((m << 10) | (m >>> 6)) & maskCToHFiles);
+ knightAttacks[sq] = mask;
+ }
+
+ // Compute pawn attacks
+ wPawnAttacks = new long[64];
+ bPawnAttacks = new long[64];
+ wPawnBlockerMask = new long[64];
+ bPawnBlockerMask = new long[64];
+ for (int sq = 0; sq < 64; sq++) {
+ long m = 1L << sq;
+ long mask = ((m << 7) & maskAToGFiles) | ((m << 9) & maskBToHFiles);
+ wPawnAttacks[sq] = mask;
+ mask = ((m >>> 9) & maskAToGFiles) | ((m >>> 7) & maskBToHFiles);
+ bPawnAttacks[sq] = mask;
+
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ m = 0;
+ for (int y2 = y+1; y2 < 8; y2++) {
+ if (x > 0) m |= 1L << Position.getSquare(x-1, y2);
+ m |= 1L << Position.getSquare(x , y2);
+ if (x < 7) m |= 1L << Position.getSquare(x+1, y2);
+ }
+ wPawnBlockerMask[sq] = m;
+ m = 0;
+ for (int y2 = y-1; y2 >= 0; y2--) {
+ if (x > 0) m |= 1L << Position.getSquare(x-1, y2);
+ m |= 1L << Position.getSquare(x , y2);
+ if (x < 7) m |= 1L << Position.getSquare(x+1, y2);
+ }
+ bPawnBlockerMask[sq] = m;
+ }
+ }
+
+ private final static long[][] rTables;
+ private final static long[] rMasks;
+ private final static int[] rBits = { 12, 11, 11, 11, 11, 11, 11, 12,
+ 11, 10, 10, 10, 10, 10, 10, 11,
+ 11, 10, 10, 10, 10, 10, 10, 11,
+ 11, 10, 10, 10, 10, 10, 10, 11,
+ 11, 10, 10, 10, 10, 10, 10, 11,
+ 11, 10, 10, 10, 10, 10, 10, 11,
+ 10, 9, 9, 9, 9, 9, 10, 10,
+ 11, 10, 10, 10, 10, 11, 11, 11 };
+ private final static long[] rMagics = {
+ 0x0080011084624000L, 0x1440031000200141L, 0x2080082004801000L, 0x0100040900100020L,
+ 0x0200020010200408L, 0x0300010008040002L, 0x040024081000a102L, 0x0080003100054680L,
+ 0x1100800040008024L, 0x8440401000200040L, 0x0432001022008044L, 0x0402002200100840L,
+ 0x4024808008000400L, 0x100a000410820008L, 0x8042001144020028L, 0x2451000041002082L,
+ 0x1080004000200056L, 0xd41010c020004000L, 0x0004410020001104L, 0x0000818050000800L,
+ 0x0000050008010010L, 0x0230808002000400L, 0x2000440090022108L, 0x0488020000811044L,
+ 0x8000410100208006L, 0x2000a00240100140L, 0x2088802200401600L, 0x0a10100180080082L,
+ 0x0000080100110004L, 0x0021002300080400L, 0x8400880400010230L, 0x2001008200004401L,
+ 0x0000400022800480L, 0x00200040e2401000L, 0x4004100084802000L, 0x0218800800801002L,
+ 0x0420800800800400L, 0x002a000402001008L, 0x0e0b000401008200L, 0x0815908072000401L,
+ 0x1840008002498021L, 0x1070122002424000L, 0x1040200100410010L, 0x0600080010008080L,
+ 0x0215001008010004L, 0x0000020004008080L, 0x1300021051040018L, 0x0004040040820001L,
+ 0x48fffe99fecfaa00L, 0x48fffe99fecfaa00L, 0x497fffadff9c2e00L, 0x613fffddffce9200L,
+ 0xffffffe9ffe7ce00L, 0xfffffff5fff3e600L, 0x2000080281100400L, 0x510ffff5f63c96a0L,
+ 0xebffffb9ff9fc526L, 0x61fffeddfeedaeaeL, 0x53bfffedffdeb1a2L, 0x127fffb9ffdfb5f6L,
+ 0x411fffddffdbf4d6L, 0x0005000208040001L, 0x264038060100d004L, 0x7645fffecbfea79eL,
+ };
+ private final static long[][] bTables;
+ private final static long[] bMasks;
+ private final static int[] bBits = { 5, 4, 5, 5, 5, 5, 4, 5,
+ 4, 4, 5, 5, 5, 5, 4, 4,
+ 4, 4, 7, 7, 7, 7, 4, 4,
+ 5, 5, 7, 9, 9, 7, 5, 5,
+ 5, 5, 7, 9, 9, 7, 5, 5,
+ 4, 4, 7, 7, 7, 7, 4, 4,
+ 4, 4, 5, 5, 5, 5, 4, 4,
+ 5, 4, 5, 5, 5, 5, 4, 5 };
+ private final static long[] bMagics = {
+ 0xffedf9fd7cfcffffL, 0xfc0962854a77f576L, 0x9010210041047000L, 0x52242420800c0000L,
+ 0x884404220480004aL, 0x0002080248000802L, 0xfc0a66c64a7ef576L, 0x7ffdfdfcbd79ffffL,
+ 0xfc0846a64a34fff6L, 0xfc087a874a3cf7f6L, 0x02000888010a2211L, 0x0040044040801808L,
+ 0x0880040420000000L, 0x0000084110109000L, 0xfc0864ae59b4ff76L, 0x3c0860af4b35ff76L,
+ 0x73c01af56cf4cffbL, 0x41a01cfad64aaffcL, 0x1010000200841104L, 0x802802142a006000L,
+ 0x0a02000412020020L, 0x0000800040504030L, 0x7c0c028f5b34ff76L, 0xfc0a028e5ab4df76L,
+ 0x0020082044905488L, 0xa572211102080220L, 0x0014020001280300L, 0x0220208058008042L,
+ 0x0001010000104016L, 0x0005114028080800L, 0x0202640000848800L, 0x040040900a008421L,
+ 0x400e094000600208L, 0x800a100400120890L, 0x0041229001480020L, 0x0000020080880082L,
+ 0x0040002020060080L, 0x1819100100c02400L, 0x04112a4082c40400L, 0x0001240130210500L,
+ 0xdcefd9b54bfcc09fL, 0xf95ffa765afd602bL, 0x008200222800a410L, 0x0100020102406400L,
+ 0x80a8040094000200L, 0x002002006200a041L, 0x43ff9a5cf4ca0c01L, 0x4bffcd8e7c587601L,
+ 0xfc0ff2865334f576L, 0xfc0bf6ce5924f576L, 0x0900420442088104L, 0x0062042084040010L,
+ 0x01380810220a0240L, 0x0000101002082800L, 0xc3ffb7dc36ca8c89L, 0xc3ff8a54f4ca2c89L,
+ 0xfffffcfcfd79edffL, 0xfc0863fccb147576L, 0x0050009040441000L, 0x00139a0000840400L,
+ 0x9080000412220a00L, 0x0000002020010a42L, 0xfc087e8e4bb2f736L, 0x43ff9e4ef4ca2c89L,
+ };
+
+ private static final long createPattern(int i, long mask) {
+ long ret = 0L;
+ for (int j = 0; ; j++) {
+ long nextMask = mask & (mask - 1);
+ long bit = mask ^ nextMask;
+ if ((i & (1L << j)) != 0)
+ ret |= bit;
+ mask = nextMask;
+ if (mask == 0)
+ break;
+ }
+ return ret;
+ }
+
+ private static final long addRookRays(int x, int y, long occupied, boolean inner) {
+ long mask = 0;
+ mask = addRay(mask, x, y, 1, 0, occupied, inner);
+ mask = addRay(mask, x, y, -1, 0, occupied, inner);
+ mask = addRay(mask, x, y, 0, 1, occupied, inner);
+ mask = addRay(mask, x, y, 0, -1, occupied, inner);
+ return mask;
+ }
+ private static final long addBishopRays(int x, int y, long occupied, boolean inner) {
+ long mask = 0;
+ mask = addRay(mask, x, y, 1, 1, occupied, inner);
+ mask = addRay(mask, x, y, -1, -1, occupied, inner);
+ mask = addRay(mask, x, y, 1, -1, occupied, inner);
+ mask = addRay(mask, x, y, -1, 1, occupied, inner);
+ return mask;
+ }
+
+ private static final long addRay(long mask, int x, int y, int dx, int dy,
+ long occupied, boolean inner) {
+ int lo = inner ? 1 : 0;
+ int hi = inner ? 6 : 7;
+ while (true) {
+ if (dx != 0) {
+ x += dx; if ((x < lo) || (x > hi)) break;
+ }
+ if (dy != 0) {
+ y += dy; if ((y < lo) || (y > hi)) break;
+ }
+ int sq = Position.getSquare(x, y);
+ mask |= 1L << sq;
+ if ((occupied & (1L << sq)) != 0)
+ break;
+ }
+ return mask;
+ }
+
+ static { // Rook magics
+ rTables = new long[64][];
+ rMasks = new long[64];
+ for (int sq = 0; sq < 64; sq++) {
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ rMasks[sq] = addRookRays(x, y, 0L, true);
+ int tableSize = 1 << rBits[sq];
+ long[] table = new long[tableSize];
+ for (int i = 0; i < tableSize; i++) table[i] = -1;
+ int nPatterns = 1 << Long.bitCount(rMasks[sq]);
+ for (int i = 0; i < nPatterns; i++) {
+ long p = createPattern(i, rMasks[sq]);
+ int entry = (int)((p * rMagics[sq]) >>> (64 - rBits[sq]));
+ long atks = addRookRays(x, y, p, false);
+ if (table[entry] == -1) {
+ table[entry] = atks;
+ } else if (table[entry] != atks) {
+ throw new RuntimeException();
+ }
+ }
+ rTables[sq] = table;
+ }
+ }
+
+ static { // Bishop magics
+ bTables = new long[64][];
+ bMasks = new long[64];
+ for (int sq = 0; sq < 64; sq++) {
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ bMasks[sq] = addBishopRays(x, y, 0L, true);
+ int tableSize = 1 << bBits[sq];
+ long[] table = new long[tableSize];
+ for (int i = 0; i < tableSize; i++) table[i] = -1;
+ int nPatterns = 1 << Long.bitCount(bMasks[sq]);
+ for (int i = 0; i < nPatterns; i++) {
+ long p = createPattern(i, bMasks[sq]);
+ int entry = (int)((p * bMagics[sq]) >>> (64 - bBits[sq]));
+ long atks = addBishopRays(x, y, p, false);
+ if (table[entry] == -1) {
+ table[entry] = atks;
+ } else if (table[entry] != atks) {
+ throw new RuntimeException();
+ }
+ }
+ bTables[sq] = table;
+ }
+ }
+
+ public static final long bishopAttacks(int sq, long occupied) {
+ return bTables[sq][(int)(((occupied & bMasks[sq]) * bMagics[sq]) >>> (64 - bBits[sq]))];
+ }
+
+ public static final long rookAttacks(int sq, long occupied) {
+ return rTables[sq][(int)(((occupied & rMasks[sq]) * rMagics[sq]) >>> (64 - rBits[sq]))];
+ }
+
+ static public final long[][] squaresBetween;
+ static {
+ squaresBetween = new long[64][];
+ for (int sq1 = 0; sq1 < 64; sq1++) {
+ squaresBetween[sq1] = new long[64];
+ for (int j = 0; j < 64; j++)
+ squaresBetween[sq1][j] = 0;
+ for (int dx = -1; dx <= 1; dx++) {
+ for (int dy = -1; dy <= 1; dy++) {
+ if ((dx == 0) && (dy == 0))
+ continue;
+ long m = 0;
+ int x = Position.getX(sq1);
+ int y = Position.getY(sq1);
+ while (true) {
+ x += dx; y += dy;
+ if ((x < 0) || (x > 7) || (y < 0) || (y > 7))
+ break;
+ int sq2 = Position.getSquare(x, y);
+ squaresBetween[sq1][sq2] = m;
+ m |= 1L << sq2;
+ }
+ }
+ }
+ }
+ }
+
+ private static final byte dirTable[] = {
+ -9, 0, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, 0, -7,
+ 0, 0, -9, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, -7, 0,
+ 0, 0, 0, -9, 0, 0, 0, 0, -8, 0, 0, 0, 0, -7, 0, 0,
+ 0, 0, 0, 0, -9, 0, 0, 0, -8, 0, 0, 0, -7, 0, 0, 0,
+ 0, 0, 0, 0, 0, -9, 0, 0, -8, 0, 0, -7, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, -9, -17, -8, -15, -7, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, -10, -9, -8, -7, -6, 0, 0, 0, 0, 0,
+ 0, -1, -1, -1, -1, -1, -1, -1, 0, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 7, 15, 8, 17, 9, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 7, 0, 0, 8, 0, 0, 9, 0, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0,
+ 0, 0, 0, 7, 0, 0, 0, 0, 8, 0, 0, 0, 0, 9, 0, 0,
+ 0, 0, 7, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 9, 0,
+ 0, 7, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 9
+ };
+
+ static public final int getDirection(int from, int to) {
+ int offs = to + (to|7) - from - (from|7) + 0x77;
+ return dirTable[offs];
+ }
+
+ public static final long southFill(long mask) {
+ mask |= (mask >>> 8);
+ mask |= (mask >>> 16);
+ mask |= (mask >>> 32);
+ return mask;
+ }
+
+ public static final long northFill(long mask) {
+ mask |= (mask << 8);
+ mask |= (mask << 16);
+ mask |= (mask << 32);
+ return mask;
+ }
+
+ private static final int trailingZ[] = {
+ 63, 0, 58, 1, 59, 47, 53, 2,
+ 60, 39, 48, 27, 54, 33, 42, 3,
+ 61, 51, 37, 40, 49, 18, 28, 20,
+ 55, 30, 34, 11, 43, 14, 22, 4,
+ 62, 57, 46, 52, 38, 26, 32, 41,
+ 50, 36, 17, 19, 29, 10, 13, 21,
+ 56, 45, 25, 31, 35, 16, 9, 12,
+ 44, 24, 15, 8, 23, 7, 6, 5
+ };
+
+ static public final int numberOfTrailingZeros(long mask) {
+ return trailingZ[(int)(((mask & -mask) * 0x07EDD5E59A4E28C2L) >>> 58)];
+ }
+}
diff --git a/CuckooChessEngine/src/chess/Book.java b/CuckooChessEngine/src/chess/Book.java
new file mode 100644
index 0000000..94d4059
--- /dev/null
+++ b/CuckooChessEngine/src/chess/Book.java
@@ -0,0 +1,280 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.io.BufferedReader;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Implements an opening book.
+ * @author petero
+ */
+public class Book {
+ public static class BookEntry {
+ Move move;
+ int count;
+ BookEntry(Move move) {
+ this.move = move;
+ count = 1;
+ }
+ }
+ private static Map> bookMap;
+ private static Random rndGen;
+ private static int numBookMoves = -1;
+ private boolean verbose;
+
+ public Book(boolean verbose) {
+ this.verbose = verbose;
+ }
+
+ private final void initBook() {
+ if (numBookMoves >= 0)
+ return;
+ long t0 = System.currentTimeMillis();
+ bookMap = new HashMap>();
+ rndGen = new SecureRandom();
+ rndGen.setSeed(System.currentTimeMillis());
+ numBookMoves = 0;
+ try {
+ InputStream inStream = getClass().getResourceAsStream("/book.bin");
+ List buf = new ArrayList(8192);
+ byte[] tmpBuf = new byte[1024];
+ while (true) {
+ int len = inStream.read(tmpBuf);
+ if (len <= 0) break;
+ for (int i = 0; i < len; i++)
+ buf.add(tmpBuf[i]);
+ }
+ inStream.close();
+ Position startPos = TextIO.readFEN(TextIO.startPosFEN);
+ Position pos = new Position(startPos);
+ UndoInfo ui = new UndoInfo();
+ int len = buf.size();
+ for (int i = 0; i < len; i += 2) {
+ int b0 = buf.get(i); if (b0 < 0) b0 += 256;
+ int b1 = buf.get(i+1); if (b1 < 0) b1 += 256;
+ int move = (b0 << 8) + b1;
+ if (move == 0) {
+ pos = new Position(startPos);
+ } else {
+ boolean bad = ((move >> 15) & 1) != 0;
+ int prom = (move >> 12) & 7;
+ Move m = new Move(move & 63, (move >> 6) & 63,
+ promToPiece(prom, pos.whiteMove));
+ if (!bad)
+ addToBook(pos, m);
+ pos.makeMove(m, ui);
+ }
+ }
+ } catch (ChessParseError ex) {
+ throw new RuntimeException();
+ } catch (IOException ex) {
+ System.out.println("Can't read opening book resource");
+ throw new RuntimeException();
+ }
+ if (verbose) {
+ long t1 = System.currentTimeMillis();
+ System.out.printf("Book moves:%d (parse time:%.3f)%n", numBookMoves,
+ (t1 - t0) / 1000.0);
+ }
+ }
+
+ /** Add a move to a position in the opening book. */
+ private final void addToBook(Position pos, Move moveToAdd) {
+ List ent = bookMap.get(pos.zobristHash());
+ if (ent == null) {
+ ent = new ArrayList();
+ bookMap.put(pos.zobristHash(), ent);
+ }
+ for (int i = 0; i < ent.size(); i++) {
+ BookEntry be = ent.get(i);
+ if (be.move.equals(moveToAdd)) {
+ be.count++;
+ return;
+ }
+ }
+ BookEntry be = new BookEntry(moveToAdd);
+ ent.add(be);
+ numBookMoves++;
+ }
+
+ /** Return a random book move for a position, or null if out of book. */
+ public final Move getBookMove(Position pos) {
+ initBook();
+ List bookMoves = bookMap.get(pos.zobristHash());
+ if (bookMoves == null) {
+ return null;
+ }
+
+ MoveGen.MoveList legalMoves = new MoveGen().pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, legalMoves);
+ int sum = 0;
+ for (int i = 0; i < bookMoves.size(); i++) {
+ BookEntry be = bookMoves.get(i);
+ boolean contains = false;
+ for (int mi = 0; mi < legalMoves.size; mi++)
+ if (legalMoves.m[mi].equals(be.move)) {
+ contains = true;
+ break;
+ }
+ if (!contains) {
+ // If an illegal move was found, it means there was a hash collision.
+ return null;
+ }
+ sum += getWeight(bookMoves.get(i).count);
+ }
+ if (sum <= 0) {
+ return null;
+ }
+ int rnd = rndGen.nextInt(sum);
+ sum = 0;
+ for (int i = 0; i < bookMoves.size(); i++) {
+ sum += getWeight(bookMoves.get(i).count);
+ if (rnd < sum) {
+ return bookMoves.get(i).move;
+ }
+ }
+ // Should never get here
+ throw new RuntimeException();
+ }
+
+ final private int getWeight(int count) {
+ double tmp = Math.sqrt(count);
+ return (int)(tmp * Math.sqrt(tmp) * 100 + 1);
+ }
+
+ /** Return a string describing all book moves. */
+ public final String getAllBookMoves(Position pos) {
+ initBook();
+ StringBuilder ret = new StringBuilder();
+ List bookMoves = bookMap.get(pos.zobristHash());
+ if (bookMoves != null) {
+ for (BookEntry be : bookMoves) {
+ String moveStr = TextIO.moveToString(pos, be.move, false);
+ ret.append(moveStr);
+ ret.append("(");
+ ret.append(be.count);
+ ret.append(") ");
+ }
+ }
+ return ret.toString();
+ }
+
+ /** Creates the book.bin file. */
+ public static void main(String[] args) throws IOException {
+ List binBook = createBinBook();
+ FileOutputStream out = new FileOutputStream("../src/book.bin");
+ int bookLen = binBook.size();
+ byte[] binBookA = new byte[bookLen];
+ for (int i = 0; i < bookLen; i++)
+ binBookA[i] = binBook.get(i);
+ out.write(binBookA);
+ out.close();
+ }
+
+ public static List createBinBook() {
+ List binBook = new ArrayList(0);
+ try {
+ InputStream inStream = new Object().getClass().getResourceAsStream("/book.txt");
+ InputStreamReader inFile = new InputStreamReader(inStream);
+ BufferedReader inBuf = new BufferedReader(inFile);
+ LineNumberReader lnr = new LineNumberReader(inBuf);
+ String line;
+ while ((line = lnr.readLine()) != null) {
+ if (line.startsWith("#") || (line.length() == 0)) {
+ continue;
+ }
+ if (!addBookLine(line, binBook)) {
+ System.out.printf("Book parse error, line:%d\n", lnr.getLineNumber());
+ throw new RuntimeException();
+ }
+// System.out.printf("no:%d line:%s%n", lnr.getLineNumber(), line);
+ }
+ lnr.close();
+ } catch (ChessParseError ex) {
+ throw new RuntimeException();
+ } catch (IOException ex) {
+ System.out.println("Can't read opening book resource");
+ throw new RuntimeException();
+ }
+ return binBook;
+ }
+
+ /** Add a sequence of moves, starting from the initial position, to the binary opening book. */
+ private static boolean addBookLine(String line, List binBook) throws ChessParseError {
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ UndoInfo ui = new UndoInfo();
+ String[] strMoves = line.split(" ");
+ for (String strMove : strMoves) {
+// System.out.printf("Adding move:%s\n", strMove);
+ int bad = 0;
+ if (strMove.endsWith("?")) {
+ strMove = strMove.substring(0, strMove.length() - 1);
+ bad = 1;
+ }
+ Move m = TextIO.stringToMove(pos, strMove);
+ if (m == null) {
+ return false;
+ }
+ int prom = pieceToProm(m.promoteTo);
+ int val = m.from + (m.to << 6) + (prom << 12) + (bad << 15);
+ binBook.add((byte)(val >> 8));
+ binBook.add((byte)(val & 255));
+ pos.makeMove(m, ui);
+ }
+ binBook.add((byte)0);
+ binBook.add((byte)0);
+ return true;
+ }
+
+ private static int pieceToProm(int p) {
+ switch (p) {
+ case Piece.WQUEEN: case Piece.BQUEEN:
+ return 1;
+ case Piece.WROOK: case Piece.BROOK:
+ return 2;
+ case Piece.WBISHOP: case Piece.BBISHOP:
+ return 3;
+ case Piece.WKNIGHT: case Piece.BKNIGHT:
+ return 4;
+ default:
+ return 0;
+ }
+ }
+
+ private static int promToPiece(int prom, boolean whiteMove) {
+ switch (prom) {
+ case 1: return whiteMove ? Piece.WQUEEN : Piece.BQUEEN;
+ case 2: return whiteMove ? Piece.WROOK : Piece.BROOK;
+ case 3: return whiteMove ? Piece.WBISHOP : Piece.BBISHOP;
+ case 4: return whiteMove ? Piece.WKNIGHT : Piece.BKNIGHT;
+ default: return Piece.EMPTY;
+ }
+ }
+}
diff --git a/CuckooChessEngine/src/chess/ChessParseError.java b/CuckooChessEngine/src/chess/ChessParseError.java
new file mode 100644
index 0000000..220444a
--- /dev/null
+++ b/CuckooChessEngine/src/chess/ChessParseError.java
@@ -0,0 +1,32 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+/**
+ * Exception class to represent parse errors in FEN or algebraic notation.
+ * @author petero
+ */
+public class ChessParseError extends Exception {
+ private static final long serialVersionUID = -6051856171275301175L;
+ public ChessParseError() {
+ }
+ public ChessParseError(String msg) {
+ super(msg);
+ }
+}
diff --git a/CuckooChessEngine/src/chess/ComputerPlayer.java b/CuckooChessEngine/src/chess/ComputerPlayer.java
new file mode 100644
index 0000000..5aea724
--- /dev/null
+++ b/CuckooChessEngine/src/chess/ComputerPlayer.java
@@ -0,0 +1,244 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.security.SecureRandom;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * A computer algorithm player.
+ * @author petero
+ */
+public class ComputerPlayer implements Player {
+ public static final String engineName;
+
+ static {
+ String name = "CuckooChess 1.13a2";
+ String m = System.getProperty("sun.arch.data.model");
+ if ("32".equals(m))
+ name += " 32-bit";
+ else if ("64".equals(m))
+ name += " 64-bit";
+ engineName = name;
+ }
+
+ int minTimeMillis;
+ int maxTimeMillis;
+ int maxDepth;
+ int maxNodes;
+ public boolean verbose;
+ TranspositionTable tt;
+ Book book;
+ boolean bookEnabled;
+ boolean randomMode;
+ Search currentSearch;
+
+ public ComputerPlayer() {
+ minTimeMillis = 10000;
+ maxTimeMillis = 10000;
+ maxDepth = 100;
+ maxNodes = -1;
+ verbose = true;
+ setTTLogSize(15);
+ book = new Book(verbose);
+ bookEnabled = true;
+ randomMode = false;
+ }
+
+ public void setTTLogSize(int logSize) {
+ tt = new TranspositionTable(logSize);
+ }
+
+ Search.Listener listener;
+ public void setListener(Search.Listener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public String getCommand(Position pos, boolean drawOffer, List history) {
+ // Create a search object
+ long[] posHashList = new long[200 + history.size()];
+ int posHashListSize = 0;
+ for (Position p : history) {
+ posHashList[posHashListSize++] = p.zobristHash();
+ }
+ tt.nextGeneration();
+ Search sc = new Search(pos, posHashList, posHashListSize, tt);
+
+ // Determine all legal moves
+ MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ sc.scoreMoveList(moves, 0);
+
+ // Test for "game over"
+ if (moves.size == 0) {
+ // Switch sides so that the human can decide what to do next.
+ return "swap";
+ }
+
+ if (bookEnabled) {
+ Move bookMove = book.getBookMove(pos);
+ if (bookMove != null) {
+ System.out.printf("Book moves: %s\n", book.getAllBookMoves(pos));
+ return TextIO.moveToString(pos, bookMove, false);
+ }
+ }
+
+ // Find best move using iterative deepening
+ currentSearch = sc;
+ sc.setListener(listener);
+ Move bestM;
+ if ((moves.size == 1) && (canClaimDraw(pos, posHashList, posHashListSize, moves.m[0]) == "")) {
+ bestM = moves.m[0];
+ bestM.score = 0;
+ } else if (randomMode) {
+ bestM = findSemiRandomMove(sc, moves);
+ } else {
+ sc.timeLimit(minTimeMillis, maxTimeMillis);
+ bestM = sc.iterativeDeepening(moves, maxDepth, maxNodes, verbose);
+ }
+ currentSearch = null;
+// tt.printStats();
+ String strMove = TextIO.moveToString(pos, bestM, false);
+
+ // Claim draw if appropriate
+ if (bestM.score <= 0) {
+ String drawClaim = canClaimDraw(pos, posHashList, posHashListSize, bestM);
+ if (drawClaim != "")
+ strMove = drawClaim;
+ }
+ return strMove;
+ }
+
+ /** Check if a draw claim is allowed, possibly after playing "move".
+ * @param move The move that may have to be made before claiming draw.
+ * @return The draw string that claims the draw, or empty string if draw claim not valid.
+ */
+ private String canClaimDraw(Position pos, long[] posHashList, int posHashListSize, Move move) {
+ String drawStr = "";
+ if (Search.canClaimDraw50(pos)) {
+ drawStr = "draw 50";
+ } else if (Search.canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
+ drawStr = "draw rep";
+ } else {
+ String strMove = TextIO.moveToString(pos, move, false);
+ posHashList[posHashListSize++] = pos.zobristHash();
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(move, ui);
+ if (Search.canClaimDraw50(pos)) {
+ drawStr = "draw 50 " + strMove;
+ } else if (Search.canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
+ drawStr = "draw rep " + strMove;
+ }
+ pos.unMakeMove(move, ui);
+ }
+ return drawStr;
+ }
+
+ @Override
+ public boolean isHumanPlayer() {
+ return false;
+ }
+
+ @Override
+ public void useBook(boolean bookOn) {
+ bookEnabled = bookOn;
+ }
+
+ @Override
+ public void timeLimit(int minTimeLimit, int maxTimeLimit, boolean randomMode) {
+ if (randomMode) {
+ minTimeLimit = 0;
+ maxTimeLimit = 0;
+ }
+ minTimeMillis = minTimeLimit;
+ maxTimeMillis = maxTimeLimit;
+ this.randomMode = randomMode;
+ if (currentSearch != null) {
+ currentSearch.timeLimit(minTimeLimit, maxTimeLimit);
+ }
+ }
+
+ @Override
+ public void clearTT() {
+ tt.clear();
+ }
+
+ /** Search a position and return the best move and score. Used for test suite processing. */
+ public TwoReturnValues searchPosition(Position pos, int maxTimeMillis) {
+ // Create a search object
+ long[] posHashList = new long[200];
+ tt.nextGeneration();
+ Search sc = new Search(pos, posHashList, 0, tt);
+
+ // Determine all legal moves
+ MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ sc.scoreMoveList(moves, 0);
+
+ // Find best move using iterative deepening
+ sc.timeLimit(maxTimeMillis, maxTimeMillis);
+ Move bestM = sc.iterativeDeepening(moves, -1, -1, false);
+
+ // Extract PV
+ String PV = TextIO.moveToString(pos, bestM, false) + " ";
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(bestM, ui);
+ PV += tt.extractPV(pos);
+ pos.unMakeMove(bestM, ui);
+
+// tt.printStats();
+
+ // Return best move and PV
+ return new TwoReturnValues(bestM, PV);
+ }
+
+ private Move findSemiRandomMove(Search sc, MoveGen.MoveList moves) {
+ sc.timeLimit(minTimeMillis, maxTimeMillis);
+ Move bestM = sc.iterativeDeepening(moves, 1, maxNodes, verbose);
+ int bestScore = bestM.score;
+
+ Random rndGen = new SecureRandom();
+ rndGen.setSeed(System.currentTimeMillis());
+
+ int sum = 0;
+ for (int mi = 0; mi < moves.size; mi++) {
+ sum += moveProbWeight(moves.m[mi].score, bestScore);
+ }
+ int rnd = rndGen.nextInt(sum);
+ for (int mi = 0; mi < moves.size; mi++) {
+ int weight = moveProbWeight(moves.m[mi].score, bestScore);
+ if (rnd < weight) {
+ return moves.m[mi];
+ }
+ rnd -= weight;
+ }
+ assert(false);
+ return null;
+ }
+
+ private final static int moveProbWeight(int moveScore, int bestScore) {
+ double d = (bestScore - moveScore) / 100.0;
+ double w = 100*Math.exp(-d*d/2);
+ return (int)Math.ceil(w);
+ }
+
+ // FIXME!!! Test Botvinnik-Markoff extension
+}
diff --git a/CuckooChessEngine/src/chess/Evaluate.java b/CuckooChessEngine/src/chess/Evaluate.java
new file mode 100644
index 0000000..ca595e8
--- /dev/null
+++ b/CuckooChessEngine/src/chess/Evaluate.java
@@ -0,0 +1,1181 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Position evaluation routines.
+ *
+ * @author petero
+ */
+public class Evaluate {
+ static final int pV = 92 + Parameters.instance().getIntPar("pV");
+ static final int nV = 385 + Parameters.instance().getIntPar("nV");
+ static final int bV = 385 + Parameters.instance().getIntPar("bV");
+ static final int rV = 593 + Parameters.instance().getIntPar("rV");
+ static final int qV = 1244 + Parameters.instance().getIntPar("qV");
+ static final int kV = 9900; // Used by SEE algorithm, but not included in board material sums
+
+ static final int[] pieceValue;
+ static {
+ // Initialize material table
+ pieceValue = new int[Piece.nPieceTypes];
+ pieceValue[Piece.WKING ] = kV;
+ pieceValue[Piece.WQUEEN ] = qV;
+ pieceValue[Piece.WROOK ] = rV;
+ pieceValue[Piece.WBISHOP] = bV;
+ pieceValue[Piece.WKNIGHT] = nV;
+ pieceValue[Piece.WPAWN ] = pV;
+ pieceValue[Piece.BKING ] = kV;
+ pieceValue[Piece.BQUEEN ] = qV;
+ pieceValue[Piece.BROOK ] = rV;
+ pieceValue[Piece.BBISHOP] = bV;
+ pieceValue[Piece.BKNIGHT] = nV;
+ pieceValue[Piece.BPAWN ] = pV;
+ pieceValue[Piece.EMPTY ] = 0;
+ }
+
+ /** Piece/square table for king during middle game. */
+ static final int[] kt1b = { -22,-35,-40,-40,-40,-40,-35,-22,
+ -22,-35,-40,-40,-40,-40,-35,-22,
+ -25,-35,-40,-45,-45,-40,-35,-25,
+ -15,-30,-35,-40,-40,-35,-30,-15,
+ -10,-15,-20,-25,-25,-20,-15,-10,
+ 4, -2, -5,-15,-15, -5, -2, 4,
+ 16, 14, 7, -3, -3, 7, 14, 16,
+ 24, 24, 9, 0, 0, 9, 24, 24 };
+
+ /** Piece/square table for king during end game. */
+ static final int[] kt2b = { 0, 8, 16, 24, 24, 16, 8, 0,
+ 8, 16, 24, 32, 32, 24, 16, 8,
+ 16, 24, 32, 40, 40, 32, 24, 16,
+ 24, 32, 40, 48, 48, 40, 32, 24,
+ 24, 32, 40, 48, 48, 40, 32, 24,
+ 16, 24, 32, 40, 40, 32, 24, 16,
+ 8, 16, 24, 32, 32, 24, 16, 8,
+ 0, 8, 16, 24, 24, 16, 8, 0 };
+
+ /** Piece/square table for pawns during middle game. */
+ static final int[] pt1b = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 8, 16, 24, 32, 32, 24, 16, 8,
+ 3, 12, 20, 28, 28, 20, 12, 3,
+ -5, 4, 10, 20, 20, 10, 4, -5,
+ -6, 4, 5, 16, 16, 5, 4, -6,
+ -6, 4, 2, 5, 5, 2, 4, -6,
+ -6, 4, 4,-15,-15, 4, 4, -6,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ /** Piece/square table for pawns during end game. */
+ static final int[] pt2b = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 40, 45, 45, 45, 45, 40, 25,
+ 17, 32, 35, 35, 35, 35, 32, 17,
+ 5, 24, 24, 24, 24, 24, 24, 5,
+ -9, 11, 11, 11, 11, 11, 11, -9,
+ -17, 3, 3, 3, 3, 3, 3,-17,
+ -20, 0, 0, 0, 0, 0, 0,-20,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ /** Piece/square table for knights during middle game. */
+ static final int[] nt1b = { -53,-42,-32,-21,-21,-32,-42,-53,
+ -42,-32,-10, 0, 0,-10,-32,-42,
+ -21, 5, 10, 16, 16, 10, 5,-21,
+ -18, 0, 10, 21, 21, 10, 0,-18,
+ -18, 0, 3, 21, 21, 3, 0,-18,
+ -21,-10, 0, 0, 0, 0,-10,-21,
+ -42,-32,-10, 0, 0,-10,-32,-42,
+ -53,-42,-32,-21,-21,-32,-42,-53 };
+
+ /** Piece/square table for knights during end game. */
+ static final int[] nt2b = { -56,-44,-34,-22,-22,-34,-44,-56,
+ -44,-34,-10, 0, 0,-10,-34,-44,
+ -22, 5, 10, 17, 17, 10, 5,-22,
+ -19, 0, 10, 22, 22, 10, 0,-19,
+ -19, 0, 3, 22, 22, 3, 0,-19,
+ -22,-10, 0, 0, 0, 0,-10,-22,
+ -44,-34,-10, 0, 0,-10,-34,-44,
+ -56,-44,-34,-22,-22,-34,-44,-56 };
+
+ /** Piece/square table for bishops during middle game. */
+ static final int[] bt1b = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 4, 2, 2, 2, 2, 4, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 3, 4, 4, 4, 4, 3, 0,
+ 0, 4, 2, 2, 2, 2, 4, 0,
+ -5, -5, -7, -5, -5, -7, -5, -5 };
+
+ /** Piece/square table for bishops during middle game. */
+ static final int[] bt2b = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 4, 4, 4, 4, 2, 0,
+ 0, 2, 2, 2, 2, 2, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ /** Piece/square table for queens during middle game. */
+ static final int[] qt1b = { -10, -5, 0, 0, 0, 0, -5,-10,
+ -5, 0, 5, 5, 5, 5, 0, -5,
+ 0, 5, 5, 6, 6, 5, 5, 0,
+ 0, 5, 6, 6, 6, 6, 5, 0,
+ 0, 5, 6, 6, 6, 6, 5, 0,
+ 0, 5, 5, 6, 6, 5, 5, 0,
+ -5, 0, 5, 5, 5, 5, 0, -5,
+ -10, -5, 0, 0, 0, 0, -5,-10 };
+
+ /** Piece/square table for rooks during middle game. */
+ static final int[] rt1b = { 8, 11, 13, 13, 13, 13, 11, 8,
+ 22, 27, 27, 27, 27, 27, 27, 22,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ -2, 0, 0, 0, 0, 0, 0, -2,
+ -2, 0, 0, 2, 2, 0, 0, -2,
+ -3, 2, 5, 5, 5, 5, 2, -3,
+ 0, 3, 5, 5, 5, 5, 3, 0 };
+
+ static final int[] kt1w, qt1w, rt1w, bt1w, nt1w, pt1w, kt2w, bt2w, nt2w, pt2w;
+ static {
+ kt1w = new int[64];
+ qt1w = new int[64];
+ rt1w = new int[64];
+ bt1w = new int[64];
+ nt1w = new int[64];
+ pt1w = new int[64];
+ kt2w = new int[64];
+ bt2w = new int[64];
+ nt2w = new int[64];
+ pt2w = new int[64];
+ for (int i = 0; i < 64; i++) {
+ kt1w[i] = kt1b[63-i];
+ qt1w[i] = qt1b[63-i];
+ rt1w[i] = rt1b[63-i];
+ bt1w[i] = bt1b[63-i];
+ nt1w[i] = nt1b[63-i];
+ pt1w[i] = pt1b[63-i];
+ kt2w[i] = kt2b[63-i];
+ bt2w[i] = bt2b[63-i];
+ nt2w[i] = nt2b[63-i];
+ pt2w[i] = pt2b[63-i];
+ }
+ }
+
+ private static final int[] empty = { 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0};
+ static final int[][] psTab1 = { empty, kt1w, qt1w, rt1w, bt1w, nt1w, pt1w,
+ kt1b, qt1b, rt1b, bt1b, nt1b, pt1b };
+ static final int[][] psTab2 = { empty, kt2w, qt1w, rt1w, bt2w, nt2w, pt2w,
+ kt2b, qt1b, rt1b, bt2b, nt2b, pt2b };
+
+ static final int[][] distToH1A8 = { { 0, 1, 2, 3, 4, 5, 6, 7 },
+ { 1, 2, 3, 4, 5, 6, 7, 6 },
+ { 2, 3, 4, 5, 6, 7, 6, 5 },
+ { 3, 4, 5, 6, 7, 6, 5, 4 },
+ { 4, 5, 6, 7, 6, 5, 4, 3 },
+ { 5, 6, 7, 6, 5, 4, 3, 2 },
+ { 6, 7, 6, 5, 4, 3, 2, 1 },
+ { 7, 6, 5, 4, 3, 2, 1, 0 } };
+
+ static final int[] rookMobScore = {-10,-7,-4,-1,2,5,7,9,11,12,13,14,14,14,14};
+ static final int[] bishMobScore = {-15,-10,-6,-2,2,6,10,13,16,18,20,22,23,24};
+ static final int[] queenMobScore = {-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,9,10,10,10,10,10,10,10,10,10,10,10,10};
+
+ private static final class PawnHashData {
+ long key;
+ int score; // Positive score means good for white
+ short passedBonusW;
+ short passedBonusB;
+ long passedPawnsW; // The most advanced passed pawns for each file
+ long passedPawnsB;
+ }
+ static final PawnHashData[] pawnHash;
+ static {
+ final int numEntries = 1<<16;
+ pawnHash = new PawnHashData[numEntries];
+ for (int i = 0; i < numEntries; i++) {
+ PawnHashData phd = new PawnHashData();
+ phd.key = -1; // Non-zero to avoid collision for positions with no pawns
+ phd.score = 0;
+ pawnHash[i] = phd;
+ }
+ }
+
+ static byte[] kpkTable = null;
+ static byte[] krkpTable = null;
+
+ // King safety variables
+ private long wKingZone, bKingZone; // Squares close to king that are worth attacking
+ private int wKingAttacks, bKingAttacks; // Number of attacks close to white/black king
+ private long wAttacksBB, bAttacksBB;
+ private long wPawnAttacks, bPawnAttacks; // Squares attacked by white/black pawns
+
+ /** Constructor. */
+ public Evaluate() {
+ if (kpkTable == null)
+ kpkTable = readTable("/kpk.bitbase", 2*32*64*48/8);
+ if (krkpTable == null)
+ krkpTable = readTable("/krkp.winmasks", 2*32*48*8);
+ }
+
+ private byte[] readTable(String resource, int length) {
+ byte[] table = new byte[2*32*64*48/8];
+ InputStream inStream = getClass().getResourceAsStream(resource);
+ try {
+ int off = 0;
+ while (off < table.length) {
+ int len = inStream.read(table, off, table.length - off);
+ if (len < 0)
+ throw new RuntimeException();
+ off += len;
+ }
+ inStream.close();
+ return table;
+ } catch (IOException e) {
+ throw new RuntimeException();
+ }
+ }
+
+ /**
+ * Static evaluation of a position.
+ * @param pos The position to evaluate.
+ * @return The evaluation score, measured in centipawns.
+ * Positive values are good for the side to make the next move.
+ */
+ final public int evalPos(Position pos) {
+ int score = pos.wMtrl - pos.bMtrl;
+
+ wKingAttacks = bKingAttacks = 0;
+ wKingZone = BitBoard.kingAttacks[pos.getKingSq(true)]; wKingZone |= wKingZone << 8;
+ bKingZone = BitBoard.kingAttacks[pos.getKingSq(false)]; bKingZone |= bKingZone >>> 8;
+ wAttacksBB = bAttacksBB = 0L;
+
+ long pawns = pos.pieceTypeBB[Piece.WPAWN];
+ wPawnAttacks = ((pawns & BitBoard.maskBToHFiles) << 7) |
+ ((pawns & BitBoard.maskAToGFiles) << 9);
+ pawns = pos.pieceTypeBB[Piece.BPAWN];
+ bPawnAttacks = ((pawns & BitBoard.maskBToHFiles) >>> 9) |
+ ((pawns & BitBoard.maskAToGFiles) >>> 7);
+
+ score += pieceSquareEval(pos);
+ score += pawnBonus(pos);
+ score += tradeBonus(pos);
+ score += castleBonus(pos);
+
+ score += rookBonus(pos);
+ score += bishopEval(pos, score);
+ score += threatBonus(pos);
+ score += kingSafety(pos);
+ score = endGameEval(pos, score);
+
+ if (!pos.whiteMove)
+ score = -score;
+ return score;
+
+ // FIXME! Test penalty if side to move has >1 hanging piece
+
+ // FIXME! Test "tempo value"
+ }
+
+ /** Compute white_material - black_material. */
+ static final int material(Position pos) {
+ return pos.wMtrl - pos.bMtrl;
+ }
+
+ /** Compute score based on piece square tables. Positive values are good for white. */
+ private final int pieceSquareEval(Position pos) {
+ int score = 0;
+ final int wMtrl = pos.wMtrl;
+ final int bMtrl = pos.bMtrl;
+ final int wMtrlPawns = pos.wMtrlPawns;
+ final int bMtrlPawns = pos.bMtrlPawns;
+
+ // Kings
+ {
+ final int t1 = qV + 2 * rV + 2 * bV;
+ final int t2 = rV;
+ {
+ final int k1 = pos.psScore1[Piece.WKING];
+ final int k2 = pos.psScore2[Piece.WKING];
+ final int t = bMtrl - bMtrlPawns;
+ score += interpolate(t, t2, k2, t1, k1);
+ }
+ {
+ final int k1 = pos.psScore1[Piece.BKING];
+ final int k2 = pos.psScore2[Piece.BKING];
+ final int t = wMtrl - wMtrlPawns;
+ score -= interpolate(t, t2, k2, t1, k1);
+ }
+ }
+
+ // Pawns
+ {
+ final int t1 = qV + 2 * rV + 2 * bV;
+ final int t2 = rV;
+ int wp1 = pos.psScore1[Piece.WPAWN];
+ int wp2 = pos.psScore2[Piece.WPAWN];
+ if ((wp1 != 0) || (wp2 != 0)) {
+ final int tw = bMtrl - bMtrlPawns;
+ score += interpolate(tw, t2, wp2, t1, wp1);
+ }
+ int bp1 = pos.psScore1[Piece.BPAWN];
+ int bp2 = pos.psScore2[Piece.BPAWN];
+ if ((bp1 != 0) || (bp2 != 0)) {
+ final int tb = wMtrl - wMtrlPawns;
+ score -= interpolate(tb, t2, bp2, t1, bp1);
+ }
+ }
+
+ // Knights
+ {
+ final int t1 = qV + 2 * rV + 1 * bV + 1 * nV + 6 * pV;
+ final int t2 = nV + 8 * pV;
+ int n1 = pos.psScore1[Piece.WKNIGHT];
+ int n2 = pos.psScore2[Piece.WKNIGHT];
+ if ((n1 != 0) || (n2 != 0)) {
+ score += interpolate(bMtrl, t2, n2, t1, n1);
+ }
+ n1 = pos.psScore1[Piece.BKNIGHT];
+ n2 = pos.psScore2[Piece.BKNIGHT];
+ if ((n1 != 0) || (n2 != 0)) {
+ score -= interpolate(wMtrl, t2, n2, t1, n1);
+ }
+ }
+
+ // Bishops
+ {
+ score += pos.psScore1[Piece.WBISHOP];
+ score -= pos.psScore1[Piece.BBISHOP];
+ }
+
+ // Queens
+ {
+ final long occupied = pos.whiteBB | pos.blackBB;
+ score += pos.psScore1[Piece.WQUEEN];
+ long m = pos.pieceTypeBB[Piece.WQUEEN];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ long atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied);
+ wAttacksBB |= atk;
+ score += queenMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))];
+ bKingAttacks += Long.bitCount(atk & bKingZone) * 2;
+ m &= m-1;
+ }
+ score -= pos.psScore1[Piece.BQUEEN];
+ m = pos.pieceTypeBB[Piece.BQUEEN];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ long atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied);
+ bAttacksBB |= atk;
+ score -= queenMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))];
+ wKingAttacks += Long.bitCount(atk & wKingZone) * 2;
+ m &= m-1;
+ }
+ }
+
+ // Rooks
+ {
+ int r1 = pos.psScore1[Piece.WROOK];
+ if (r1 != 0) {
+ final int nP = bMtrlPawns / pV;
+ final int s = r1 * Math.min(nP, 6) / 6;
+ score += s;
+ }
+ r1 = pos.psScore1[Piece.BROOK];
+ if (r1 != 0) {
+ final int nP = wMtrlPawns / pV;
+ final int s = r1 * Math.min(nP, 6) / 6;
+ score -= s;
+ }
+ }
+
+ return score;
+ }
+
+ /** Implement the "when ahead trade pieces, when behind trade pawns" rule. */
+ private final int tradeBonus(Position pos) {
+ final int wM = pos.wMtrl;
+ final int bM = pos.bMtrl;
+ final int wPawn = pos.wMtrlPawns;
+ final int bPawn = pos.bMtrlPawns;
+ final int deltaScore = wM - bM;
+
+ int pBonus = 0;
+ pBonus += interpolate((deltaScore > 0) ? wPawn : bPawn, 0, -30 * deltaScore / 100, 6 * pV, 0);
+ pBonus += interpolate((deltaScore > 0) ? bM : wM, 0, 30 * deltaScore / 100, qV + 2 * rV + 2 * bV + 2 * nV, 0);
+
+ return pBonus;
+ }
+
+ private static final int[] castleFactor;
+ static {
+ castleFactor = new int[256];
+ for (int i = 0; i < 256; i++) {
+ int h1Dist = 100;
+ boolean h1Castle = (i & (1<<7)) != 0;
+ if (h1Castle)
+ h1Dist = 2 + Long.bitCount(i & 0x0000000000000060L); // f1,g1
+ int a1Dist = 100;
+ boolean a1Castle = (i & 1) != 0;
+ if (a1Castle)
+ a1Dist = 2 + Long.bitCount(i & 0x000000000000000EL); // b1,c1,d1
+ castleFactor[i] = 1024 / Math.min(a1Dist, h1Dist);
+ }
+ }
+
+ /** Score castling ability. */
+ private final int castleBonus(Position pos) {
+ if (pos.getCastleMask() == 0) return 0;
+
+ final int k1 = kt1b[7*8+6] - kt1b[7*8+4];
+ final int k2 = kt2b[7*8+6] - kt2b[7*8+4];
+ final int t1 = qV + 2 * rV + 2 * bV;
+ final int t2 = rV;
+ final int t = pos.bMtrl - pos.bMtrlPawns;
+ final int ks = interpolate(t, t2, k2, t1, k1);
+
+ final int castleValue = ks + rt1b[7*8+5] - rt1b[7*8+7];
+ if (castleValue <= 0)
+ return 0;
+
+ long occupied = pos.whiteBB | pos.blackBB;
+ int tmp = (int) (occupied & 0x6E);
+ if (pos.a1Castle()) tmp |= 1;
+ if (pos.h1Castle()) tmp |= (1 << 7);
+ final int wBonus = (castleValue * castleFactor[tmp]) >> 10;
+
+ tmp = (int) ((occupied >>> 56) & 0x6E);
+ if (pos.a8Castle()) tmp |= 1;
+ if (pos.h8Castle()) tmp |= (1 << 7);
+ final int bBonus = (castleValue * castleFactor[tmp]) >> 10;
+
+ return wBonus - bBonus;
+ }
+
+ private final int pawnBonus(Position pos) {
+ long key = pos.pawnZobristHash();
+ PawnHashData phd = pawnHash[(int)key & (pawnHash.length - 1)];
+ if (phd.key != key)
+ computePawnHashData(pos, phd);
+ int score = phd.score;
+
+ final int hiMtrl = qV + rV;
+ score += interpolate(pos.bMtrl - pos.bMtrlPawns, 0, 2 * phd.passedBonusW, hiMtrl, phd.passedBonusW);
+ score -= interpolate(pos.wMtrl - pos.wMtrlPawns, 0, 2 * phd.passedBonusB, hiMtrl, phd.passedBonusB);
+
+ // Passed pawns are more dangerous if enemy king is far away
+ int mtrlNoPawns;
+ final int highMtrl = qV + rV;
+ long m = phd.passedPawnsW;
+ if (m != 0) {
+ mtrlNoPawns = pos.bMtrl - pos.bMtrlPawns;
+ if (mtrlNoPawns < highMtrl) {
+ int kingPos = pos.getKingSq(false);
+ int kingX = Position.getX(kingPos);
+ int kingY = Position.getY(kingPos);
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ int pawnDist = Math.min(5, 7 - y);
+ int kingDistX = Math.abs(kingX - x);
+ int kingDistY = Math.abs(kingY - 7);
+ int kingDist = Math.max(kingDistX, kingDistY);
+ int kScore = kingDist * 4;
+ if (kingDist > pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist);
+ score += interpolate(mtrlNoPawns, 0, kScore, highMtrl, 0);
+ if (!pos.whiteMove)
+ kingDist--;
+ if ((pawnDist < kingDist) && (mtrlNoPawns == 0))
+ score += 500; // King can't stop pawn
+ m &= m-1;
+ }
+ }
+ }
+ m = phd.passedPawnsB;
+ if (m != 0) {
+ mtrlNoPawns = pos.wMtrl - pos.wMtrlPawns;
+ if (mtrlNoPawns < highMtrl) {
+ int kingPos = pos.getKingSq(true);
+ int kingX = Position.getX(kingPos);
+ int kingY = Position.getY(kingPos);
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ int pawnDist = Math.min(5, y);
+ int kingDistX = Math.abs(kingX - x);
+ int kingDistY = Math.abs(kingY - 0);
+ int kingDist = Math.max(kingDistX, kingDistY);
+ int kScore = kingDist * 4;
+ if (kingDist > pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist);
+ score -= interpolate(mtrlNoPawns, 0, kScore, highMtrl, 0);
+ if (pos.whiteMove)
+ kingDist--;
+ if ((pawnDist < kingDist) && (mtrlNoPawns == 0))
+ score -= 500; // King can't stop pawn
+ m &= m-1;
+ }
+ }
+ }
+
+ return score;
+ }
+
+ /** Compute pawn hash data for pos. */
+ private final void computePawnHashData(Position pos, PawnHashData ph) {
+ int score = 0;
+
+ // Evaluate double pawns and pawn islands
+ long wPawns = pos.pieceTypeBB[Piece.WPAWN];
+ long wPawnFiles = BitBoard.southFill(wPawns) & 0xff;
+ int wDouble = Long.bitCount(wPawns) - Long.bitCount(wPawnFiles);
+ int wIslands = Long.bitCount(((~wPawnFiles) >>> 1) & wPawnFiles);
+ int wIsolated = Long.bitCount(~(wPawnFiles<<1) & wPawnFiles & ~(wPawnFiles>>>1));
+
+
+ long bPawns = pos.pieceTypeBB[Piece.BPAWN];
+ long bPawnFiles = BitBoard.southFill(bPawns) & 0xff;
+ int bDouble = Long.bitCount(bPawns) - Long.bitCount(bPawnFiles);
+ int bIslands = Long.bitCount(((~bPawnFiles) >>> 1) & bPawnFiles);
+ int bIsolated = Long.bitCount(~(bPawnFiles<<1) & bPawnFiles & ~(bPawnFiles>>>1));
+
+ score -= (wDouble - bDouble) * 25;
+ score -= (wIslands - bIslands) * 15;
+ score -= (wIsolated - bIsolated) * 15;
+
+ // Evaluate backward pawns, defined as a pawn that guards a friendly pawn,
+ // can't be guarded by friendly pawns, can advance, but can't advance without
+ // being captured by an enemy pawn.
+ long wPawnAttacks = (((wPawns & BitBoard.maskBToHFiles) << 7) |
+ ((wPawns & BitBoard.maskAToGFiles) << 9));
+ long bPawnAttacks = (((bPawns & BitBoard.maskBToHFiles) >>> 9) |
+ ((bPawns & BitBoard.maskAToGFiles) >>> 7));
+ long wBackward = wPawns & ~((wPawns | bPawns) >>> 8) & (bPawnAttacks >>> 8) &
+ ~BitBoard.northFill(wPawnAttacks);
+ wBackward &= (((wPawns & BitBoard.maskBToHFiles) >>> 9) |
+ ((wPawns & BitBoard.maskAToGFiles) >>> 7));
+ wBackward &= ~BitBoard.northFill(bPawnFiles);
+ long bBackward = bPawns & ~((wPawns | bPawns) << 8) & (wPawnAttacks << 8) &
+ ~BitBoard.southFill(bPawnAttacks);
+ bBackward &= (((bPawns & BitBoard.maskBToHFiles) << 7) |
+ ((bPawns & BitBoard.maskAToGFiles) << 9));
+ bBackward &= ~BitBoard.northFill(wPawnFiles);
+ score -= (Long.bitCount(wBackward) - Long.bitCount(bBackward)) * 15;
+
+ // Evaluate passed pawn bonus, white
+ long passedPawnsW = wPawns & ~BitBoard.southFill(bPawns | bPawnAttacks | (wPawns >>> 8));
+ final int[] ppBonus = {-1,24,26,30,36,47,64,-1};
+ int passedBonusW = 0;
+ if (passedPawnsW != 0) {
+ long guardedPassedW = passedPawnsW & (((wPawns & BitBoard.maskBToHFiles) << 7) |
+ ((wPawns & BitBoard.maskAToGFiles) << 9));
+ passedBonusW += 15 * Long.bitCount(guardedPassedW);
+ long m = passedPawnsW;
+ while (m != 0) {
+ int sq = Long .numberOfTrailingZeros(m);
+ int y = Position.getY(sq);
+ passedBonusW += ppBonus[y];
+ m &= m-1;
+ }
+ }
+
+ // Evaluate passed pawn bonus, black
+ long passedPawnsB = bPawns & ~BitBoard.northFill(wPawns | wPawnAttacks | (bPawns << 8));
+ int passedBonusB = 0;
+ if (passedPawnsB != 0) {
+ long guardedPassedB = passedPawnsB & (((bPawns & BitBoard.maskBToHFiles) >>> 9) |
+ ((bPawns & BitBoard.maskAToGFiles) >>> 7));
+ passedBonusB += 15 * Long.bitCount(guardedPassedB);
+ long m = passedPawnsB;
+ while (m != 0) {
+ int sq = Long .numberOfTrailingZeros(m);
+ int y = Position.getY(sq);
+ passedBonusB += ppBonus[7-y];
+ m &= m-1;
+ }
+ }
+
+ // Connected passed pawn bonus. Seems logical but doesn't help in tests
+// if (passedPawnsW != 0)
+// passedBonusW += 15 * Long.bitCount(passedPawnsW & ((passedPawnsW & BitBoard.maskBToHFiles) >>> 1));
+// if (passedPawnsB != 0)
+// passedBonusB += 15 * Long.bitCount(passedPawnsB & ((passedPawnsB & BitBoard.maskBToHFiles) >>> 1));
+
+ ph.key = pos.pawnZobristHash();
+ ph.score = score;
+ ph.passedBonusW = (short)passedBonusW;
+ ph.passedBonusB = (short)passedBonusB;
+ ph.passedPawnsW = passedPawnsW;
+ ph.passedPawnsB = passedPawnsB;
+ }
+
+ /** Compute rook bonus. Rook on open/half-open file. */
+ private final int rookBonus(Position pos) {
+ int score = 0;
+ final long wPawns = pos.pieceTypeBB[Piece.WPAWN];
+ final long bPawns = pos.pieceTypeBB[Piece.BPAWN];
+ final long occupied = pos.whiteBB | pos.blackBB;
+ long m = pos.pieceTypeBB[Piece.WROOK];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ final int x = Position.getX(sq);
+ if ((wPawns & BitBoard.maskFile[x]) == 0) { // At least half-open file
+ score += (bPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12;
+ }
+ long atk = BitBoard.rookAttacks(sq, occupied);
+ wAttacksBB |= atk;
+ score += rookMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))];
+ if ((atk & bKingZone) != 0)
+ bKingAttacks += Long.bitCount(atk & bKingZone);
+ m &= m-1;
+ }
+ long r7 = (pos.pieceTypeBB[Piece.WROOK] >>> 48) & 0x00ffL;
+ if (((r7 & (r7 - 1)) != 0) &&
+ ((pos.pieceTypeBB[Piece.BKING] & 0xff00000000000000L) != 0))
+ score += 30; // Two rooks on 7:th row
+ m = pos.pieceTypeBB[Piece.BROOK];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ final int x = Position.getX(sq);
+ if ((bPawns & BitBoard.maskFile[x]) == 0) {
+ score -= (wPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12;
+ }
+ long atk = BitBoard.rookAttacks(sq, occupied);
+ bAttacksBB |= atk;
+ score -= rookMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))];
+ if ((atk & wKingZone) != 0)
+ wKingAttacks += Long.bitCount(atk & wKingZone);
+ m &= m-1;
+ }
+ r7 = pos.pieceTypeBB[Piece.BROOK] & 0xff00L;
+ if (((r7 & (r7 - 1)) != 0) &&
+ ((pos.pieceTypeBB[Piece.WKING] & 0xffL) != 0))
+ score -= 30; // Two rooks on 2:nd row
+ return score;
+ }
+
+ /** Compute bishop evaluation. */
+ private final int bishopEval(Position pos, int oldScore) {
+ int score = 0;
+ final long occupied = pos.whiteBB | pos.blackBB;
+ long wBishops = pos.pieceTypeBB[Piece.WBISHOP];
+ long bBishops = pos.pieceTypeBB[Piece.BBISHOP];
+ if ((wBishops | bBishops) == 0)
+ return 0;
+ long m = wBishops;
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ long atk = BitBoard.bishopAttacks(sq, occupied);
+ wAttacksBB |= atk;
+ score += bishMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))];
+ if ((atk & bKingZone) != 0)
+ bKingAttacks += Long.bitCount(atk & bKingZone);
+ m &= m-1;
+ }
+ m = bBishops;
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ long atk = BitBoard.bishopAttacks(sq, occupied);
+ bAttacksBB |= atk;
+ score -= bishMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))];
+ if ((atk & wKingZone) != 0)
+ wKingAttacks += Long.bitCount(atk & wKingZone);
+ m &= m-1;
+ }
+
+ boolean whiteDark = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq ) != 0;
+ boolean whiteLight = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) != 0;
+ boolean blackDark = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq ) != 0;
+ boolean blackLight = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) != 0;
+ int numWhite = (whiteDark ? 1 : 0) + (whiteLight ? 1 : 0);
+ int numBlack = (blackDark ? 1 : 0) + (blackLight ? 1 : 0);
+
+ // Bishop pair bonus
+ if (numWhite == 2) {
+ final int numPawns = pos.wMtrlPawns / pV;
+ score += 28 + (8 - numPawns) * 3;
+ }
+ if (numBlack == 2) {
+ final int numPawns = pos.bMtrlPawns / pV;
+ score -= 28 + (8 - numPawns) * 3;
+ }
+
+ // FIXME!!! Bad bishop
+
+ if ((numWhite == 1) && (numBlack == 1) && (whiteDark != blackDark) &&
+ (pos.wMtrl - pos.wMtrlPawns == pos.bMtrl - pos.bMtrlPawns)) {
+ final int penalty = (oldScore + score) / 2;
+ final int loMtrl = 2 * bV;
+ final int hiMtrl = 2 * (qV + rV + bV);
+ int mtrl = pos.wMtrl + pos.bMtrl - pos.wMtrlPawns - pos.bMtrlPawns;
+ score -= interpolate(mtrl, loMtrl, penalty, hiMtrl, 0);
+ }
+
+ // Penalty for bishop trapped behind pawn at a2/h2/a7/h7
+ if (((wBishops | bBishops) & 0x0081000000008100L) != 0) {
+ if ((pos.squares[48] == Piece.WBISHOP) && // a7
+ (pos.squares[41] == Piece.BPAWN) &&
+ (pos.squares[50] == Piece.BPAWN))
+ score -= pV * 3 / 2;
+ if ((pos.squares[55] == Piece.WBISHOP) && // h7
+ (pos.squares[46] == Piece.BPAWN) &&
+ (pos.squares[53] == Piece.BPAWN))
+ score -= (pos.pieceTypeBB[Piece.WQUEEN] != 0) ? pV : pV * 3 / 2;
+ if ((pos.squares[8] == Piece.BBISHOP) && // a2
+ (pos.squares[17] == Piece.WPAWN) &&
+ (pos.squares[10] == Piece.WPAWN))
+ score += pV * 3 / 2;
+ if ((pos.squares[15] == Piece.BBISHOP) && // h2
+ (pos.squares[22] == Piece.WPAWN) &&
+ (pos.squares[13] == Piece.WPAWN))
+ score += (pos.pieceTypeBB[Piece.BQUEEN] != 0) ? pV : pV * 3 / 2;
+ }
+
+ return score;
+ }
+
+ private int threatBonus(Position pos) {
+ int score = 0;
+
+ // Sum values for all black pieces under attack
+ long m = pos.pieceTypeBB[Piece.WKNIGHT];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ wAttacksBB |= BitBoard.knightAttacks[sq];
+ m &= m-1;
+ }
+ wAttacksBB &= (pos.pieceTypeBB[Piece.BKNIGHT] |
+ pos.pieceTypeBB[Piece.BBISHOP] |
+ pos.pieceTypeBB[Piece.BROOK] |
+ pos.pieceTypeBB[Piece.BQUEEN]);
+ wAttacksBB |= wPawnAttacks;
+ m = wAttacksBB & pos.blackBB & ~pos.pieceTypeBB[Piece.BKING];
+ int tmp = 0;
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ tmp += pieceValue[pos.squares[sq]];
+ m &= m-1;
+ }
+ score += tmp + tmp * tmp / qV;
+
+ // Sum values for all white pieces under attack
+ m = pos.pieceTypeBB[Piece.BKNIGHT];
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ bAttacksBB |= BitBoard.knightAttacks[sq];
+ m &= m-1;
+ }
+ bAttacksBB &= (pos.pieceTypeBB[Piece.WKNIGHT] |
+ pos.pieceTypeBB[Piece.WBISHOP] |
+ pos.pieceTypeBB[Piece.WROOK] |
+ pos.pieceTypeBB[Piece.WQUEEN]);
+ bAttacksBB |= bPawnAttacks;
+ m = bAttacksBB & pos.whiteBB & ~pos.pieceTypeBB[Piece.WKING];
+ tmp = 0;
+ while (m != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(m);
+ tmp += pieceValue[pos.squares[sq]];
+ m &= m-1;
+ }
+ score -= tmp + tmp * tmp / qV;
+ return score / 64;
+ }
+
+ /** Compute king safety for both kings. */
+ private final int kingSafety(Position pos) {
+ final int minM = rV + bV;
+ final int m = (pos.wMtrl - pos.wMtrlPawns + pos.bMtrl - pos.bMtrlPawns) / 2;
+ if (m <= minM)
+ return 0;
+ final int maxM = qV + 2 * rV + 2 * bV + 2 * nV;
+ int score = kingSafetyKPPart(pos);
+ if (Position.getY(pos.wKingSq) == 0) {
+ if (((pos.pieceTypeBB[Piece.WKING] & 0x60L) != 0) && // King on f1 or g1
+ ((pos.pieceTypeBB[Piece.WROOK] & 0xC0L) != 0) && // Rook on g1 or h1
+ ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[6]) != 0) &&
+ ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[7]) != 0)) {
+ score -= 6 * 15;
+ } else
+ if (((pos.pieceTypeBB[Piece.WKING] & 0x6L) != 0) && // King on b1 or c1
+ ((pos.pieceTypeBB[Piece.WROOK] & 0x3L) != 0) && // Rook on a1 or b1
+ ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[0]) != 0) &&
+ ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[1]) != 0)) {
+ score -= 6 * 15;
+ }
+ }
+ if (Position.getY(pos.bKingSq) == 7) {
+ if (((pos.pieceTypeBB[Piece.BKING] & 0x6000000000000000L) != 0) && // King on f8 or g8
+ ((pos.pieceTypeBB[Piece.BROOK] & 0xC000000000000000L) != 0) && // Rook on g8 or h8
+ ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[6]) != 0) &&
+ ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[7]) != 0)) {
+ score += 6 * 15;
+ } else
+ if (((pos.pieceTypeBB[Piece.BKING] & 0x600000000000000L) != 0) && // King on b8 or c8
+ ((pos.pieceTypeBB[Piece.BROOK] & 0x300000000000000L) != 0) && // Rook on a8 or b8
+ ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[0]) != 0) &&
+ ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[1]) != 0)) {
+ score += 6 * 15;
+ }
+ }
+ score += (bKingAttacks - wKingAttacks) * 4;
+ final int kSafety = interpolate(m, minM, 0, maxM, score);
+ return kSafety;
+ }
+
+ private static final class KingSafetyHashData {
+ long key;
+ int score;
+ }
+ private static final KingSafetyHashData[] kingSafetyHash;
+ static {
+ final int numEntries = 1 << 15;
+ kingSafetyHash = new KingSafetyHashData[numEntries];
+ for (int i = 0; i < numEntries; i++) {
+ KingSafetyHashData ksh = new KingSafetyHashData();
+ ksh.key = -1;
+ ksh.score = 0;
+ kingSafetyHash[i] = ksh;
+ }
+ }
+
+ private final int kingSafetyKPPart(Position pos) {
+ final long key = pos.pawnZobristHash() ^ pos.kingZobristHash();
+ KingSafetyHashData ksh = kingSafetyHash[(int)key & (kingSafetyHash.length - 1)];
+ if (ksh.key != key) {
+ int score = 0;
+ long wPawns = pos.pieceTypeBB[Piece.WPAWN];
+ long bPawns = pos.pieceTypeBB[Piece.BPAWN];
+ {
+ int safety = 0;
+ int halfOpenFiles = 0;
+ if (Position.getY(pos.wKingSq) < 2) {
+ long shelter = 1L << Position.getX(pos.wKingSq);
+ shelter |= ((shelter & BitBoard.maskBToHFiles) >>> 1) |
+ ((shelter & BitBoard.maskAToGFiles) << 1);
+ shelter <<= 8;
+ safety += 3 * Long.bitCount(wPawns & shelter);
+ safety -= 2 * Long.bitCount(bPawns & (shelter | (shelter << 8)));
+ shelter <<= 8;
+ safety += 2 * Long.bitCount(wPawns & shelter);
+ shelter <<= 8;
+ safety -= Long.bitCount(bPawns & shelter);
+
+ long wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff;
+ if (wOpen != 0) {
+ halfOpenFiles += 25 * Long.bitCount(wOpen & 0xe7);
+ halfOpenFiles += 10 * Long.bitCount(wOpen & 0x18);
+ }
+ long bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff;
+ if (bOpen != 0) {
+ halfOpenFiles += 25 * Long.bitCount(bOpen & 0xe7);
+ halfOpenFiles += 10 * Long.bitCount(bOpen & 0x18);
+ }
+ safety = Math.min(safety, 8);
+ }
+ final int kSafety = (safety - 9) * 15 - halfOpenFiles;
+ score += kSafety;
+ }
+ {
+ int safety = 0;
+ int halfOpenFiles = 0;
+ if (Position.getY(pos.bKingSq) >= 6) {
+ long shelter = 1L << (56 + Position.getX(pos.bKingSq));
+ shelter |= ((shelter & BitBoard.maskBToHFiles) >>> 1) |
+ ((shelter & BitBoard.maskAToGFiles) << 1);
+ shelter >>>= 8;
+ safety += 3 * Long.bitCount(bPawns & shelter);
+ safety -= 2 * Long.bitCount(wPawns & (shelter | (shelter >>> 8)));
+ shelter >>>= 8;
+ safety += 2 * Long.bitCount(bPawns & shelter);
+ shelter >>>= 8;
+ safety -= Long.bitCount(wPawns & shelter);
+
+ long wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff;
+ if (wOpen != 0) {
+ halfOpenFiles += 25 * Long.bitCount(wOpen & 0xe7);
+ halfOpenFiles += 10 * Long.bitCount(wOpen & 0x18);
+ }
+ long bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff;
+ if (bOpen != 0) {
+ halfOpenFiles += 25 * Long.bitCount(bOpen & 0xe7);
+ halfOpenFiles += 10 * Long.bitCount(bOpen & 0x18);
+ }
+ safety = Math.min(safety, 8);
+ }
+ final int kSafety = (safety - 9) * 15 - halfOpenFiles;
+ score -= kSafety;
+ }
+ ksh.key = key;
+ ksh.score = score;
+ }
+ return ksh.score;
+ }
+
+ /** Implements special knowledge for some endgame situations. */
+ private final int endGameEval(Position pos, int oldScore) {
+ int score = oldScore;
+ if (pos.wMtrl + pos.bMtrl > 6 * rV)
+ return score;
+ final int wMtrlPawns = pos.wMtrlPawns;
+ final int bMtrlPawns = pos.bMtrlPawns;
+ final int wMtrlNoPawns = pos.wMtrl - wMtrlPawns;
+ final int bMtrlNoPawns = pos.bMtrl - bMtrlPawns;
+
+ boolean handled = false;
+ if ((wMtrlPawns + bMtrlPawns == 0) && (wMtrlNoPawns < rV) && (bMtrlNoPawns < rV)) {
+ // King + minor piece vs king + minor piece is a draw
+ return 0;
+ }
+ if (!handled && (pos.wMtrl == qV) && (pos.bMtrl == pV) &&
+ (Long.bitCount(pos.pieceTypeBB[Piece.WQUEEN]) == 1)) {
+ int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]);
+ int wq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WQUEEN]);
+ int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]);
+ int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]);
+ score = evalKQKP(wk, wq, bk, bp);
+ handled = true;
+ }
+ if (!handled && (pos.wMtrl == rV) && (pos.bMtrl == pV) &&
+ (Long.bitCount(pos.pieceTypeBB[Piece.WROOK]) == 1)) {
+ int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]);
+ score = krkpEval(pos.getKingSq(true), pos.getKingSq(false),
+ bp, pos.whiteMove);
+ handled = true;
+ }
+ if (!handled && (pos.bMtrl == qV) && (pos.wMtrl == pV) &&
+ (Long.bitCount(pos.pieceTypeBB[Piece.BQUEEN]) == 1)) {
+ int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]);
+ int bq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BQUEEN]);
+ int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]);
+ int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]);
+ score = -evalKQKP(63-bk, 63-bq, 63-wk, 63-wp);
+ handled = true;
+ }
+ if (!handled && (pos.bMtrl == rV) && (pos.wMtrl == pV) &&
+ (Long.bitCount(pos.pieceTypeBB[Piece.BROOK]) == 1)) {
+ int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]);
+ score = -krkpEval(63-pos.getKingSq(false), 63-pos.getKingSq(true),
+ 63-wp, !pos.whiteMove);
+ handled = true;
+ }
+ if (!handled && (score > 0)) {
+ if ((wMtrlPawns == 0) && (wMtrlNoPawns <= bMtrlNoPawns + bV)) {
+ if (wMtrlNoPawns < rV) {
+ return -pos.bMtrl / 50;
+ } else {
+ score /= 8; // Too little excess material, probably draw
+ handled = true;
+ }
+ } else if ((pos.pieceTypeBB[Piece.WROOK] | pos.pieceTypeBB[Piece.WKNIGHT] |
+ pos.pieceTypeBB[Piece.WQUEEN]) == 0) {
+ // Check for rook pawn + wrong color bishop
+ if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskBToHFiles) == 0) &&
+ ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) == 0) &&
+ ((pos.pieceTypeBB[Piece.BKING] & 0x0303000000000000L) != 0)) {
+ return 0;
+ } else
+ if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskAToGFiles) == 0) &&
+ ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) == 0) &&
+ ((pos.pieceTypeBB[Piece.BKING] & 0xC0C0000000000000L) != 0)) {
+ return 0;
+ }
+ }
+ }
+ if (!handled) {
+ if (bMtrlPawns == 0) {
+ if (wMtrlNoPawns - bMtrlNoPawns > bV) {
+ int wKnights = Long.bitCount(pos.pieceTypeBB[Piece.WKNIGHT]);
+ int wBishops = Long.bitCount(pos.pieceTypeBB[Piece.WBISHOP]);
+ if ((wKnights == 2) && (wMtrlNoPawns == 2 * nV) && (bMtrlNoPawns == 0)) {
+ score /= 50; // KNNK is a draw
+ } else if ((wKnights == 1) && (wBishops == 1) && (wMtrlNoPawns == nV + bV) && (bMtrlNoPawns == 0)) {
+ score /= 10;
+ score += nV + bV + 300;
+ final int kSq = pos.getKingSq(false);
+ final int x = Position.getX(kSq);
+ final int y = Position.getY(kSq);
+ if ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) != 0) {
+ score += (7 - distToH1A8[7-y][7-x]) * 10;
+ } else {
+ score += (7 - distToH1A8[7-y][x]) * 10;
+ }
+ } else {
+ score += 300; // Enough excess material, should win
+ }
+ handled = true;
+ } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (wMtrlPawns == pV)) { // KPK
+ int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]);
+ score = kpkEval(pos.getKingSq(true), pos.getKingSq(false),
+ wp, pos.whiteMove);
+ handled = true;
+ }
+ }
+ }
+ if (!handled && (score < 0)) {
+ if ((bMtrlPawns == 0) && (bMtrlNoPawns <= wMtrlNoPawns + bV)) {
+ if (bMtrlNoPawns < rV) {
+ return pos.wMtrl / 50;
+ } else {
+ score /= 8; // Too little excess material, probably draw
+ handled = true;
+ }
+ } else if ((pos.pieceTypeBB[Piece.BROOK] | pos.pieceTypeBB[Piece.BKNIGHT] |
+ pos.pieceTypeBB[Piece.BQUEEN]) == 0) {
+ // Check for rook pawn + wrong color bishop
+ if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskBToHFiles) == 0) &&
+ ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) == 0) &&
+ ((pos.pieceTypeBB[Piece.WKING] & 0x0303L) != 0)) {
+ return 0;
+ } else
+ if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskAToGFiles) == 0) &&
+ ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) == 0) &&
+ ((pos.pieceTypeBB[Piece.WKING] & 0xC0C0L) != 0)) {
+ return 0;
+ }
+ }
+ }
+ if (!handled) {
+ if (wMtrlPawns == 0) {
+ if (bMtrlNoPawns - wMtrlNoPawns > bV) {
+ int bKnights = Long.bitCount(pos.pieceTypeBB[Piece.BKNIGHT]);
+ int bBishops = Long.bitCount(pos.pieceTypeBB[Piece.BBISHOP]);
+ if ((bKnights == 2) && (bMtrlNoPawns == 2 * nV) && (wMtrlNoPawns == 0)) {
+ score /= 50; // KNNK is a draw
+ } else if ((bKnights == 1) && (bBishops == 1) && (bMtrlNoPawns == nV + bV) && (wMtrlNoPawns == 0)) {
+ score /= 10;
+ score -= nV + bV + 300;
+ final int kSq = pos.getKingSq(true);
+ final int x = Position.getX(kSq);
+ final int y = Position.getY(kSq);
+ if ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) != 0) {
+ score -= (7 - distToH1A8[7-y][7-x]) * 10;
+ } else {
+ score -= (7 - distToH1A8[7-y][x]) * 10;
+ }
+ } else {
+ score -= 300; // Enough excess material, should win
+ }
+ handled = true;
+ } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (bMtrlPawns == pV)) { // KPK
+ int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]);
+ score = -kpkEval(63-pos.getKingSq(false), 63-pos.getKingSq(true),
+ 63-bp, !pos.whiteMove);
+ handled = true;
+ }
+ }
+ }
+ return score;
+
+ // FIXME! Add evaluation of KRPKR : eg 8/8/8/5pk1/1r6/R7/8/4K3 w - - 0 74
+ // FIXME! KRBKR is very hard to draw
+ }
+
+ private static final int evalKQKP(int wKing, int wQueen, int bKing, int bPawn) {
+ boolean canWin = false;
+ if (((1L << bKing) & 0xFFFF) == 0) {
+ canWin = true; // King doesn't support pawn
+ } else if (Math.abs(Position.getX(bPawn) - Position.getX(bKing)) > 2) {
+ canWin = true; // King doesn't support pawn
+ } else {
+ switch (bPawn) {
+ case 8: // a2
+ canWin = ((1L << wKing) & 0x0F1F1F1F1FL) != 0;
+ break;
+ case 10: // c2
+ canWin = ((1L << wKing) & 0x071F1F1FL) != 0;
+ break;
+ case 13: // f2
+ canWin = ((1L << wKing) & 0xE0F8F8F8L) != 0;
+ break;
+ case 15: // h2
+ canWin = ((1L << wKing) & 0xF0F8F8F8F8L) != 0;
+ break;
+ default:
+ canWin = true;
+ break;
+ }
+ }
+
+ final int dist = Math.max(Math.abs(Position.getX(wKing)-Position.getX(bPawn)),
+ Math.abs(Position.getY(wKing)-Position.getY(bPawn)));
+ int score = qV - pV - 20 * dist;
+ if (!canWin)
+ score /= 50;
+ return score;
+ }
+
+ private static final int kpkEval(int wKing, int bKing, int wPawn, boolean whiteMove) {
+ if (Position.getX(wKing) >= 4) { // Mirror X
+ wKing ^= 7;
+ bKing ^= 7;
+ wPawn ^= 7;
+ }
+ int index = whiteMove ? 0 : 1;
+ index = index * 32 + Position.getY(wKing)*4+Position.getX(wKing);
+ index = index * 64 + bKing;
+ index = index * 48 + wPawn - 8;
+
+ int bytePos = index / 8;
+ int bitPos = index % 8;
+ boolean draw = (((int)kpkTable[bytePos]) & (1 << bitPos)) == 0;
+ if (draw)
+ return 0;
+ return qV - pV / 4 * (7-Position.getY(wPawn));
+ }
+
+ private static final int krkpEval(int wKing, int bKing, int bPawn, boolean whiteMove) {
+ if (Position.getX(bKing) >= 4) { // Mirror X
+ wKing ^= 7;
+ bKing ^= 7;
+ bPawn ^= 7;
+ }
+ int index = whiteMove ? 0 : 1;
+ index = index * 32 + Position.getY(bKing)*4+Position.getX(bKing);
+ index = index * 48 + bPawn - 8;
+ index = index * 8 + Position.getY(wKing);
+ byte mask = krkpTable[index];
+ boolean canWin = (mask & (1 << Position.getX(wKing))) != 0;
+
+ int score = rV - pV + Position.getY(bPawn) * pV / 4;
+ if (canWin)
+ score += 150;
+ else
+ score /= 50;
+ return score;
+ }
+
+ /**
+ * Interpolate between (x1,y1) and (x2,y2).
+ * If x < x1, return y1, if x > x2 return y2. Otherwise, use linear interpolation.
+ */
+ static final int interpolate(int x, int x1, int y1, int x2, int y2) {
+ if (x > x2) {
+ return y2;
+ } else if (x < x1) {
+ return y1;
+ } else {
+ return (x - x1) * (y2 - y1) / (x2 - x1) + y1;
+ }
+ }
+}
diff --git a/CuckooChessEngine/src/chess/Game.java b/CuckooChessEngine/src/chess/Game.java
new file mode 100644
index 0000000..b23a57f
--- /dev/null
+++ b/CuckooChessEngine/src/chess/Game.java
@@ -0,0 +1,572 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ *
+ * @author petero
+ */
+public class Game {
+ protected List moveList = null;
+ protected List uiInfoList = null;
+ List drawOfferList = null;
+ protected int currentMove;
+ boolean pendingDrawOffer;
+ GameState drawState;
+ String drawStateMoveStr; // Move required to claim DRAW_REP or DRAW_50
+ GameState resignState;
+ public Position pos = null;
+ protected Player whitePlayer;
+ protected Player blackPlayer;
+
+ public Game(Player whitePlayer, Player blackPlayer) {
+ this.whitePlayer = whitePlayer;
+ this.blackPlayer = blackPlayer;
+ handleCommand("new");
+ }
+
+ /**
+ * Update the game state according to move/command string from a player.
+ * @param str The move or command to process.
+ * @return True if str was understood, false otherwise.
+ */
+ public boolean processString(String str) {
+ if (handleCommand(str)) {
+ return true;
+ }
+ if (getGameState() != GameState.ALIVE) {
+ return false;
+ }
+
+ Move m = TextIO.stringToMove(pos, str);
+ if (m == null) {
+ return false;
+ }
+
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(m, ui);
+ TextIO.fixupEPSquare(pos);
+ while (currentMove < moveList.size()) {
+ moveList.remove(currentMove);
+ uiInfoList.remove(currentMove);
+ drawOfferList.remove(currentMove);
+ }
+ moveList.add(m);
+ uiInfoList.add(ui);
+ drawOfferList.add(pendingDrawOffer);
+ pendingDrawOffer = false;
+ currentMove++;
+ return true;
+ }
+
+ public final String getGameStateString() {
+ switch (getGameState()) {
+ case ALIVE:
+ return "";
+ case WHITE_MATE:
+ return "Game over, white mates!";
+ case BLACK_MATE:
+ return "Game over, black mates!";
+ case WHITE_STALEMATE:
+ case BLACK_STALEMATE:
+ return "Game over, draw by stalemate!";
+ case DRAW_REP:
+ {
+ String ret = "Game over, draw by repetition!";
+ if ((drawStateMoveStr != null) && (drawStateMoveStr.length() > 0)) {
+ ret = ret + " [" + drawStateMoveStr + "]";
+ }
+ return ret;
+ }
+ case DRAW_50:
+ {
+ String ret = "Game over, draw by 50 move rule!";
+ if ((drawStateMoveStr != null) && (drawStateMoveStr.length() > 0)) {
+ ret = ret + " [" + drawStateMoveStr + "]";
+ }
+ return ret;
+ }
+ case DRAW_NO_MATE:
+ return "Game over, draw by impossibility of mate!";
+ case DRAW_AGREE:
+ return "Game over, draw by agreement!";
+ case RESIGN_WHITE:
+ return "Game over, white resigns!";
+ case RESIGN_BLACK:
+ return "Game over, black resigns!";
+ default:
+ throw new RuntimeException();
+ }
+ }
+
+ /**
+ * Get the last played move, or null if no moves played yet.
+ */
+ public Move getLastMove() {
+ Move m = null;
+ if (currentMove > 0) {
+ m = moveList.get(currentMove - 1);
+ }
+ return m;
+ }
+
+ public enum GameState {
+ ALIVE,
+ WHITE_MATE, // White mates
+ BLACK_MATE, // Black mates
+ WHITE_STALEMATE, // White is stalemated
+ BLACK_STALEMATE, // Black is stalemated
+ DRAW_REP, // Draw by 3-fold repetition
+ DRAW_50, // Draw by 50 move rule
+ DRAW_NO_MATE, // Draw by impossibility of check mate
+ DRAW_AGREE, // Draw by agreement
+ RESIGN_WHITE, // White resigns
+ RESIGN_BLACK // Black resigns
+ }
+
+ /**
+ * Get the current state of the game.
+ */
+ public GameState getGameState() {
+ MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ if (moves.size == 0) {
+ if (MoveGen.inCheck(pos)) {
+ return pos.whiteMove ? GameState.BLACK_MATE : GameState.WHITE_MATE;
+ } else {
+ return pos.whiteMove ? GameState.WHITE_STALEMATE : GameState.BLACK_STALEMATE;
+ }
+ }
+ if (insufficientMaterial()) {
+ return GameState.DRAW_NO_MATE;
+ }
+ if (resignState != GameState.ALIVE) {
+ return resignState;
+ }
+ return drawState;
+ }
+
+ /**
+ * Check if a draw offer is available.
+ * @return True if the current player has the option to accept a draw offer.
+ */
+ public boolean haveDrawOffer() {
+ if (currentMove > 0) {
+ return drawOfferList.get(currentMove - 1);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Handle a special command.
+ * @param moveStr The command to handle
+ * @return True if command handled, false otherwise.
+ */
+ protected boolean handleCommand(String moveStr) {
+ if (moveStr.equals("new")) {
+ moveList = new ArrayList();
+ uiInfoList = new ArrayList();
+ drawOfferList = new ArrayList();
+ currentMove = 0;
+ pendingDrawOffer = false;
+ drawState = GameState.ALIVE;
+ resignState = GameState.ALIVE;
+ try {
+ pos = TextIO.readFEN(TextIO.startPosFEN);
+ } catch (ChessParseError ex) {
+ throw new RuntimeException();
+ }
+ whitePlayer.clearTT();
+ blackPlayer.clearTT();
+ activateHumanPlayer();
+ return true;
+ } else if (moveStr.equals("undo")) {
+ if (currentMove > 0) {
+ pos.unMakeMove(moveList.get(currentMove - 1), uiInfoList.get(currentMove - 1));
+ currentMove--;
+ pendingDrawOffer = false;
+ drawState = GameState.ALIVE;
+ resignState = GameState.ALIVE;
+ return handleCommand("swap");
+ } else {
+ System.out.println("Nothing to undo");
+ }
+ return true;
+ } else if (moveStr.equals("redo")) {
+ if (currentMove < moveList.size()) {
+ pos.makeMove(moveList.get(currentMove), uiInfoList.get(currentMove));
+ currentMove++;
+ pendingDrawOffer = false;
+ return handleCommand("swap");
+ } else {
+ System.out.println("Nothing to redo");
+ }
+ return true;
+ } else if (moveStr.equals("swap") || moveStr.equals("go")) {
+ Player tmp = whitePlayer;
+ whitePlayer = blackPlayer;
+ blackPlayer = tmp;
+ return true;
+ } else if (moveStr.equals("list")) {
+ listMoves();
+ return true;
+ } else if (moveStr.startsWith("setpos ")) {
+ String fen = moveStr.substring(moveStr.indexOf(" ") + 1);
+ Position newPos = null;
+ try {
+ newPos = TextIO.readFEN(fen);
+ } catch (ChessParseError ex) {
+ System.out.printf("Invalid FEN: %s (%s)%n", fen, ex.getMessage());
+ }
+ if (newPos != null) {
+ handleCommand("new");
+ pos = newPos;
+ activateHumanPlayer();
+ }
+ return true;
+ } else if (moveStr.equals("getpos")) {
+ String fen = TextIO.toFEN(pos);
+ System.out.println(fen);
+ return true;
+ } else if (moveStr.startsWith("draw ")) {
+ if (getGameState() == GameState.ALIVE) {
+ String drawCmd = moveStr.substring(moveStr.indexOf(" ") + 1);
+ return handleDrawCmd(drawCmd);
+ } else {
+ return true;
+ }
+ } else if (moveStr.equals("resign")) {
+ if (getGameState()== GameState.ALIVE) {
+ resignState = pos.whiteMove ? GameState.RESIGN_WHITE : GameState.RESIGN_BLACK;
+ return true;
+ } else {
+ return true;
+ }
+ } else if (moveStr.startsWith("book")) {
+ String bookCmd = moveStr.substring(moveStr.indexOf(" ") + 1);
+ return handleBookCmd(bookCmd);
+ } else if (moveStr.startsWith("time")) {
+ try {
+ String timeStr = moveStr.substring(moveStr.indexOf(" ") + 1);
+ int timeLimit = Integer.parseInt(timeStr);
+ whitePlayer.timeLimit(timeLimit, timeLimit, false);
+ blackPlayer.timeLimit(timeLimit, timeLimit, false);
+ return true;
+ }
+ catch (NumberFormatException nfe) {
+ System.out.printf("Number format exception: %s\n", nfe.getMessage());
+ return false;
+ }
+ } else if (moveStr.startsWith("perft ")) {
+ try {
+ String depthStr = moveStr.substring(moveStr.indexOf(" ") + 1);
+ int depth = Integer.parseInt(depthStr);
+ MoveGen moveGen = new MoveGen();
+ long t0 = System.currentTimeMillis();
+ long nodes = perfT(moveGen, pos, depth);
+ long t1 = System.currentTimeMillis();
+ System.out.printf("perft(%d) = %d, t=%.3fs\n", depth, nodes, (t1 - t0)*1e-3);
+ }
+ catch (NumberFormatException nfe) {
+ System.out.printf("Number format exception: %s\n", nfe.getMessage());
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /** Swap players around if needed to make the human player in control of the next move. */
+ protected void activateHumanPlayer() {
+ if (!(pos.whiteMove ? whitePlayer : blackPlayer).isHumanPlayer()) {
+ Player tmp = whitePlayer;
+ whitePlayer = blackPlayer;
+ blackPlayer = tmp;
+ }
+ }
+
+ public List getPosHistory() {
+ List ret = new ArrayList();
+
+ Position pos = new Position(this.pos);
+ for (int i = currentMove; i > 0; i--) {
+ pos.unMakeMove(moveList.get(i - 1), uiInfoList.get(i - 1));
+ }
+ ret.add(TextIO.toFEN(pos)); // Store initial FEN
+
+ StringBuilder moves = new StringBuilder();
+ for (int i = 0; i < moveList.size(); i++) {
+ Move move = moveList.get(i);
+ String strMove = TextIO.moveToString(pos, move, false);
+ moves.append(String.format(" %s", strMove));
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(move, ui);
+ }
+ ret.add(moves.toString()); // Store move list string
+ int numUndo = moveList.size() - currentMove;
+ ret.add(((Integer)numUndo).toString());
+ return ret;
+ }
+
+ /**
+ * Print a list of all moves.
+ */
+ private void listMoves() {
+ String movesStr = getMoveListString(false);
+ System.out.printf("%s", movesStr);
+ }
+
+ final public String getMoveListString(boolean compressed) {
+ StringBuilder ret = new StringBuilder();
+
+ // Undo all moves in move history.
+ Position pos = new Position(this.pos);
+ for (int i = currentMove; i > 0; i--) {
+ pos.unMakeMove(moveList.get(i - 1), uiInfoList.get(i - 1));
+ }
+
+ // Print all moves
+ String whiteMove = "";
+ String blackMove = "";
+ for (int i = 0; i < currentMove; i++) {
+ Move move = moveList.get(i);
+ String strMove = TextIO.moveToString(pos, move, false);
+ if (drawOfferList.get(i)) {
+ strMove += " (d)";
+ }
+ if (pos.whiteMove) {
+ whiteMove = strMove;
+ } else {
+ blackMove = strMove;
+ if (whiteMove.length() == 0) {
+ whiteMove = "...";
+ }
+ if (compressed) {
+ ret.append(String.format("%d. %s %s ",
+ pos.fullMoveCounter, whiteMove, blackMove));
+ } else {
+ ret.append(String.format("%3d. %-10s %-10s%n",
+ pos.fullMoveCounter, whiteMove, blackMove));
+ }
+ whiteMove = "";
+ blackMove = "";
+ }
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(move, ui);
+ }
+ if ((whiteMove.length() > 0) || (blackMove.length() > 0)) {
+ if (whiteMove.length() == 0) {
+ whiteMove = "...";
+ }
+ if (compressed) {
+ ret.append(String.format("%d. %s %s ",
+ pos.fullMoveCounter, whiteMove, blackMove));
+ } else {
+ ret.append(String.format("%3d. %-8s %-8s%n",
+ pos.fullMoveCounter, whiteMove, blackMove));
+ }
+ }
+ String gameResult = getPGNResultString();
+ if (!gameResult.equals("*")) {
+ if (compressed) {
+ ret.append(gameResult);
+ } else {
+ ret.append(String.format("%s%n", gameResult));
+ }
+ }
+ return ret.toString();
+ }
+
+ public final String getPGNResultString() {
+ String gameResult = "*";
+ switch (getGameState()) {
+ case ALIVE:
+ break;
+ case WHITE_MATE:
+ case RESIGN_BLACK:
+ gameResult = "1-0";
+ break;
+ case BLACK_MATE:
+ case RESIGN_WHITE:
+ gameResult = "0-1";
+ break;
+ case WHITE_STALEMATE:
+ case BLACK_STALEMATE:
+ case DRAW_REP:
+ case DRAW_50:
+ case DRAW_NO_MATE:
+ case DRAW_AGREE:
+ gameResult = "1/2-1/2";
+ break;
+ }
+ return gameResult;
+ }
+
+ /** Return a list of previous positions in this game, back to the last "zeroing" move. */
+ public ArrayList getHistory() {
+ ArrayList posList = new ArrayList();
+ Position pos = new Position(this.pos);
+ for (int i = currentMove; i > 0; i--) {
+ if (pos.halfMoveClock == 0)
+ break;
+ pos.unMakeMove(moveList.get(i- 1), uiInfoList.get(i- 1));
+ posList.add(new Position(pos));
+ }
+ Collections.reverse(posList);
+ return posList;
+ }
+
+ private boolean handleDrawCmd(String drawCmd) {
+ if (drawCmd.startsWith("rep") || drawCmd.startsWith("50")) {
+ boolean rep = drawCmd.startsWith("rep");
+ Move m = null;
+ String ms = drawCmd.substring(drawCmd.indexOf(" ") + 1);
+ if (ms.length() > 0) {
+ m = TextIO.stringToMove(pos, ms);
+ }
+ boolean valid;
+ if (rep) {
+ valid = false;
+ List oldPositions = new ArrayList();
+ if (m != null) {
+ UndoInfo ui = new UndoInfo();
+ Position tmpPos = new Position(pos);
+ tmpPos.makeMove(m, ui);
+ oldPositions.add(tmpPos);
+ }
+ oldPositions.add(pos);
+ Position tmpPos = pos;
+ for (int i = currentMove - 1; i >= 0; i--) {
+ tmpPos = new Position(tmpPos);
+ tmpPos.unMakeMove(moveList.get(i), uiInfoList.get(i));
+ oldPositions.add(tmpPos);
+ }
+ int repetitions = 0;
+ Position firstPos = oldPositions.get(0);
+ for (Position p : oldPositions) {
+ if (p.drawRuleEquals(firstPos))
+ repetitions++;
+ }
+ if (repetitions >= 3) {
+ valid = true;
+ }
+ } else {
+ Position tmpPos = new Position(pos);
+ if (m != null) {
+ UndoInfo ui = new UndoInfo();
+ tmpPos.makeMove(m, ui);
+ }
+ valid = tmpPos.halfMoveClock >= 100;
+ }
+ if (valid) {
+ drawState = rep ? GameState.DRAW_REP : GameState.DRAW_50;
+ drawStateMoveStr = null;
+ if (m != null) {
+ drawStateMoveStr = TextIO.moveToString(pos, m, false);
+ }
+ } else {
+ pendingDrawOffer = true;
+ if (m != null) {
+ processString(ms);
+ }
+ }
+ return true;
+ } else if (drawCmd.startsWith("offer ")) {
+ pendingDrawOffer = true;
+ String ms = drawCmd.substring(drawCmd.indexOf(" ") + 1);
+ if (TextIO.stringToMove(pos, ms) != null) {
+ processString(ms);
+ }
+ return true;
+ } else if (drawCmd.equals("accept")) {
+ if (haveDrawOffer()) {
+ drawState = GameState.DRAW_AGREE;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean handleBookCmd(String bookCmd) {
+ if (bookCmd.equals("off")) {
+ whitePlayer.useBook(false);
+ blackPlayer.useBook(false);
+ return true;
+ } else if (bookCmd.equals("on")) {
+ whitePlayer.useBook(true);
+ whitePlayer.useBook(true);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean insufficientMaterial() {
+ if (pos.pieceTypeBB[Piece.WQUEEN] != 0) return false;
+ if (pos.pieceTypeBB[Piece.WROOK] != 0) return false;
+ if (pos.pieceTypeBB[Piece.WPAWN] != 0) return false;
+ if (pos.pieceTypeBB[Piece.BQUEEN] != 0) return false;
+ if (pos.pieceTypeBB[Piece.BROOK] != 0) return false;
+ if (pos.pieceTypeBB[Piece.BPAWN] != 0) return false;
+ int wb = Long.bitCount(pos.pieceTypeBB[Piece.WBISHOP]);
+ int wn = Long.bitCount(pos.pieceTypeBB[Piece.WKNIGHT]);
+ int bb = Long.bitCount(pos.pieceTypeBB[Piece.BBISHOP]);
+ int bn = Long.bitCount(pos.pieceTypeBB[Piece.BKNIGHT]);
+ if (wb + wn + bb + bn <= 1) {
+ return true; // King + bishop/knight vs king is draw
+ }
+ if (wn + bn == 0) {
+ // Only bishops. If they are all on the same color, the position is a draw.
+ long bMask = pos.pieceTypeBB[Piece.WBISHOP] | pos.pieceTypeBB[Piece.BBISHOP];
+ if (((bMask & BitBoard.maskDarkSq) == 0) ||
+ ((bMask & BitBoard.maskLightSq) == 0))
+ return true;
+ }
+
+ return false;
+ }
+
+ final static long perfT(MoveGen moveGen, Position pos, int depth) {
+ if (depth == 0)
+ return 1;
+ long nodes = 0;
+ MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ if (depth == 1) {
+ int ret = moves.size;
+ moveGen.returnMoveList(moves);
+ return ret;
+ }
+ UndoInfo ui = new UndoInfo();
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ pos.makeMove(m, ui);
+ nodes += perfT(moveGen, pos, depth - 1);
+ pos.unMakeMove(m, ui);
+ }
+ moveGen.returnMoveList(moves);
+ return nodes;
+ }
+}
diff --git a/CuckooChessEngine/src/chess/History.java b/CuckooChessEngine/src/chess/History.java
new file mode 100644
index 0000000..4935264
--- /dev/null
+++ b/CuckooChessEngine/src/chess/History.java
@@ -0,0 +1,80 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+/**
+ * Implements the relative history heuristic.
+ * @author petero
+ */
+public final class History {
+ private final int countSuccess[][];
+ private final int countFail[][];
+ private final int score[][];
+
+ public History() {
+ countSuccess = new int[Piece.nPieceTypes][64];
+ countFail = new int[Piece.nPieceTypes][64];
+ score = new int[Piece.nPieceTypes][64];
+ for (int p = 0; p < Piece.nPieceTypes; p++) {
+ for (int sq = 0; sq < 64; sq++) {
+ countSuccess[p][sq] = 0;
+ countFail[p][sq] = 0;
+ score[p][sq] = -1;
+ }
+ }
+ }
+
+ /** Record move as a success. */
+ public final void addSuccess(Position pos, Move m, int depth) {
+ int p = pos.getPiece(m.from);
+ int cnt = depth;
+ int val = countSuccess[p][m.to] + cnt;
+ if (val > 1000) {
+ val /= 2;
+ countFail[p][m.to] /= 2;
+ }
+ countSuccess[p][m.to] = val;
+ score[p][m.to] = -1;
+ }
+
+ /** Record move as a failure. */
+ public final void addFail(Position pos, Move m, int depth) {
+ int p = pos.getPiece(m.from);
+ int cnt = depth;
+ countFail[p][m.to] += cnt;
+ score[p][m.to] = -1;
+ }
+
+ /** Get a score between 0 and 49, depending of the success/fail ratio of the move. */
+ public final int getHistScore(Position pos, Move m) {
+ int p = pos.getPiece(m.from);
+ int ret = score[p][m.to];
+ if (ret >= 0)
+ return ret;
+ int succ = countSuccess[p][m.to];
+ int fail = countFail[p][m.to];
+ if (succ + fail > 0) {
+ ret = succ * 49 / (succ + fail);
+ } else {
+ ret = 0;
+ }
+ score[p][m.to] = ret;
+ return ret;
+ }
+}
diff --git a/CuckooChessEngine/src/chess/HumanPlayer.java b/CuckooChessEngine/src/chess/HumanPlayer.java
new file mode 100644
index 0000000..e0e29f9
--- /dev/null
+++ b/CuckooChessEngine/src/chess/HumanPlayer.java
@@ -0,0 +1,73 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+
+/**
+ * A player that reads input from the keyboard.
+ * @author petero
+ */
+public class HumanPlayer implements Player {
+ private String lastCmd = "";
+ private BufferedReader in;
+
+ public HumanPlayer() {
+ in = new BufferedReader(new InputStreamReader(System.in));
+ }
+
+ @Override
+ public String getCommand(Position pos, boolean drawOffer, List history) {
+ try {
+ String color = pos.whiteMove ? "white" : "black";
+ System.out.print(String.format("Enter move (%s):", color));
+ String moveStr = in.readLine();
+ if (moveStr == null)
+ return "quit";
+ if (moveStr.length() == 0) {
+ return lastCmd;
+ } else {
+ lastCmd = moveStr;
+ }
+ return moveStr;
+ } catch (IOException ex) {
+ return "quit";
+ }
+ }
+
+ @Override
+ public boolean isHumanPlayer() {
+ return true;
+ }
+
+ @Override
+ public void useBook(boolean bookOn) {
+ }
+
+ @Override
+ public void timeLimit(int minTimeLimit, int maxTimeLimit, boolean randomMode) {
+ }
+
+ @Override
+ public void clearTT() {
+ }
+}
diff --git a/CuckooChessEngine/src/chess/KillerTable.java b/CuckooChessEngine/src/chess/KillerTable.java
new file mode 100644
index 0000000..372639b
--- /dev/null
+++ b/CuckooChessEngine/src/chess/KillerTable.java
@@ -0,0 +1,83 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+/**
+ * Implement a table of killer moves for the killer heuristic.
+ * @author petero
+ */
+public class KillerTable {
+ /** There is one KTEntry for each ply in the search tree. */
+ static final class KTEntry {
+ public KTEntry() {
+ move0 = move1 = 0;
+ }
+ int move0;
+ int move1;
+ }
+ KTEntry[] ktList;
+
+ /** Create an empty killer table. */
+ public KillerTable() {
+ ktList = new KTEntry[200];
+ for (int i = 0; i < ktList.length; i++)
+ ktList[i] = new KTEntry();
+ }
+
+ /** Add a killer move to the table. Moves are replaced on an LRU basis. */
+ final public void addKiller(int ply, Move m) {
+ if (ply >= ktList.length)
+ return;
+ int move = (short)(m.from + (m.to << 6) + (m.promoteTo << 12));
+ KTEntry ent = ktList[ply];
+ if (move != ent.move0) {
+ ent.move1 = ent.move0;
+ ent.move0 = move;
+ }
+ }
+
+ /**
+ * Get a score for move m based on hits in the killer table.
+ * The score is 4 for primary hit at ply.
+ * The score is 3 for secondary hit at ply.
+ * The score is 2 for primary hit at ply - 2.
+ * The score is 1 for secondary hit at ply - 2.
+ * The score is 0 otherwise.
+ */
+ final public int getKillerScore(int ply, Move m) {
+ int move = (short)(m.from + (m.to << 6) + (m.promoteTo << 12));
+ if (ply < ktList.length) {
+ KTEntry ent = ktList[ply];
+ if (move == ent.move0) {
+ return 4;
+ } else if (move == ent.move1) {
+ return 3;
+ }
+ }
+ if ((ply - 2 >= 0) && (ply - 2 < ktList.length)) {
+ KTEntry ent = ktList[ply - 2];
+ if (move == ent.move0) {
+ return 2;
+ } else if (move == ent.move1) {
+ return 1;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/CuckooChessEngine/src/chess/Move.java b/CuckooChessEngine/src/chess/Move.java
new file mode 100644
index 0000000..7f7cc51
--- /dev/null
+++ b/CuckooChessEngine/src/chess/Move.java
@@ -0,0 +1,97 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.util.Comparator;
+
+/**
+ *
+ * @author petero
+ */
+public class Move {
+ /** From square, 0-63. */
+ public int from;
+
+ /** To square, 0-63. */
+ public int to;
+
+ /** Promotion piece. */
+ public int promoteTo;
+
+ public int score;
+
+ /** Create a move object. */
+ public Move(int from, int to, int promoteTo) {
+ this.from = from;
+ this.to = to;
+ this.promoteTo = promoteTo;
+ this.score = 0;
+ }
+
+ public Move(int from, int to, int promoteTo, int score) {
+ this.from = from;
+ this.to = to;
+ this.promoteTo = promoteTo;
+ this.score = score;
+ }
+
+ static public class SortByScore implements Comparator {
+ public int compare(Move sm1, Move sm2) {
+ return sm2.score - sm1.score;
+ }
+ }
+
+ public Move(Move m) {
+ this.from = m.from;
+ this.to = m.to;
+ this.promoteTo = m.promoteTo;
+ this.score = m.score;
+ }
+
+ public void copyFrom(Move m) {
+ from = m.from;
+ to = m.to;
+ promoteTo = m.promoteTo;
+// score = m.score;
+ }
+
+ /** Note that score is not included in the comparison. */
+ @Override
+ public boolean equals(Object o) {
+ if ((o == null) || (o.getClass() != this.getClass()))
+ return false;
+ Move other = (Move)o;
+ if (from != other.from)
+ return false;
+ if (to != other.to)
+ return false;
+ if (promoteTo != other.promoteTo)
+ return false;
+ return true;
+ }
+ @Override
+ public int hashCode() {
+ return (from * 64 + to) * 16 + promoteTo;
+ }
+
+ /** Useful for debugging. */
+ public final String toString() {
+ return TextIO.moveToUCIString(this);
+ }
+}
diff --git a/CuckooChessEngine/src/chess/MoveGen.java b/CuckooChessEngine/src/chess/MoveGen.java
new file mode 100644
index 0000000..ee112b4
--- /dev/null
+++ b/CuckooChessEngine/src/chess/MoveGen.java
@@ -0,0 +1,1064 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+/**
+ *
+ * @author petero
+ */
+public final class MoveGen {
+ static final MoveGen instance;
+ static {
+ instance = new MoveGen();
+ }
+
+ public final static class MoveList {
+ public final Move[] m;
+ public int size;
+ MoveList() {
+ m = new Move[MAX_MOVES];
+ this.size = 0;
+ }
+ }
+
+ /**
+ * Generate and return a list of pseudo-legal moves.
+ * Pseudo-legal means that the moves doesn't necessarily defend from check threats.
+ */
+ public final MoveList pseudoLegalMoves(Position pos) {
+ MoveList moveList = getMoveListObj();
+ final long occupied = pos.whiteBB | pos.blackBB;
+ if (pos.whiteMove) {
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.WQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.WROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.WBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // King moves
+ {
+ int sq = pos.getKingSq(true);
+ long m = BitBoard.kingAttacks[sq] & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ final int k0 = 4;
+ if (sq == k0) {
+ final long OO_SQ = 0x60L;
+ final long OOO_SQ = 0xEL;
+ if (((pos.getCastleMask() & (1 << Position.H1_CASTLE)) != 0) &&
+ ((OO_SQ & (pos.whiteBB | pos.blackBB)) == 0) &&
+ (pos.getPiece(k0 + 3) == Piece.WROOK) &&
+ !sqAttacked(pos, k0) &&
+ !sqAttacked(pos, k0 + 1)) {
+ setMove(moveList, k0, k0 + 2, Piece.EMPTY);
+ }
+ if (((pos.getCastleMask() & (1 << Position.A1_CASTLE)) != 0) &&
+ ((OOO_SQ & (pos.whiteBB | pos.blackBB)) == 0) &&
+ (pos.getPiece(k0 - 4) == Piece.WROOK) &&
+ !sqAttacked(pos, k0) &&
+ !sqAttacked(pos, k0 - 1)) {
+ setMove(moveList, k0, k0 - 2, Piece.EMPTY);
+ }
+ }
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.WKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.WPAWN];
+ long m = (pawns << 8) & ~occupied;
+ if (addPawnMovesByMask(moveList, pos, m, -8, true)) return moveList;
+ m = ((m & BitBoard.maskRow3) << 8) & ~occupied;
+ addPawnDoubleMovesByMask(moveList, pos, m, -16);
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns << 7) & BitBoard.maskAToGFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -7, true)) return moveList;
+
+ m = (pawns << 9) & BitBoard.maskBToHFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -9, true)) return moveList;
+ } else {
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.BQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.BROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.BBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // King moves
+ {
+ int sq = pos.getKingSq(false);
+ long m = BitBoard.kingAttacks[sq] & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ final int k0 = 60;
+ if (sq == k0) {
+ final long OO_SQ = 0x6000000000000000L;
+ final long OOO_SQ = 0xE00000000000000L;
+ if (((pos.getCastleMask() & (1 << Position.H8_CASTLE)) != 0) &&
+ ((OO_SQ & (pos.whiteBB | pos.blackBB)) == 0) &&
+ (pos.getPiece(k0 + 3) == Piece.BROOK) &&
+ !sqAttacked(pos, k0) &&
+ !sqAttacked(pos, k0 + 1)) {
+ setMove(moveList, k0, k0 + 2, Piece.EMPTY);
+ }
+ if (((pos.getCastleMask() & (1 << Position.A8_CASTLE)) != 0) &&
+ ((OOO_SQ & (pos.whiteBB | pos.blackBB)) == 0) &&
+ (pos.getPiece(k0 - 4) == Piece.BROOK) &&
+ !sqAttacked(pos, k0) &&
+ !sqAttacked(pos, k0 - 1)) {
+ setMove(moveList, k0, k0 - 2, Piece.EMPTY);
+ }
+ }
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.BKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.BPAWN];
+ long m = (pawns >>> 8) & ~occupied;
+ if (addPawnMovesByMask(moveList, pos, m, 8, true)) return moveList;
+ m = ((m & BitBoard.maskRow6) >>> 8) & ~occupied;
+ addPawnDoubleMovesByMask(moveList, pos, m, 16);
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns >>> 9) & BitBoard.maskAToGFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 9, true)) return moveList;
+
+ m = (pawns >>> 7) & BitBoard.maskBToHFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 7, true)) return moveList;
+ }
+ return moveList;
+ }
+
+ /**
+ * Generate and return a list of pseudo-legal check evasion moves.
+ * Pseudo-legal means that the moves doesn't necessarily defend from check threats.
+ */
+ public final MoveList checkEvasions(Position pos) {
+ MoveList moveList = getMoveListObj();
+ final long occupied = pos.whiteBB | pos.blackBB;
+ if (pos.whiteMove) {
+ long kingThreats = pos.pieceTypeBB[Piece.BKNIGHT] & BitBoard.knightAttacks[pos.wKingSq];
+ long rookPieces = pos.pieceTypeBB[Piece.BROOK] | pos.pieceTypeBB[Piece.BQUEEN];
+ if (rookPieces != 0)
+ kingThreats |= rookPieces & BitBoard.rookAttacks(pos.wKingSq, occupied);
+ long bishPieces = pos.pieceTypeBB[Piece.BBISHOP] | pos.pieceTypeBB[Piece.BQUEEN];
+ if (bishPieces != 0)
+ kingThreats |= bishPieces & BitBoard.bishopAttacks(pos.wKingSq, occupied);
+ kingThreats |= pos.pieceTypeBB[Piece.BPAWN] & BitBoard.wPawnAttacks[pos.wKingSq];
+ long validTargets = 0;
+ if ((kingThreats != 0) && ((kingThreats & (kingThreats-1)) == 0)) { // Exactly one attacking piece
+ int threatSq = BitBoard.numberOfTrailingZeros(kingThreats);
+ validTargets = kingThreats | BitBoard.squaresBetween[pos.wKingSq][threatSq];
+ }
+ validTargets |= pos.pieceTypeBB[Piece.BKING];
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.WQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) &
+ ~pos.whiteBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.WROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & ~pos.whiteBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.WBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.whiteBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // King moves
+ {
+ int sq = pos.getKingSq(true);
+ long m = BitBoard.kingAttacks[sq] & ~pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.WKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & ~pos.whiteBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.WPAWN];
+ long m = (pawns << 8) & ~occupied;
+ if (addPawnMovesByMask(moveList, pos, m & validTargets, -8, true)) return moveList;
+ m = ((m & BitBoard.maskRow3) << 8) & ~occupied;
+ addPawnDoubleMovesByMask(moveList, pos, m & validTargets, -16);
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns << 7) & BitBoard.maskAToGFiles & ((pos.blackBB & validTargets) | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -7, true)) return moveList;
+
+ m = (pawns << 9) & BitBoard.maskBToHFiles & ((pos.blackBB & validTargets) | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -9, true)) return moveList;
+ } else {
+ long kingThreats = pos.pieceTypeBB[Piece.WKNIGHT] & BitBoard.knightAttacks[pos.bKingSq];
+ long rookPieces = pos.pieceTypeBB[Piece.WROOK] | pos.pieceTypeBB[Piece.WQUEEN];
+ if (rookPieces != 0)
+ kingThreats |= rookPieces & BitBoard.rookAttacks(pos.bKingSq, occupied);
+ long bishPieces = pos.pieceTypeBB[Piece.WBISHOP] | pos.pieceTypeBB[Piece.WQUEEN];
+ if (bishPieces != 0)
+ kingThreats |= bishPieces & BitBoard.bishopAttacks(pos.bKingSq, occupied);
+ kingThreats |= pos.pieceTypeBB[Piece.WPAWN] & BitBoard.bPawnAttacks[pos.bKingSq];
+ long validTargets = 0;
+ if ((kingThreats != 0) && ((kingThreats & (kingThreats-1)) == 0)) { // Exactly one attacking piece
+ int threatSq = BitBoard.numberOfTrailingZeros(kingThreats);
+ validTargets = kingThreats | BitBoard.squaresBetween[pos.bKingSq][threatSq];
+ }
+ validTargets |= pos.pieceTypeBB[Piece.WKING];
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.BQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) &
+ ~pos.blackBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.BROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & ~pos.blackBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.BBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.blackBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // King moves
+ {
+ int sq = pos.getKingSq(false);
+ long m = BitBoard.kingAttacks[sq] & ~pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.BKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & ~pos.blackBB & validTargets;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.BPAWN];
+ long m = (pawns >>> 8) & ~occupied;
+ if (addPawnMovesByMask(moveList, pos, m & validTargets, 8, true)) return moveList;
+ m = ((m & BitBoard.maskRow6) >>> 8) & ~occupied;
+ addPawnDoubleMovesByMask(moveList, pos, m & validTargets, 16);
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns >>> 9) & BitBoard.maskAToGFiles & ((pos.whiteBB & validTargets) | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 9, true)) return moveList;
+
+ m = (pawns >>> 7) & BitBoard.maskBToHFiles & ((pos.whiteBB & validTargets) | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 7, true)) return moveList;
+ }
+
+ /* Extra debug checks
+ {
+ ArrayList allMoves = pseudoLegalMoves(pos);
+ allMoves = MoveGen.removeIllegal(pos, allMoves);
+ HashSet evMoves = new HashSet();
+ for (Move m : moveList)
+ evMoves.add(TextIO.moveToUCIString(m));
+ for (Move m : allMoves)
+ if (!evMoves.contains(TextIO.moveToUCIString(m)))
+ throw new RuntimeException();
+ }
+ */
+
+ return moveList;
+ }
+
+ /** Generate captures, checks, and possibly some other moves that are too hard to filter out. */
+ public final MoveList pseudoLegalCapturesAndChecks(Position pos) {
+ MoveList moveList = getMoveListObj();
+ long occupied = pos.whiteBB | pos.blackBB;
+ if (pos.whiteMove) {
+ int bKingSq = pos.getKingSq(false);
+ long discovered = 0; // Squares that could generate discovered checks
+ long kRookAtk = BitBoard.rookAttacks(bKingSq, occupied);
+ if ((BitBoard.rookAttacks(bKingSq, occupied & ~kRookAtk) &
+ (pos.pieceTypeBB[Piece.WQUEEN] | pos.pieceTypeBB[Piece.WROOK])) != 0)
+ discovered |= kRookAtk;
+ long kBishAtk = BitBoard.bishopAttacks(bKingSq, occupied);
+ if ((BitBoard.bishopAttacks(bKingSq, occupied & ~kBishAtk) &
+ (pos.pieceTypeBB[Piece.WQUEEN] | pos.pieceTypeBB[Piece.WBISHOP])) != 0)
+ discovered |= kBishAtk;
+
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.WQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied));
+ if ((discovered & (1L<= 0) ? (1L << epSquare) : 0L;
+ long m = (pawns << 7) & BitBoard.maskAToGFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -7, false)) return moveList;
+ m = (pawns << 9) & BitBoard.maskBToHFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -9, false)) return moveList;
+
+ // Discovered checks and promotions
+ long pawnAll = discovered | BitBoard.maskRow7;
+ m = ((pawns & pawnAll) << 8) & ~(pos.whiteBB | pos.blackBB);
+ if (addPawnMovesByMask(moveList, pos, m, -8, false)) return moveList;
+ m = ((m & BitBoard.maskRow3) << 8) & ~(pos.whiteBB | pos.blackBB);
+ addPawnDoubleMovesByMask(moveList, pos, m, -16);
+
+ // Normal checks
+ m = ((pawns & ~pawnAll) << 8) & ~(pos.whiteBB | pos.blackBB);
+ if (addPawnMovesByMask(moveList, pos, m & BitBoard.bPawnAttacks[bKingSq], -8, false)) return moveList;
+ m = ((m & BitBoard.maskRow3) << 8) & ~(pos.whiteBB | pos.blackBB);
+ addPawnDoubleMovesByMask(moveList, pos, m & BitBoard.bPawnAttacks[bKingSq], -16);
+ } else {
+ int wKingSq = pos.getKingSq(true);
+ long discovered = 0; // Squares that could generate discovered checks
+ long kRookAtk = BitBoard.rookAttacks(wKingSq, occupied);
+ if ((BitBoard.rookAttacks(wKingSq, occupied & ~kRookAtk) &
+ (pos.pieceTypeBB[Piece.BQUEEN] | pos.pieceTypeBB[Piece.BROOK])) != 0)
+ discovered |= kRookAtk;
+ long kBishAtk = BitBoard.bishopAttacks(wKingSq, occupied);
+ if ((BitBoard.bishopAttacks(wKingSq, occupied & ~kBishAtk) &
+ (pos.pieceTypeBB[Piece.BQUEEN] | pos.pieceTypeBB[Piece.BBISHOP])) != 0)
+ discovered |= kBishAtk;
+
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.BQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied));
+ if ((discovered & (1L<= 0) ? (1L << epSquare) : 0L;
+ long m = (pawns >>> 9) & BitBoard.maskAToGFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 9, false)) return moveList;
+ m = (pawns >>> 7) & BitBoard.maskBToHFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 7, false)) return moveList;
+
+ // Discovered checks and promotions
+ long pawnAll = discovered | BitBoard.maskRow2;
+ m = ((pawns & pawnAll) >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ if (addPawnMovesByMask(moveList, pos, m, 8, false)) return moveList;
+ m = ((m & BitBoard.maskRow6) >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ addPawnDoubleMovesByMask(moveList, pos, m, 16);
+
+ // Normal checks
+ m = ((pawns & ~pawnAll) >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ if (addPawnMovesByMask(moveList, pos, m & BitBoard.wPawnAttacks[wKingSq], 8, false)) return moveList;
+ m = ((m & BitBoard.maskRow6) >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ addPawnDoubleMovesByMask(moveList, pos, m & BitBoard.wPawnAttacks[wKingSq], 16);
+ }
+
+ return moveList;
+ }
+
+ public final MoveList pseudoLegalCaptures(Position pos) {
+ MoveList moveList = getMoveListObj();
+ long occupied = pos.whiteBB | pos.blackBB;
+ if (pos.whiteMove) {
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.WQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.WROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.WBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.WKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // King moves
+ int sq = pos.getKingSq(true);
+ long m = BitBoard.kingAttacks[sq] & pos.blackBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.WPAWN];
+ m = (pawns << 8) & ~(pos.whiteBB | pos.blackBB);
+ m &= BitBoard.maskRow8;
+ if (addPawnMovesByMask(moveList, pos, m, -8, false)) return moveList;
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns << 7) & BitBoard.maskAToGFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -7, false)) return moveList;
+ m = (pawns << 9) & BitBoard.maskBToHFiles & (pos.blackBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, -9, false)) return moveList;
+ } else {
+ // Queen moves
+ long squares = pos.pieceTypeBB[Piece.BQUEEN];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Rook moves
+ squares = pos.pieceTypeBB[Piece.BROOK];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.rookAttacks(sq, occupied) & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Bishop moves
+ squares = pos.pieceTypeBB[Piece.BBISHOP];
+ while (squares != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(squares);
+ long m = BitBoard.bishopAttacks(sq, occupied) & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ squares &= squares-1;
+ }
+
+ // Knight moves
+ long knights = pos.pieceTypeBB[Piece.BKNIGHT];
+ while (knights != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(knights);
+ long m = BitBoard.knightAttacks[sq] & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+ knights &= knights-1;
+ }
+
+ // King moves
+ int sq = pos.getKingSq(false);
+ long m = BitBoard.kingAttacks[sq] & pos.whiteBB;
+ if (addMovesByMask(moveList, pos, sq, m)) return moveList;
+
+ // Pawn moves
+ long pawns = pos.pieceTypeBB[Piece.BPAWN];
+ m = (pawns >>> 8) & ~(pos.whiteBB | pos.blackBB);
+ m &= BitBoard.maskRow1;
+ if (addPawnMovesByMask(moveList, pos, m, 8, false)) return moveList;
+
+ int epSquare = pos.getEpSquare();
+ long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L;
+ m = (pawns >>> 9) & BitBoard.maskAToGFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 9, false)) return moveList;
+ m = (pawns >>> 7) & BitBoard.maskBToHFiles & (pos.whiteBB | epMask);
+ if (addPawnMovesByMask(moveList, pos, m, 7, false)) return moveList;
+ }
+ return moveList;
+ }
+
+ /**
+ * Return true if the side to move is in check.
+ */
+ public static final boolean inCheck(Position pos) {
+ int kingSq = pos.getKingSq(pos.whiteMove);
+ return sqAttacked(pos, kingSq);
+ }
+
+ /**
+ * Return the next piece in a given direction, starting from sq.
+ */
+ private static final int nextPiece(Position pos, int sq, int delta) {
+ while (true) {
+ sq += delta;
+ int p = pos.getPiece(sq);
+ if (p != Piece.EMPTY)
+ return p;
+ }
+ }
+
+ /** Like nextPiece(), but handles board edges. */
+ private static final int nextPieceSafe(Position pos, int sq, int delta) {
+ int dx = 0, dy = 0;
+ switch (delta) {
+ case 1: dx=1; dy=0; break;
+ case 9: dx=1; dy=1; break;
+ case 8: dx=0; dy=1; break;
+ case 7: dx=-1; dy=1; break;
+ case -1: dx=-1; dy=0; break;
+ case -9: dx=-1; dy=-1; break;
+ case -8: dx=0; dy=-1; break;
+ case -7: dx=1; dy=-1; break;
+ }
+ int x = Position.getX(sq);
+ int y = Position.getY(sq);
+ while (true) {
+ x += dx;
+ y += dy;
+ if ((x < 0) || (x > 7) || (y < 0) || (y > 7)) {
+ return Piece.EMPTY;
+ }
+ int p = pos.getPiece(Position.getSquare(x, y));
+ if (p != Piece.EMPTY)
+ return p;
+ }
+ }
+
+ /**
+ * Return true if making a move delivers check to the opponent
+ */
+ public static final boolean givesCheck(Position pos, Move m) {
+ boolean wtm = pos.whiteMove;
+ int oKingSq = pos.getKingSq(!wtm);
+ int oKing = wtm ? Piece.BKING : Piece.WKING;
+ int p = Piece.makeWhite(m.promoteTo == Piece.EMPTY ? pos.getPiece(m.from) : m.promoteTo);
+ int d1 = BitBoard.getDirection(m.to, oKingSq);
+ switch (d1) {
+ case 8: case -8: case 1: case -1: // Rook direction
+ if ((p == Piece.WQUEEN) || (p == Piece.WROOK))
+ if ((d1 != 0) && (MoveGen.nextPiece(pos, m.to, d1) == oKing))
+ return true;
+ break;
+ case 9: case 7: case -9: case -7: // Bishop direction
+ if ((p == Piece.WQUEEN) || (p == Piece.WBISHOP)) {
+ if ((d1 != 0) && (MoveGen.nextPiece(pos, m.to, d1) == oKing))
+ return true;
+ } else if (p == Piece.WPAWN) {
+ if (((d1 > 0) == wtm) && (pos.getPiece(m.to + d1) == oKing))
+ return true;
+ }
+ break;
+ default:
+ if (d1 != 0) { // Knight direction
+ if (p == Piece.WKNIGHT)
+ return true;
+ }
+ }
+ int d2 = BitBoard.getDirection(m.from, oKingSq);
+ if ((d2 != 0) && (d2 != d1) && (MoveGen.nextPiece(pos, m.from, d2) == oKing)) {
+ int p2 = MoveGen.nextPieceSafe(pos, m.from, -d2);
+ switch (d2) {
+ case 8: case -8: case 1: case -1: // Rook direction
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WROOK : Piece.BROOK)))
+ return true;
+ break;
+ case 9: case 7: case -9: case -7: // Bishop direction
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WBISHOP : Piece.BBISHOP)))
+ return true;
+ break;
+ }
+ }
+ if ((m.promoteTo != Piece.EMPTY) && (d1 != 0) && (d1 == d2)) {
+ switch (d1) {
+ case 8: case -8: case 1: case -1: // Rook direction
+ if ((p == Piece.WQUEEN) || (p == Piece.WROOK))
+ if ((d1 != 0) && (MoveGen.nextPiece(pos, m.from, d1) == oKing))
+ return true;
+ break;
+ case 9: case 7: case -9: case -7: // Bishop direction
+ if ((p == Piece.WQUEEN) || (p == Piece.WBISHOP)) {
+ if ((d1 != 0) && (MoveGen.nextPiece(pos, m.from, d1) == oKing))
+ return true;
+ }
+ break;
+ }
+ }
+ if (p == Piece.WKING) {
+ if (m.to - m.from == 2) { // O-O
+ if (MoveGen.nextPieceSafe(pos, m.from, -1) == oKing)
+ return true;
+ if (MoveGen.nextPieceSafe(pos, m.from + 1, wtm ? 8 : -8) == oKing)
+ return true;
+ } else if (m.to - m.from == -2) { // O-O-O
+ if (MoveGen.nextPieceSafe(pos, m.from, 1) == oKing)
+ return true;
+ if (MoveGen.nextPieceSafe(pos, m.from - 1, wtm ? 8 : -8) == oKing)
+ return true;
+ }
+ } else if (p == Piece.WPAWN) {
+ if (pos.getPiece(m.to) == Piece.EMPTY) {
+ int dx = Position.getX(m.to) - Position.getX(m.from);
+ if (dx != 0) { // en passant
+ int epSq = m.from + dx;
+ int d3 = BitBoard.getDirection(epSq, oKingSq);
+ switch (d3) {
+ case 9: case 7: case -9: case -7:
+ if (MoveGen.nextPiece(pos, epSq, d3) == oKing) {
+ int p2 = MoveGen.nextPieceSafe(pos, epSq, -d3);
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WBISHOP : Piece.BBISHOP)))
+ return true;
+ }
+ break;
+ case 1:
+ if (MoveGen.nextPiece(pos, Math.max(epSq, m.from), d3) == oKing) {
+ int p2 = MoveGen.nextPieceSafe(pos, Math.min(epSq, m.from), -d3);
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WROOK : Piece.BROOK)))
+ return true;
+ }
+ break;
+ case -1:
+ if (MoveGen.nextPiece(pos, Math.min(epSq, m.from), d3) == oKing) {
+ int p2 = MoveGen.nextPieceSafe(pos, Math.max(epSq, m.from), -d3);
+ if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) ||
+ (p2 == (wtm ? Piece.WROOK : Piece.BROOK)))
+ return true;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return true if the side to move can take the opponents king.
+ */
+ public static final boolean canTakeKing(Position pos) {
+ pos.setWhiteMove(!pos.whiteMove);
+ boolean ret = inCheck(pos);
+ pos.setWhiteMove(!pos.whiteMove);
+ return ret;
+ }
+
+ /**
+ * Return true if a square is attacked by the opposite side.
+ */
+ public static final boolean sqAttacked(Position pos, int sq) {
+ if (pos.whiteMove) {
+ if ((BitBoard.knightAttacks[sq] & pos.pieceTypeBB[Piece.BKNIGHT]) != 0)
+ return true;
+ if ((BitBoard.kingAttacks[sq] & pos.pieceTypeBB[Piece.BKING]) != 0)
+ return true;
+ if ((BitBoard.wPawnAttacks[sq] & pos.pieceTypeBB[Piece.BPAWN]) != 0)
+ return true;
+ long occupied = pos.whiteBB | pos.blackBB;
+ long bbQueen = pos.pieceTypeBB[Piece.BQUEEN];
+ if ((BitBoard.bishopAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.BBISHOP] | bbQueen)) != 0)
+ return true;
+ if ((BitBoard.rookAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.BROOK] | bbQueen)) != 0)
+ return true;
+ } else {
+ if ((BitBoard.knightAttacks[sq] & pos.pieceTypeBB[Piece.WKNIGHT]) != 0)
+ return true;
+ if ((BitBoard.kingAttacks[sq] & pos.pieceTypeBB[Piece.WKING]) != 0)
+ return true;
+ if ((BitBoard.bPawnAttacks[sq] & pos.pieceTypeBB[Piece.WPAWN]) != 0)
+ return true;
+ long occupied = pos.whiteBB | pos.blackBB;
+ long bbQueen = pos.pieceTypeBB[Piece.WQUEEN];
+ if ((BitBoard.bishopAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.WBISHOP] | bbQueen)) != 0)
+ return true;
+ if ((BitBoard.rookAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.WROOK] | bbQueen)) != 0)
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove all illegal moves from moveList.
+ * "moveList" is assumed to be a list of pseudo-legal moves.
+ * This function removes the moves that don't defend from check threats.
+ */
+ public static final void removeIllegal(Position pos, MoveList moveList) {
+ int length = 0;
+ UndoInfo ui = new UndoInfo();
+
+ boolean isInCheck = inCheck(pos);
+ final long occupied = pos.whiteBB | pos.blackBB;
+ int kSq = pos.getKingSq(pos.whiteMove);
+ long kingAtks = BitBoard.rookAttacks(kSq, occupied) | BitBoard.bishopAttacks(kSq, occupied);
+ int epSquare = pos.getEpSquare();
+ if (isInCheck) {
+ kingAtks |= pos.pieceTypeBB[pos.whiteMove ? Piece.BKNIGHT : Piece.WKNIGHT];
+ for (int mi = 0; mi < moveList.size; mi++) {
+ Move m = moveList.m[mi];
+ boolean legal;
+ if ((m.from != kSq) && ((kingAtks & (1L<= 56) { // White promotion
+ setMove(moveList, sq0, sq, Piece.WQUEEN);
+ setMove(moveList, sq0, sq, Piece.WKNIGHT);
+ if (allPromotions) {
+ setMove(moveList, sq0, sq, Piece.WROOK);
+ setMove(moveList, sq0, sq, Piece.WBISHOP);
+ }
+ } else { // Black promotion
+ setMove(moveList, sq0, sq, Piece.BQUEEN);
+ setMove(moveList, sq0, sq, Piece.BKNIGHT);
+ if (allPromotions) {
+ setMove(moveList, sq0, sq, Piece.BROOK);
+ setMove(moveList, sq0, sq, Piece.BBISHOP);
+ }
+ }
+ promMask &= (promMask - 1);
+ }
+ while (mask != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(mask);
+ setMove(moveList, sq + delta, sq, Piece.EMPTY);
+ mask &= (mask - 1);
+ }
+ return false;
+ }
+
+ private final static void addPawnDoubleMovesByMask(MoveList moveList, Position pos,
+ long mask, int delta) {
+ while (mask != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(mask);
+ setMove(moveList, sq + delta, sq, Piece.EMPTY);
+ mask &= (mask - 1);
+ }
+ }
+
+ private final static boolean addMovesByMask(MoveList moveList, Position pos, int sq0, long mask) {
+ long oKingMask = pos.pieceTypeBB[pos.whiteMove ? Piece.BKING : Piece.WKING];
+ if ((mask & oKingMask) != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(mask & oKingMask);
+ moveList.size = 0;
+ setMove(moveList, sq0, sq, Piece.EMPTY);
+ return true;
+ }
+ while (mask != 0) {
+ int sq = BitBoard.numberOfTrailingZeros(mask);
+ setMove(moveList, sq0, sq, Piece.EMPTY);
+ mask &= (mask - 1);
+ }
+ return false;
+ }
+
+ private final static void setMove(MoveList moveList, int from, int to, int promoteTo) {
+ Move m = moveList.m[moveList.size++];
+ m.from = from;
+ m.to = to;
+ m.promoteTo = promoteTo;
+ m.score = 0;
+ }
+
+ // Code to handle the Move cache.
+ private Object[] moveListCache = new Object[200];
+ private int moveListsInCache = 0;
+
+ private static final int MAX_MOVES = 256;
+
+ private final MoveList getMoveListObj() {
+ MoveList ml;
+ if (moveListsInCache > 0) {
+ ml = (MoveList)moveListCache[--moveListsInCache];
+ ml.size = 0;
+ } else {
+ ml = new MoveList();
+ for (int i = 0; i < MAX_MOVES; i++)
+ ml.m[i] = new Move(0, 0, Piece.EMPTY);
+ }
+ return ml;
+ }
+
+ /** Return all move objects in moveList to the move cache. */
+ public final void returnMoveList(MoveList moveList) {
+ if (moveListsInCache < moveListCache.length) {
+ moveListCache[moveListsInCache++] = moveList;
+ }
+ }
+}
diff --git a/CuckooChessEngine/src/chess/Parameters.java b/CuckooChessEngine/src/chess/Parameters.java
new file mode 100644
index 0000000..92df884
--- /dev/null
+++ b/CuckooChessEngine/src/chess/Parameters.java
@@ -0,0 +1,166 @@
+package chess;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class Parameters {
+ public static enum Type {
+ CHECK,
+ SPIN,
+ COMBO,
+ BUTTON,
+ STRING
+ }
+
+ public static class ParamBase {
+ public String name;
+ public Type type;
+ public boolean visible;
+ }
+
+ public static final class CheckParam extends ParamBase {
+ public boolean value;
+ public boolean defaultValue;
+ CheckParam(String name, boolean visible, boolean def) {
+ this.name = name;
+ this.type = Type.CHECK;
+ this.visible = visible;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ }
+
+ public static final class SpinParam extends ParamBase {
+ public int minValue;
+ public int maxValue;
+ public int value;
+ public int defaultValue;
+ SpinParam(String name, boolean visible, int minV, int maxV, int def) {
+ this.name = name;
+ this.type = Type.SPIN;
+ this.visible = visible;
+ this.minValue = minV;
+ this.maxValue = maxV;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ }
+
+ public static final class ComboParam extends ParamBase {
+ public String[] allowedValues;
+ public String value;
+ public String defaultValue;
+ ComboParam(String name, boolean visible, String[] allowed, String def) {
+ this.name = name;
+ this.type = Type.COMBO;
+ this.visible = visible;
+ this.allowedValues = allowed;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ }
+
+ public static final class ButtonParam extends ParamBase {
+ ButtonParam(String name, boolean visible) {
+ this.name = name;
+ this.type = Type.BUTTON;
+ this.visible = visible;
+ }
+ }
+
+ public static final class StringParam extends ParamBase {
+ public String value;
+ public String defaultValue;
+ StringParam(String name, boolean visible, String def) {
+ this.name = name;
+ this.type = Type.STRING;
+ this.visible = visible;
+ this.value = def;
+ this.defaultValue = def;
+ }
+ }
+
+ public static Parameters instance() {
+ return inst;
+ }
+ public final String[] getParamNames() {
+ ArrayList parNames = new ArrayList();
+ for (Map.Entry e : params.entrySet())
+ if (e.getValue().visible)
+ parNames.add(e.getKey());
+ return parNames.toArray(new String[parNames.size()]);
+ }
+
+ public final ParamBase getParam(String name) {
+ return params.get(name);
+ }
+
+ private static final Parameters inst = new Parameters();
+ private Map params = new TreeMap();
+
+ private Parameters() {
+ addPar(new SpinParam("qV", false, -200, 200, 0));
+ addPar(new SpinParam("rV", false, -200, 200, 0));
+ addPar(new SpinParam("bV", false, -200, 200, 0));
+ addPar(new SpinParam("nV", false, -200, 200, 0));
+ addPar(new SpinParam("pV", false, -200, 200, 0));
+ }
+
+ private final void addPar(ParamBase p) {
+ params.put(p.name.toLowerCase(), p);
+ }
+
+ final boolean getBooleanPar(String name) {
+ return ((CheckParam)params.get(name.toLowerCase())).value;
+ }
+ final int getIntPar(String name) {
+ int ret = ((SpinParam)params.get(name.toLowerCase())).value;
+ return ret;
+ }
+ final String getStringPar(String name) {
+ return ((StringParam)params.get(name.toLowerCase())).value;
+ }
+
+ public final void set(String name, String value) {
+ ParamBase p = params.get(name.toLowerCase());
+ if (p == null)
+ return;
+ switch (p.type) {
+ case CHECK: {
+ CheckParam cp = (CheckParam)p;
+ if (value.toLowerCase().equals("true"))
+ cp.value = true;
+ else if (value.toLowerCase().equals("false"))
+ cp.value = false;
+ break;
+ }
+ case SPIN: {
+ SpinParam sp = (SpinParam)p;
+ try {
+ int val = Integer.parseInt(value);
+ if ((val >= sp.minValue) && (val <= sp.maxValue))
+ sp.value = val;
+ } catch (NumberFormatException ex) {
+ }
+ break;
+ }
+ case COMBO: {
+ ComboParam cp = (ComboParam)p;
+ for (String allowed : cp.allowedValues)
+ if (allowed.toLowerCase().equals(value.toLowerCase())) {
+ cp.value = allowed;
+ break;
+ }
+ break;
+ }
+ case BUTTON:
+ break;
+ case STRING: {
+ StringParam sp = (StringParam)p;
+ sp.value = value;
+ break;
+ }
+ }
+ }
+}
diff --git a/CuckooChessEngine/src/chess/Piece.java b/CuckooChessEngine/src/chess/Piece.java
new file mode 100644
index 0000000..a974356
--- /dev/null
+++ b/CuckooChessEngine/src/chess/Piece.java
@@ -0,0 +1,57 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+/**
+ * Constants for different piece types.
+ * @author petero
+ */
+public class Piece {
+ public static final int EMPTY = 0;
+
+ public static final int WKING = 1;
+ public static final int WQUEEN = 2;
+ public static final int WROOK = 3;
+ public static final int WBISHOP = 4;
+ public static final int WKNIGHT = 5;
+ public static final int WPAWN = 6;
+
+ public static final int BKING = 7;
+ public static final int BQUEEN = 8;
+ public static final int BROOK = 9;
+ public static final int BBISHOP = 10;
+ public static final int BKNIGHT = 11;
+ public static final int BPAWN = 12;
+
+ public static final int nPieceTypes = 13;
+
+ /**
+ * Return true if p is a white piece, false otherwise.
+ * Note that if p is EMPTY, an unspecified value is returned.
+ */
+ public static final boolean isWhite(int pType) {
+ return pType < BKING;
+ }
+ public static final int makeWhite(int pType) {
+ return pType < BKING ? pType : pType - (BKING - WKING);
+ }
+ public static final int makeBlack(int pType) {
+ return ((pType > EMPTY) && (pType < BKING)) ? pType + (BKING - WKING) : pType;
+ }
+}
diff --git a/CuckooChessEngine/src/chess/Player.java b/CuckooChessEngine/src/chess/Player.java
new file mode 100644
index 0000000..4b24aee
--- /dev/null
+++ b/CuckooChessEngine/src/chess/Player.java
@@ -0,0 +1,59 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.util.List;
+
+/**
+ * Interface for human/computer players.
+ * @author petero
+ */
+public interface Player {
+ /**
+ * Get a command from a player.
+ * The command can be a valid move string, in which case the move is played
+ * and the turn goes over to the other player. The command can also be a special
+ * command, such as "quit", "new", "resign", etc.
+ * @param history List of earlier positions (not including the current position).
+ * This makes it possible for the player to correctly handle
+ * the draw by repetition rule.
+ */
+ public String getCommand(Position pos, boolean drawOffer, List history);
+
+ /** Return true if this player is a human player. */
+ public boolean isHumanPlayer();
+
+ /**
+ * Inform player whether or not to use an opening book.
+ * Of course, a human player is likely to ignore this.
+ */
+ public void useBook(boolean bookOn);
+
+ /**
+ * Inform player about min recommended/max allowed thinking time per move.
+ * Of course, a human player is likely to ignore this.
+ */
+ public void timeLimit(int minTimeLimit, int maxTimeLimit, boolean randomMode);
+
+ /**
+ * Inform player that the transposition table should be cleared.
+ * Of coarse, a human player has a hard time implementing this.
+ */
+ public void clearTT();
+}
diff --git a/CuckooChessEngine/src/chess/Position.java b/CuckooChessEngine/src/chess/Position.java
new file mode 100644
index 0000000..65c7b40
--- /dev/null
+++ b/CuckooChessEngine/src/chess/Position.java
@@ -0,0 +1,674 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Stores the state of a chess position.
+ * All required state is stored, except for all previous positions
+ * since the last capture or pawn move. That state is only needed
+ * for three-fold repetition draw detection, and is better stored
+ * in a separate hash table.
+ * @author petero
+ */
+public class Position {
+ public int[] squares;
+
+ // Bitboards
+ public long[] pieceTypeBB;
+ public long whiteBB, blackBB;
+
+ // Piece square table scores
+ public short[] psScore1, psScore2;
+
+ public boolean whiteMove;
+
+ /** Bit definitions for the castleMask bit mask. */
+ public static final int A1_CASTLE = 0; /** White long castle. */
+ public static final int H1_CASTLE = 1; /** White short castle. */
+ public static final int A8_CASTLE = 2; /** Black long castle. */
+ public static final int H8_CASTLE = 3; /** Black short castle. */
+
+ private int castleMask;
+
+ private int epSquare;
+
+ /** Number of half-moves since last 50-move reset. */
+ int halfMoveClock;
+
+ /** Game move number, starting from 1. */
+ public int fullMoveCounter;
+
+ private long hashKey; // Cached Zobrist hash key
+ private long pHashKey;
+ public int wKingSq, bKingSq; // Cached king positions
+ public int wMtrl; // Total value of all white pieces and pawns
+ public int bMtrl; // Total value of all black pieces and pawns
+ public int wMtrlPawns; // Total value of all white pawns
+ public int bMtrlPawns; // Total value of all black pawns
+
+ /** Initialize board to empty position. */
+ public Position() {
+ squares = new int[64];
+ for (int i = 0; i < 64; i++)
+ squares[i] = Piece.EMPTY;
+ pieceTypeBB = new long[Piece.nPieceTypes];
+ psScore1 = new short[Piece.nPieceTypes];
+ psScore2 = new short[Piece.nPieceTypes];
+ for (int i = 0; i < Piece.nPieceTypes; i++) {
+ pieceTypeBB[i] = 0L;
+ psScore1[i] = 0;
+ psScore2[i] = 0;
+ }
+ whiteBB = blackBB = 0L;
+ whiteMove = true;
+ castleMask = 0;
+ epSquare = -1;
+ halfMoveClock = 0;
+ fullMoveCounter = 1;
+ hashKey = computeZobristHash();
+ wKingSq = bKingSq = -1;
+ wMtrl = bMtrl = -Evaluate.kV;
+ wMtrlPawns = bMtrlPawns = 0;
+ }
+
+ public Position(Position other) {
+ squares = new int[64];
+ for (int i = 0; i < 64; i++)
+ squares[i] = other.squares[i];
+ pieceTypeBB = new long[Piece.nPieceTypes];
+ psScore1 = new short[Piece.nPieceTypes];
+ psScore2 = new short[Piece.nPieceTypes];
+ for (int i = 0; i < Piece.nPieceTypes; i++) {
+ pieceTypeBB[i] = other.pieceTypeBB[i];
+ psScore1[i] = other.psScore1[i];
+ psScore2[i] = other.psScore2[i];
+ }
+ whiteBB = other.whiteBB;
+ blackBB = other.blackBB;
+ whiteMove = other.whiteMove;
+ castleMask = other.castleMask;
+ epSquare = other.epSquare;
+ halfMoveClock = other.halfMoveClock;
+ fullMoveCounter = other.fullMoveCounter;
+ hashKey = other.hashKey;
+ pHashKey = other.pHashKey;
+ wKingSq = other.wKingSq;
+ bKingSq = other.bKingSq;
+ wMtrl = other.wMtrl;
+ bMtrl = other.bMtrl;
+ wMtrlPawns = other.wMtrlPawns;
+ bMtrlPawns = other.bMtrlPawns;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ((o == null) || (o.getClass() != this.getClass()))
+ return false;
+ Position other = (Position)o;
+ if (!drawRuleEquals(other))
+ return false;
+ if (halfMoveClock != other.halfMoveClock)
+ return false;
+ if (fullMoveCounter != other.fullMoveCounter)
+ return false;
+ if (hashKey != other.hashKey)
+ return false;
+ if (pHashKey != other.pHashKey)
+ return false;
+ return true;
+ }
+ @Override
+ public int hashCode() {
+ return (int)hashKey;
+ }
+
+ /**
+ * Return Zobrist hash value for the current position.
+ * Everything except the move counters are included in the hash value.
+ */
+ public final long zobristHash() {
+ return hashKey;
+ }
+ public final long pawnZobristHash() {
+ return pHashKey;
+ }
+ public final long kingZobristHash() {
+ return psHashKeys[Piece.WKING][wKingSq] ^
+ psHashKeys[Piece.BKING][bKingSq];
+ }
+
+ public final long historyHash() {
+ long ret = hashKey;
+ if (halfMoveClock >= 80) {
+ ret ^= moveCntKeys[Math.min(halfMoveClock, 100)];
+ }
+ return ret;
+ }
+
+ /**
+ * Decide if two positions are equal in the sense of the draw by repetition rule.
+ * @return True if positions are equal, false otherwise.
+ */
+ final public boolean drawRuleEquals(Position other) {
+ for (int i = 0; i < 64; i++) {
+ if (squares[i] != other.squares[i])
+ return false;
+ }
+ if (whiteMove != other.whiteMove)
+ return false;
+ if (castleMask != other.castleMask)
+ return false;
+ if (epSquare != other.epSquare)
+ return false;
+ return true;
+ }
+
+ public final void setWhiteMove(boolean whiteMove) {
+ if (whiteMove != this.whiteMove) {
+ hashKey ^= whiteHashKey;
+ this.whiteMove = whiteMove;
+ }
+ }
+ /** Return index in squares[] vector corresponding to (x,y). */
+ public final static int getSquare(int x, int y) {
+ return y * 8 + x;
+ }
+ /** Return x position (file) corresponding to a square. */
+ public final static int getX(int square) {
+ return square & 7;
+ }
+ /** Return y position (rank) corresponding to a square. */
+ public final static int getY(int square) {
+ return square >> 3;
+ }
+ /** Return true if (x,y) is a dark square. */
+ public final static boolean darkSquare(int x, int y) {
+ return (x & 1) == (y & 1);
+ }
+
+ /** Return piece occupying a square. */
+ public final int getPiece(int square) {
+ return squares[square];
+ }
+
+ /** Move a non-pawn piece to an empty square. */
+ private final void movePieceNotPawn(int from, int to) {
+ final int piece = squares[from];
+ hashKey ^= psHashKeys[piece][from];
+ hashKey ^= psHashKeys[piece][to];
+ hashKey ^= psHashKeys[Piece.EMPTY][from];
+ hashKey ^= psHashKeys[Piece.EMPTY][to];
+
+ squares[from] = Piece.EMPTY;
+ squares[to] = piece;
+
+ final long sqMaskF = 1L << from;
+ final long sqMaskT = 1L << to;
+ pieceTypeBB[piece] &= ~sqMaskF;
+ pieceTypeBB[piece] |= sqMaskT;
+ if (Piece.isWhite(piece)) {
+ whiteBB &= ~sqMaskF;
+ whiteBB |= sqMaskT;
+ if (piece == Piece.WKING)
+ wKingSq = to;
+ } else {
+ blackBB &= ~sqMaskF;
+ blackBB |= sqMaskT;
+ if (piece == Piece.BKING)
+ bKingSq = to;
+ }
+
+ psScore1[piece] += Evaluate.psTab1[piece][to] - Evaluate.psTab1[piece][from];
+ psScore2[piece] += Evaluate.psTab2[piece][to] - Evaluate.psTab2[piece][from];
+ }
+
+ /** Set a square to a piece value. */
+ public final void setPiece(int square, int piece) {
+ int removedPiece = squares[square];
+ squares[square] = piece;
+
+ // Update hash key
+ hashKey ^= psHashKeys[removedPiece][square];
+ hashKey ^= psHashKeys[piece][square];
+
+ // Update bitboards
+ final long sqMask = 1L << square;
+ pieceTypeBB[removedPiece] &= ~sqMask;
+ pieceTypeBB[piece] |= sqMask;
+
+ if (removedPiece != Piece.EMPTY) {
+ int pVal = Evaluate.pieceValue[removedPiece];
+ if (Piece.isWhite(removedPiece)) {
+ wMtrl -= pVal;
+ whiteBB &= ~sqMask;
+ if (removedPiece == Piece.WPAWN) {
+ wMtrlPawns -= pVal;
+ pHashKey ^= psHashKeys[Piece.WPAWN][square];
+ }
+ } else {
+ bMtrl -= pVal;
+ blackBB &= ~sqMask;
+ if (removedPiece == Piece.BPAWN) {
+ bMtrlPawns -= pVal;
+ pHashKey ^= psHashKeys[Piece.BPAWN][square];
+ }
+ }
+ }
+
+ if (piece != Piece.EMPTY) {
+ int pVal = Evaluate.pieceValue[piece];
+ if (Piece.isWhite(piece)) {
+ wMtrl += pVal;
+ whiteBB |= sqMask;
+ if (piece == Piece.WPAWN) {
+ wMtrlPawns += pVal;
+ pHashKey ^= psHashKeys[Piece.WPAWN][square];
+ }
+ if (piece == Piece.WKING)
+ wKingSq = square;
+ } else {
+ bMtrl += pVal;
+ blackBB |= sqMask;
+ if (piece == Piece.BPAWN) {
+ bMtrlPawns += pVal;
+ pHashKey ^= psHashKeys[Piece.BPAWN][square];
+ }
+ if (piece == Piece.BKING)
+ bKingSq = square;
+ }
+ }
+
+ // Update piece/square table scores
+ psScore1[removedPiece] -= Evaluate.psTab1[removedPiece][square];
+ psScore2[removedPiece] -= Evaluate.psTab2[removedPiece][square];
+ psScore1[piece] += Evaluate.psTab1[piece][square];
+ psScore2[piece] += Evaluate.psTab2[piece][square];
+ }
+
+ /**
+ * Set a square to a piece value.
+ * Special version that only updates enough of the state for the SEE function to be happy.
+ */
+ public final void setSEEPiece(int square, int piece) {
+ int removedPiece = squares[square];
+
+ // Update board
+ squares[square] = piece;
+
+ // Update bitboards
+ long sqMask = 1L << square;
+ pieceTypeBB[removedPiece] &= ~sqMask;
+ pieceTypeBB[piece] |= sqMask;
+ if (removedPiece != Piece.EMPTY) {
+ if (Piece.isWhite(removedPiece))
+ whiteBB &= ~sqMask;
+ else
+ blackBB &= ~sqMask;
+ }
+ if (piece != Piece.EMPTY) {
+ if (Piece.isWhite(piece))
+ whiteBB |= sqMask;
+ else
+ blackBB |= sqMask;
+ }
+ }
+
+ /** Return true if white long castling right has not been lost. */
+ public final boolean a1Castle() {
+ return (castleMask & (1 << A1_CASTLE)) != 0;
+ }
+ /** Return true if white short castling right has not been lost. */
+ public final boolean h1Castle() {
+ return (castleMask & (1 << H1_CASTLE)) != 0;
+ }
+ /** Return true if black long castling right has not been lost. */
+ public final boolean a8Castle() {
+ return (castleMask & (1 << A8_CASTLE)) != 0;
+ }
+ /** Return true if black short castling right has not been lost. */
+ public final boolean h8Castle() {
+ return (castleMask & (1 << H8_CASTLE)) != 0;
+ }
+ /** Bitmask describing castling rights. */
+ public final int getCastleMask() {
+ return castleMask;
+ }
+ public final void setCastleMask(int castleMask) {
+ hashKey ^= castleHashKeys[this.castleMask];
+ hashKey ^= castleHashKeys[castleMask];
+ this.castleMask = castleMask;
+ }
+
+ /** En passant square, or -1 if no ep possible. */
+ public final int getEpSquare() {
+ return epSquare;
+ }
+ public final void setEpSquare(int epSquare) {
+ if (this.epSquare != epSquare) {
+ hashKey ^= epHashKeys[(this.epSquare >= 0) ? getX(this.epSquare) + 1 : 0];
+ hashKey ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0];
+ this.epSquare = epSquare;
+ }
+ }
+
+
+ public final int getKingSq(boolean whiteMove) {
+ return whiteMove ? wKingSq : bKingSq;
+ }
+
+ /** Apply a move to the current position. */
+ public final void makeMove(Move move, UndoInfo ui) {
+ ui.capturedPiece = squares[move.to];
+ ui.castleMask = castleMask;
+ ui.epSquare = epSquare;
+ ui.halfMoveClock = halfMoveClock;
+ boolean wtm = whiteMove;
+
+ final int p = squares[move.from];
+ int capP = squares[move.to];
+ long fromMask = 1L << move.from;
+
+ int prevEpSquare = epSquare;
+ setEpSquare(-1);
+
+ if ((capP != Piece.EMPTY) || (((pieceTypeBB[Piece.WPAWN] | pieceTypeBB[Piece.BPAWN]) & fromMask) != 0)) {
+ halfMoveClock = 0;
+
+ // Handle en passant and epSquare
+ if (p == Piece.WPAWN) {
+ if (move.to - move.from == 2 * 8) {
+ int x = Position.getX(move.to);
+ if ( ((x > 0) && (squares[move.to - 1] == Piece.BPAWN)) ||
+ ((x < 7) && (squares[move.to + 1] == Piece.BPAWN))) {
+ setEpSquare(move.from + 8);
+ }
+ } else if (move.to == prevEpSquare) {
+ setPiece(move.to - 8, Piece.EMPTY);
+ }
+ } else if (p == Piece.BPAWN) {
+ if (move.to - move.from == -2 * 8) {
+ int x = Position.getX(move.to);
+ if ( ((x > 0) && (squares[move.to - 1] == Piece.WPAWN)) ||
+ ((x < 7) && (squares[move.to + 1] == Piece.WPAWN))) {
+ setEpSquare(move.from - 8);
+ }
+ } else if (move.to == prevEpSquare) {
+ setPiece(move.to + 8, Piece.EMPTY);
+ }
+ }
+
+ if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) {
+ if (wtm) {
+ setCastleMask(castleMask & ~(1 << Position.A1_CASTLE));
+ setCastleMask(castleMask & ~(1 << Position.H1_CASTLE));
+ } else {
+ setCastleMask(castleMask & ~(1 << Position.A8_CASTLE));
+ setCastleMask(castleMask & ~(1 << Position.H8_CASTLE));
+ }
+ }
+
+ // Perform move
+ setPiece(move.from, Piece.EMPTY);
+ // Handle promotion
+ if (move.promoteTo != Piece.EMPTY) {
+ setPiece(move.to, move.promoteTo);
+ } else {
+ setPiece(move.to, p);
+ }
+ } else {
+ halfMoveClock++;
+
+ // Handle castling
+ if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) {
+ int k0 = move.from;
+ if (move.to == k0 + 2) { // O-O
+ movePieceNotPawn(k0 + 3, k0 + 1);
+ } else if (move.to == k0 - 2) { // O-O-O
+ movePieceNotPawn(k0 - 4, k0 - 1);
+ }
+ if (wtm) {
+ setCastleMask(castleMask & ~(1 << Position.A1_CASTLE));
+ setCastleMask(castleMask & ~(1 << Position.H1_CASTLE));
+ } else {
+ setCastleMask(castleMask & ~(1 << Position.A8_CASTLE));
+ setCastleMask(castleMask & ~(1 << Position.H8_CASTLE));
+ }
+ }
+
+ // Perform move
+ movePieceNotPawn(move.from, move.to);
+ }
+ if (wtm) {
+ // Update castling rights when rook moves
+ if ((BitBoard.maskCorners & fromMask) != 0) {
+ if (p == Piece.WROOK)
+ removeCastleRights(move.from);
+ }
+ if ((BitBoard.maskCorners & (1L << move.to)) != 0) {
+ if (capP == Piece.BROOK)
+ removeCastleRights(move.to);
+ }
+ } else {
+ fullMoveCounter++;
+ // Update castling rights when rook moves
+ if ((BitBoard.maskCorners & fromMask) != 0) {
+ if (p == Piece.BROOK)
+ removeCastleRights(move.from);
+ }
+ if ((BitBoard.maskCorners & (1L << move.to)) != 0) {
+ if (capP == Piece.WROOK)
+ removeCastleRights(move.to);
+ }
+ }
+
+ hashKey ^= whiteHashKey;
+ whiteMove = !wtm;
+ }
+
+ public final void unMakeMove(Move move, UndoInfo ui) {
+ hashKey ^= whiteHashKey;
+ whiteMove = !whiteMove;
+ int p = squares[move.to];
+ setPiece(move.from, p);
+ setPiece(move.to, ui.capturedPiece);
+ setCastleMask(ui.castleMask);
+ setEpSquare(ui.epSquare);
+ halfMoveClock = ui.halfMoveClock;
+ boolean wtm = whiteMove;
+ if (move.promoteTo != Piece.EMPTY) {
+ p = wtm ? Piece.WPAWN : Piece.BPAWN;
+ setPiece(move.from, p);
+ }
+ if (!wtm) {
+ fullMoveCounter--;
+ }
+
+ // Handle castling
+ int king = wtm ? Piece.WKING : Piece.BKING;
+ if (p == king) {
+ int k0 = move.from;
+ if (move.to == k0 + 2) { // O-O
+ movePieceNotPawn(k0 + 1, k0 + 3);
+ } else if (move.to == k0 - 2) { // O-O-O
+ movePieceNotPawn(k0 - 1, k0 - 4);
+ }
+ }
+
+ // Handle en passant
+ if (move.to == epSquare) {
+ if (p == Piece.WPAWN) {
+ setPiece(move.to - 8, Piece.BPAWN);
+ } else if (p == Piece.BPAWN) {
+ setPiece(move.to + 8, Piece.WPAWN);
+ }
+ }
+ }
+
+ /**
+ * Apply a move to the current position.
+ * Special version that only updates enough of the state for the SEE function to be happy.
+ */
+ public final void makeSEEMove(Move move, UndoInfo ui) {
+ ui.capturedPiece = squares[move.to];
+ boolean wtm = whiteMove;
+
+ int p = squares[move.from];
+ long fromMask = 1L << move.from;
+
+ // Handle castling
+ if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) {
+ int k0 = move.from;
+ if (move.to == k0 + 2) { // O-O
+ setSEEPiece(k0 + 1, squares[k0 + 3]);
+ setSEEPiece(k0 + 3, Piece.EMPTY);
+ } else if (move.to == k0 - 2) { // O-O-O
+ setSEEPiece(k0 - 1, squares[k0 - 4]);
+ setSEEPiece(k0 - 4, Piece.EMPTY);
+ }
+ }
+
+ // Handle en passant
+ if (move.to == epSquare) {
+ if (p == Piece.WPAWN) {
+ setSEEPiece(move.to - 8, Piece.EMPTY);
+ } else if (p == Piece.BPAWN) {
+ setSEEPiece(move.to + 8, Piece.EMPTY);
+ }
+ }
+
+ // Perform move
+ setSEEPiece(move.from, Piece.EMPTY);
+ setSEEPiece(move.to, p);
+ whiteMove = !wtm;
+ }
+
+ public final void unMakeSEEMove(Move move, UndoInfo ui) {
+ whiteMove = !whiteMove;
+ int p = squares[move.to];
+ setSEEPiece(move.from, p);
+ setSEEPiece(move.to, ui.capturedPiece);
+ boolean wtm = whiteMove;
+
+ // Handle castling
+ int king = wtm ? Piece.WKING : Piece.BKING;
+ if (p == king) {
+ int k0 = move.from;
+ if (move.to == k0 + 2) { // O-O
+ setSEEPiece(k0 + 3, squares[k0 + 1]);
+ setSEEPiece(k0 + 1, Piece.EMPTY);
+ } else if (move.to == k0 - 2) { // O-O-O
+ setSEEPiece(k0 - 4, squares[k0 - 1]);
+ setSEEPiece(k0 - 1, Piece.EMPTY);
+ }
+ }
+
+ // Handle en passant
+ if (move.to == epSquare) {
+ if (p == Piece.WPAWN) {
+ setSEEPiece(move.to - 8, Piece.BPAWN);
+ } else if (p == Piece.BPAWN) {
+ setSEEPiece(move.to + 8, Piece.WPAWN);
+ }
+ }
+ }
+
+ private final void removeCastleRights(int square) {
+ if (square == Position.getSquare(0, 0)) {
+ setCastleMask(castleMask & ~(1 << Position.A1_CASTLE));
+ } else if (square == Position.getSquare(7, 0)) {
+ setCastleMask(castleMask & ~(1 << Position.H1_CASTLE));
+ } else if (square == Position.getSquare(0, 7)) {
+ setCastleMask(castleMask & ~(1 << Position.A8_CASTLE));
+ } else if (square == Position.getSquare(7, 7)) {
+ setCastleMask(castleMask & ~(1 << Position.H8_CASTLE));
+ }
+ }
+
+ /* ------------- Hashing code ------------------ */
+
+ static final long[][] psHashKeys; // [piece][square]
+ private static final long whiteHashKey;
+ private static final long[] castleHashKeys; // [castleMask]
+ private static final long[] epHashKeys; // [epFile + 1] (epFile==-1 for no ep)
+ private static final long[] moveCntKeys; // [min(halfMoveClock, 100)]
+
+ static {
+ psHashKeys = new long[Piece.nPieceTypes][64];
+ castleHashKeys = new long[16];
+ epHashKeys = new long[9];
+ moveCntKeys = new long[101];
+ int rndNo = 0;
+ for (int p = 0; p < Piece.nPieceTypes; p++) {
+ for (int sq = 0; sq < 64; sq++) {
+ psHashKeys[p][sq] = getRandomHashVal(rndNo++);
+ }
+ }
+ whiteHashKey = getRandomHashVal(rndNo++);
+ for (int cm = 0; cm < castleHashKeys.length; cm++)
+ castleHashKeys[cm] = getRandomHashVal(rndNo++);
+ for (int f = 0; f < epHashKeys.length; f++)
+ epHashKeys[f] = getRandomHashVal(rndNo++);
+ for (int mc = 0; mc < moveCntKeys.length; mc++)
+ moveCntKeys[mc] = getRandomHashVal(rndNo++);
+ }
+
+ /**
+ * Compute the Zobrist hash value non-incrementally. Only useful for test programs.
+ */
+ final long computeZobristHash() {
+ long hash = 0;
+ for (int sq = 0; sq < 64; sq++) {
+ int p = squares[sq];
+ hash ^= psHashKeys[p][sq];
+ if ((p == Piece.WPAWN) || (p == Piece.BPAWN))
+ pHashKey ^= psHashKeys[p][sq];
+ }
+ if (whiteMove)
+ hash ^= whiteHashKey;
+ hash ^= castleHashKeys[castleMask];
+ hash ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0];
+ return hash;
+ }
+
+ private final static long getRandomHashVal(int rndNo) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] input = new byte[4];
+ for (int i = 0; i < 4; i++)
+ input[i] = (byte)((rndNo >> (i * 8)) & 0xff);
+ byte[] digest = md.digest(input);
+ long ret = 0;
+ for (int i = 0; i < 8; i++) {
+ ret ^= ((long)digest[i]) << (i * 8);
+ }
+ return ret;
+ } catch (NoSuchAlgorithmException ex) {
+ throw new UnsupportedOperationException("SHA-1 not available");
+ }
+ }
+
+ /** Useful for debugging. */
+ public final String toString() {
+ return TextIO.asciiBoard(this) + (whiteMove ? "white\n" : "black\n") +
+ Long.toHexString(zobristHash()) + "\n";
+ }
+}
diff --git a/CuckooChessEngine/src/chess/Search.java b/CuckooChessEngine/src/chess/Search.java
new file mode 100644
index 0000000..d7d6f80
--- /dev/null
+++ b/CuckooChessEngine/src/chess/Search.java
@@ -0,0 +1,1220 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import chess.TranspositionTable.TTEntry;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ *
+ * @author petero
+ */
+public class Search {
+ final static int plyScale = 8; // Fractional ply resolution
+
+ Position pos;
+ MoveGen moveGen;
+ Evaluate eval;
+ KillerTable kt;
+ History ht;
+ long[] posHashList; // List of hashes for previous positions up to the last "zeroing" move.
+ int posHashListSize; // Number of used entries in posHashList
+ int posHashFirstNew; // First entry in posHashList that has not been played OTB.
+ TranspositionTable tt;
+ TreeLogger log = null;
+
+ private static final class SearchTreeInfo {
+ UndoInfo undoInfo;
+ Move hashMove; // Temporary storage for local hashMove variable
+ boolean allowNullMove; // Don't allow two null-moves in a row
+ Move bestMove; // Copy of the best found move at this ply
+ Move currentMove; // Move currently being searched
+ int lmr; // LMR reduction amount
+ long nodeIdx;
+ SearchTreeInfo() {
+ undoInfo = new UndoInfo();
+ hashMove = new Move(0, 0, 0);
+ allowNullMove = true;
+ bestMove = new Move(0, 0, 0);
+ }
+ }
+ SearchTreeInfo[] searchTreeInfo;
+
+ // Time management
+ long tStart; // Time when search started
+ long minTimeMillis; // Minimum recommended thinking time
+ long maxTimeMillis; // Maximum allowed thinking time
+ boolean searchNeedMoreTime; // True if negaScout should use up to maxTimeMillis time.
+ private int maxNodes; // Maximum number of nodes to search (approximately)
+ int nodesToGo; // Number of nodes until next time check
+ public int nodesBetweenTimeCheck = 5000; // How often to check remaining time
+
+ // Reduced strength variables
+ private int strength = 1000; // Strength (0-1000)
+ boolean weak = false; // Set to strength < 1000
+ long randomSeed = 0;
+
+ // Search statistics stuff
+ int nodes;
+ int qNodes;
+ int[] nodesPlyVec;
+ int[] nodesDepthVec;
+ int totalNodes;
+ long tLastStats; // Time when notifyStats was last called
+ boolean verbose;
+
+ public final static int MATE0 = 32000;
+
+ public final static int UNKNOWN_SCORE = -32767; // Represents unknown static eval score
+ int q0Eval; // Static eval score at first level of quiescence search
+
+ public Search(Position pos, long[] posHashList, int posHashListSize, TranspositionTable tt) {
+ this.pos = new Position(pos);
+ this.moveGen = new MoveGen();
+ this.posHashList = posHashList;
+ this.posHashListSize = posHashListSize;
+ this.tt = tt;
+ eval = new Evaluate();
+ kt = new KillerTable();
+ ht = new History();
+ posHashFirstNew = posHashListSize;
+ initNodeStats();
+ minTimeMillis = -1;
+ maxTimeMillis = -1;
+ searchNeedMoreTime = false;
+ maxNodes = -1;
+ final int vecLen = 200;
+ searchTreeInfo = new SearchTreeInfo[vecLen];
+ for (int i = 0; i < vecLen; i++) {
+ searchTreeInfo[i] = new SearchTreeInfo();
+ }
+ }
+
+ static final class StopSearch extends Exception {
+ private static final long serialVersionUID = -5546906604987117015L;
+ public StopSearch() {
+ }
+ public StopSearch(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Used to get various search information during search
+ */
+ public interface Listener {
+ public void notifyDepth(int depth);
+ public void notifyCurrMove(Move m, int moveNr);
+ public void notifyPV(int depth, int score, int time, int nodes, int nps,
+ boolean isMate, boolean upperBound, boolean lowerBound, ArrayList pv);
+ public void notifyStats(int nodes, int nps, int time);
+ }
+
+ Listener listener;
+ public void setListener(Listener listener) {
+ this.listener = listener;
+ }
+
+ private final static class MoveInfo {
+ Move move;
+ int nodes;
+ MoveInfo(Move m, int n) { move = m; nodes = n; }
+ public static final class SortByScore implements Comparator {
+ public int compare(MoveInfo mi1, MoveInfo mi2) {
+ if ((mi1 == null) && (mi2 == null))
+ return 0;
+ if (mi1 == null)
+ return 1;
+ if (mi2 == null)
+ return -1;
+ return mi2.move.score - mi1.move.score;
+ }
+ }
+ public static final class SortByNodes implements Comparator {
+ public int compare(MoveInfo mi1, MoveInfo mi2) {
+ if ((mi1 == null) && (mi2 == null))
+ return 0;
+ if (mi1 == null)
+ return 1;
+ if (mi2 == null)
+ return -1;
+ return mi2.nodes - mi1.nodes;
+ }
+ }
+ }
+
+ final public void timeLimit(int minTimeLimit, int maxTimeLimit) {
+ minTimeMillis = minTimeLimit;
+ maxTimeMillis = maxTimeLimit;
+ }
+
+ final public void setStrength(int strength, long randomSeed) {
+ if (strength < 0) strength = 0;
+ if (strength > 1000) strength = 1000;
+ this.strength = strength;
+ weak = strength < 1000;
+ this.randomSeed = randomSeed;
+ }
+
+ final public Move iterativeDeepening(MoveGen.MoveList scMovesIn,
+ int maxDepth, int initialMaxNodes, boolean verbose) {
+ tStart = System.currentTimeMillis();
+// log = TreeLogger.getWriter("/home/petero/treelog.dmp", pos);
+ totalNodes = 0;
+ if (scMovesIn.size <= 0)
+ return null; // No moves to search
+ MoveInfo[] scMoves = new MoveInfo[scMovesIn.size];
+ for (int mi = 0, len = 0; mi < scMovesIn.size; mi++) {
+ Move m = scMovesIn.m[mi];
+ scMoves[len++] = new MoveInfo(m, 0);
+ }
+ maxNodes = initialMaxNodes;
+ nodesToGo = 0;
+ Position origPos = new Position(pos);
+ int bestScoreLastIter = 0;
+ boolean firstIteration = true;
+ Move bestMove = scMoves[0].move;
+ this.verbose = verbose;
+ if ((maxDepth < 0) || (maxDepth > 100)) {
+ maxDepth = 100;
+ }
+ for (int i = 0; i < searchTreeInfo.length; i++) {
+ searchTreeInfo[i].allowNullMove = true;
+ }
+ try {
+ for (int depthS = plyScale; ; depthS += plyScale, firstIteration = false) {
+ initNodeStats();
+ if (listener != null) listener.notifyDepth(depthS/plyScale);
+ int aspirationDelta = (Math.abs(bestScoreLastIter) <= MATE0 / 2) ? 20 : 1000;
+ int alpha = firstIteration ? -Search.MATE0 : Math.max(bestScoreLastIter - aspirationDelta, -Search.MATE0);
+ int bestScore = -Search.MATE0;
+ UndoInfo ui = new UndoInfo();
+ boolean needMoreTime = false;
+ for (int mi = 0; mi < scMoves.length; mi++) {
+ searchNeedMoreTime = (mi > 0);
+ Move m = scMoves[mi].move;
+ if ((listener != null) && (System.currentTimeMillis() - tStart >= 1000)) {
+ listener.notifyCurrMove(m, mi + 1);
+ }
+ nodes = qNodes = 0;
+ posHashList[posHashListSize++] = pos.zobristHash();
+ boolean givesCheck = MoveGen.givesCheck(pos, m);
+ int beta;
+ if (firstIteration) {
+ beta = Search.MATE0;
+ } else {
+ beta = (mi == 0) ? Math.min(bestScoreLastIter + aspirationDelta, Search.MATE0) : alpha + 1;
+ }
+
+ int lmrS = 0;
+ boolean isCapture = (pos.getPiece(m.to) != Piece.EMPTY);
+ boolean isPromotion = (m.promoteTo != Piece.EMPTY);
+ if ((depthS >= 3*plyScale) && !isCapture && !isPromotion) {
+ if (!givesCheck && !passedPawnPush(pos, m)) {
+ if (mi >= 3)
+ lmrS = plyScale;
+ }
+ }
+/* int nodes0 = nodes;
+ int qNodes0 = qNodes;
+ System.out.printf("%2d %5s %5d %5d %6s %6s ",
+ mi, "-", alpha, beta, "-", "-");
+ System.out.printf("%-6s...\n", TextIO.moveToUCIString(m)); */
+ pos.makeMove(m, ui);
+ SearchTreeInfo sti = searchTreeInfo[0];
+ sti.currentMove = m;
+ sti.lmr = lmrS;
+ sti.nodeIdx = -1;
+ int score = -negaScout(-beta, -alpha, 1, depthS - lmrS - plyScale, -1, givesCheck);
+ if ((lmrS > 0) && (score > alpha)) {
+ sti.lmr = 0;
+ score = -negaScout(-beta, -alpha, 1, depthS - plyScale, -1, givesCheck);
+ }
+ int nodesThisMove = nodes + qNodes;
+ posHashListSize--;
+ pos.unMakeMove(m, ui);
+ {
+ int type = TTEntry.T_EXACT;
+ if (score <= alpha) {
+ type = TTEntry.T_LE;
+ } else if (score >= beta) {
+ type = TTEntry.T_GE;
+ }
+ m.score = score;
+ tt.insert(pos.historyHash(), m, type, 0, depthS, UNKNOWN_SCORE);
+ }
+ if (score >= beta) {
+ int retryDelta = aspirationDelta * 2;
+ while (score >= beta) {
+ beta = Math.min(score + retryDelta, Search.MATE0);
+ retryDelta = Search.MATE0 * 2;
+ if (mi != 0)
+ needMoreTime = true;
+ bestMove = m;
+ if (verbose)
+ System.out.printf("%-6s %6d %6d %6d >=\n", TextIO.moveToString(pos, m, false),
+ score, nodes, qNodes);
+ notifyPV(depthS/plyScale, score, false, true, m);
+ nodes = qNodes = 0;
+ posHashList[posHashListSize++] = pos.zobristHash();
+ pos.makeMove(m, ui);
+ int score2 = -negaScout(-beta, -score, 1, depthS - plyScale, -1, givesCheck);
+ score = Math.max(score, score2);
+ nodesThisMove += nodes + qNodes;
+ posHashListSize--;
+ pos.unMakeMove(m, ui);
+ }
+ } else if ((mi == 0) && (score <= alpha)) {
+ int retryDelta = Search.MATE0 * 2;
+ while (score <= alpha) {
+ alpha = Math.max(score - retryDelta, -Search.MATE0);
+ retryDelta = Search.MATE0 * 2;
+ needMoreTime = searchNeedMoreTime = true;
+ if (verbose)
+ System.out.printf("%-6s %6d %6d %6d <=\n", TextIO.moveToString(pos, m, false),
+ score, nodes, qNodes);
+ notifyPV(depthS/plyScale, score, true, false, m);
+ nodes = qNodes = 0;
+ posHashList[posHashListSize++] = pos.zobristHash();
+ pos.makeMove(m, ui);
+ score = -negaScout(-score, -alpha, 1, depthS - plyScale, -1, givesCheck);
+ nodesThisMove += nodes + qNodes;
+ posHashListSize--;
+ pos.unMakeMove(m, ui);
+ }
+ }
+ if (verbose || ((listener != null) && !firstIteration)) {
+ boolean havePV = false;
+ String PV = "";
+ if ((score > alpha) || (mi == 0)) {
+ havePV = true;
+ if (verbose) {
+ PV = TextIO.moveToString(pos, m, false) + " ";
+ pos.makeMove(m, ui);
+ PV += tt.extractPV(pos);
+ pos.unMakeMove(m, ui);
+ }
+ }
+ if (verbose) {
+/* System.out.printf("%2d %5d %5d %5d %6d %6d ",
+ mi, score, alpha, beta, nodes-nodes0, qNodes-qNodes0);
+ System.out.printf("%-6s\n", TextIO.moveToUCIString(m)); */
+ System.out.printf("%-6s %6d %6d %6d%s %s\n",
+ TextIO.moveToString(pos, m, false), score,
+ nodes, qNodes, (score > alpha ? " *" : ""), PV);
+ }
+ if (havePV && !firstIteration) {
+ notifyPV(depthS/plyScale, score, false, false, m);
+ }
+ }
+ scMoves[mi].move.score = score;
+ scMoves[mi].nodes = nodesThisMove;
+ bestScore = Math.max(bestScore, score);
+ if (!firstIteration) {
+ if ((score > alpha) || (mi == 0)) {
+ alpha = score;
+ MoveInfo tmp = scMoves[mi];
+ for (int i = mi - 1; i >= 0; i--) {
+ scMoves[i + 1] = scMoves[i];
+ }
+ scMoves[0] = tmp;
+ bestMove = scMoves[0].move;
+ }
+ }
+ if (!firstIteration) {
+ long timeLimit = needMoreTime ? maxTimeMillis : minTimeMillis;
+ if (timeLimit >= 0) {
+ long tNow = System.currentTimeMillis();
+ if (tNow - tStart >= timeLimit)
+ break;
+ }
+ }
+ }
+ if (firstIteration) {
+ Arrays.sort(scMoves, new MoveInfo.SortByScore());
+ bestMove = scMoves[0].move;
+ notifyPV(depthS/plyScale, bestMove.score, false, false, bestMove);
+ }
+ long tNow = System.currentTimeMillis();
+ if (verbose) {
+ for (int i = 0; i < 20; i++) {
+ System.out.printf("%2d %7d %7d\n", i, nodesPlyVec[i], nodesDepthVec[i]);
+ }
+ System.out.printf("Time: %.3f depth:%.2f nps:%d\n", (tNow - tStart) * .001, depthS/(double)plyScale,
+ (int)(totalNodes / ((tNow - tStart) * .001)));
+ }
+ if (maxTimeMillis >= 0) {
+ if (tNow - tStart >= minTimeMillis)
+ break;
+ }
+ if (depthS >= maxDepth * plyScale)
+ break;
+ if (maxNodes >= 0) {
+ if (totalNodes >= maxNodes)
+ break;
+ }
+ int plyToMate = Search.MATE0 - Math.abs(bestScore);
+ if (depthS >= plyToMate * plyScale)
+ break;
+ bestScoreLastIter = bestScore;
+
+ if (!firstIteration) {
+ // Moves that were hard to search should be searched early in the next iteration
+ Arrays.sort(scMoves, 1, scMoves.length, new MoveInfo.SortByNodes());
+ }
+ }
+ } catch (StopSearch ss) {
+ pos = origPos;
+ }
+ notifyStats();
+
+ if (log != null) {
+ log.close();
+ log = null;
+ }
+ return bestMove;
+ }
+
+ private final void notifyPV(int depth, int score, boolean uBound, boolean lBound, Move m) {
+ if (listener != null) {
+ boolean isMate = false;
+ if (score > MATE0 / 2) {
+ isMate = true;
+ score = (MATE0 - score) / 2;
+ } else if (score < -MATE0 / 2) {
+ isMate = true;
+ score = -((MATE0 + score - 1) / 2);
+ }
+ long tNow = System.currentTimeMillis();
+ int time = (int) (tNow - tStart);
+ int nps = (time > 0) ? (int)(totalNodes / (time / 1000.0)) : 0;
+ ArrayList pv = tt.extractPVMoves(pos, m);
+ listener.notifyPV(depth, score, time, totalNodes, nps, isMate, uBound, lBound, pv);
+ }
+ }
+
+ private final void notifyStats() {
+ long tNow = System.currentTimeMillis();
+ if (listener != null) {
+ int time = (int) (tNow - tStart);
+ int nps = (time > 0) ? (int)(totalNodes / (time / 1000.0)) : 0;
+ listener.notifyStats(totalNodes, nps, time);
+ }
+ tLastStats = tNow;
+ }
+
+ private static final Move emptyMove = new Move(0, 0, Piece.EMPTY, 0);
+
+ /**
+ * Main recursive search algorithm.
+ * @return Score for the side to make a move, in position given by "pos".
+ */
+ final public int negaScout(int alpha, int beta, int ply, int depth, int recaptureSquare,
+ final boolean inCheck) throws StopSearch {
+ if (log != null) {
+ SearchTreeInfo sti = searchTreeInfo[ply-1];
+ long idx = log.logNodeStart(sti.nodeIdx, sti.currentMove, alpha, beta, ply, depth/plyScale);
+ searchTreeInfo[ply].nodeIdx = idx;
+ }
+ if (--nodesToGo <= 0) {
+ nodesToGo = nodesBetweenTimeCheck;
+ long tNow = System.currentTimeMillis();
+ long timeLimit = searchNeedMoreTime ? maxTimeMillis : minTimeMillis;
+ if ( ((timeLimit >= 0) && (tNow - tStart >= timeLimit)) ||
+ ((maxNodes >= 0) && (totalNodes >= maxNodes))) {
+ throw new StopSearch();
+ }
+ if (tNow - tLastStats >= 1000) {
+ notifyStats();
+ }
+ }
+
+ // Collect statistics
+ if (verbose) {
+ if (ply < 20) nodesPlyVec[ply]++;
+ if (depth < 20*plyScale) nodesDepthVec[depth/plyScale]++;
+ }
+ final long hKey = pos.historyHash();
+
+ // Draw tests
+ if (canClaimDraw50(pos)) {
+ if (MoveGen.canTakeKing(pos)) {
+ int score = MATE0 - ply;
+ if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, score, TTEntry.T_EXACT, UNKNOWN_SCORE, hKey);
+ return score;
+ }
+ if (inCheck) {
+ MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ if (moves.size == 0) { // Can't claim draw if already check mated.
+ int score = -(MATE0-(ply+1));
+ if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, score, TTEntry.T_EXACT, UNKNOWN_SCORE, hKey);
+ moveGen.returnMoveList(moves);
+ return score;
+ }
+ moveGen.returnMoveList(moves);
+ }
+ if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, 0, TTEntry.T_EXACT, UNKNOWN_SCORE, hKey);
+ return 0;
+ }
+ if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashFirstNew)) {
+ if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, 0, TTEntry.T_EXACT, UNKNOWN_SCORE, hKey);
+ return 0; // No need to test for mate here, since it would have been
+ // discovered the first time the position came up.
+ }
+
+ int evalScore = UNKNOWN_SCORE;
+ // Check transposition table
+ TTEntry ent = tt.probe(hKey);
+ Move hashMove = null;
+ SearchTreeInfo sti = searchTreeInfo[ply];
+ if (ent.type != TTEntry.T_EMPTY) {
+ int score = ent.getScore(ply);
+ evalScore = ent.evalScore;
+ int plyToMate = MATE0 - Math.abs(score);
+ int eDepth = ent.getDepth();
+ if ((beta == alpha + 1) && ((eDepth >= depth) || (eDepth >= plyToMate*plyScale))) {
+ if ( (ent.type == TTEntry.T_EXACT) ||
+ (ent.type == TTEntry.T_GE) && (score >= beta) ||
+ (ent.type == TTEntry.T_LE) && (score <= alpha)) {
+ if (score >= beta) {
+ hashMove = sti.hashMove;
+ ent.getMove(hashMove);
+ if ((hashMove != null) && (hashMove.from != hashMove.to))
+ if (pos.getPiece(hashMove.to) == Piece.EMPTY)
+ kt.addKiller(ply, hashMove);
+ }
+ if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, score, ent.type, evalScore, hKey);
+ return score;
+ }
+ }
+ hashMove = sti.hashMove;
+ ent.getMove(hashMove);
+ }
+
+ int posExtend = inCheck ? plyScale : 0; // Check extension
+
+ // If out of depth, perform quiescence search
+ if (depth + posExtend <= 0) {
+ q0Eval = evalScore;
+ int score = quiesce(alpha, beta, ply, 0, inCheck);
+ int type = TTEntry.T_EXACT;
+ if (score <= alpha) {
+ type = TTEntry.T_LE;
+ } else if (score >= beta) {
+ type = TTEntry.T_GE;
+ }
+ emptyMove.score = score;
+ tt.insert(hKey, emptyMove, type, ply, depth, q0Eval);
+ if (log != null) log.logNodeEnd(sti.nodeIdx, score, type, evalScore, hKey);
+ return score;
+ }
+
+ // Try null-move pruning
+ // FIXME! Try null-move verification in late endgames. See loss in round 21.
+ sti.currentMove = emptyMove;
+ if ( (depth >= 3*plyScale) && !inCheck && sti.allowNullMove &&
+ (Math.abs(beta) <= MATE0 / 2)) {
+ if (MoveGen.canTakeKing(pos)) {
+ int score = MATE0 - ply;
+ if (log != null) log.logNodeEnd(sti.nodeIdx, score, TTEntry.T_EXACT, evalScore, hKey);
+ return score;
+ }
+ boolean nullOk;
+ if (pos.whiteMove) {
+ nullOk = (pos.wMtrl > pos.wMtrlPawns) && (pos.wMtrlPawns > 0);
+ } else {
+ nullOk = (pos.bMtrl > pos.bMtrlPawns) && (pos.bMtrlPawns > 0);
+ }
+ if (nullOk) {
+ if (evalScore == UNKNOWN_SCORE)
+ evalScore = eval.evalPos(pos);
+ if (evalScore < beta)
+ nullOk = false;
+ }
+ if (nullOk) {
+ final int R = (depth > 6*plyScale) ? 4*plyScale : 3*plyScale;
+ pos.setWhiteMove(!pos.whiteMove);
+ int epSquare = pos.getEpSquare();
+ pos.setEpSquare(-1);
+ searchTreeInfo[ply+1].allowNullMove = false;
+ int score = -negaScout(-beta, -(beta - 1), ply + 1, depth - R, -1, false);
+ searchTreeInfo[ply+1].allowNullMove = true;
+ pos.setEpSquare(epSquare);
+ pos.setWhiteMove(!pos.whiteMove);
+ if (score >= beta) {
+ if (score > MATE0 / 2)
+ score = beta;
+ emptyMove.score = score;
+ tt.insert(hKey, emptyMove, TTEntry.T_GE, ply, depth, evalScore);
+ if (log != null) log.logNodeEnd(sti.nodeIdx, score, TTEntry.T_GE, evalScore, hKey);
+ return score;
+ } else {
+ if ((searchTreeInfo[ply-1].lmr > 0) && (depth < 5*plyScale)) {
+ Move m1 = searchTreeInfo[ply-1].currentMove;
+ Move m2 = searchTreeInfo[ply+1].bestMove; // threat move
+ if (m1.from != m1.to) {
+ if ((m1.to == m2.from) || (m1.from == m2.to) ||
+ ((BitBoard.squaresBetween[m2.from][m2.to] & (1L << m1.from)) != 0)) {
+ // if the threat move was made possible by a reduced
+ // move on the previous ply, the reduction was unsafe.
+ // Return alpha to trigger a non-reduced re-search.
+ if (log != null) log.logNodeEnd(sti.nodeIdx, alpha, TTEntry.T_LE, evalScore, hKey);
+ return alpha;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Razoring
+ if ((Math.abs(alpha) <= MATE0 / 2) && (depth < 4*plyScale) && (beta == alpha + 1)) {
+ if (evalScore == UNKNOWN_SCORE) {
+ evalScore = eval.evalPos(pos);
+ }
+ final int razorMargin = 250;
+ if (evalScore < beta - razorMargin) {
+ q0Eval = evalScore;
+ int score = quiesce(alpha-razorMargin, beta-razorMargin, ply, 0, inCheck);
+ if (score <= alpha-razorMargin) {
+ emptyMove.score = score;
+ tt.insert(hKey, emptyMove, TTEntry.T_LE, ply, depth, evalScore);
+ if (log != null) log.logNodeEnd(sti.nodeIdx, score, TTEntry.T_LE, evalScore, hKey);
+ return score;
+ }
+ }
+ }
+
+ boolean futilityPrune = false;
+ int futilityScore = alpha;
+ if (!inCheck && (depth < 5*plyScale) && (posExtend == 0)) {
+ if ((Math.abs(alpha) <= MATE0 / 2) && (Math.abs(beta) <= MATE0 / 2)) {
+ int margin;
+ if (depth <= plyScale) {
+ margin = 61;
+ } else if (depth <= 2*plyScale) {
+ margin = 144;
+ } else if (depth <= 3*plyScale) {
+ margin = 268;
+ } else {
+ margin = 334;
+ }
+ if (evalScore == UNKNOWN_SCORE) {
+ evalScore = eval.evalPos(pos);
+ }
+ futilityScore = evalScore + margin;
+ if (futilityScore <= alpha) {
+ futilityPrune = true;
+ }
+ }
+ }
+
+ if ((depth > 4*plyScale) && ((hashMove == null) || (hashMove.from == hashMove.to))) {
+ boolean isPv = beta > alpha + 1;
+ if (isPv || (depth > 8 * plyScale)) {
+ // No hash move. Try internal iterative deepening.
+ long savedNodeIdx = sti.nodeIdx;
+ int newDepth = isPv ? depth - 2 * plyScale : depth * 3 / 8;
+ negaScout(alpha, beta, ply, newDepth, -1, inCheck);
+ sti.nodeIdx = savedNodeIdx;
+ ent = tt.probe(hKey);
+ if (ent.type != TTEntry.T_EMPTY) {
+ hashMove = sti.hashMove;
+ ent.getMove(hashMove);
+ }
+ }
+ }
+
+ // Start searching move alternatives
+ // FIXME! Try hash move before generating move list.
+ MoveGen.MoveList moves;
+ if (inCheck)
+ moves = moveGen.checkEvasions(pos);
+ else
+ moves = moveGen.pseudoLegalMoves(pos);
+ boolean seeDone = false;
+ boolean hashMoveSelected = true;
+ if (!selectHashMove(moves, hashMove)) {
+ scoreMoveList(moves, ply);
+ seeDone = true;
+ hashMoveSelected = false;
+ }
+
+ UndoInfo ui = sti.undoInfo;
+ boolean haveLegalMoves = false;
+ int illegalScore = -(MATE0-(ply+1));
+ int b = beta;
+ int bestScore = illegalScore;
+ int bestMove = -1;
+ int lmrCount = 0;
+ for (int mi = 0; mi < moves.size; mi++) {
+ if ((mi == 1) && !seeDone) {
+ scoreMoveList(moves, ply, 1);
+ seeDone = true;
+ }
+ if ((mi > 0) || !hashMoveSelected) {
+ selectBest(moves, mi);
+ }
+ Move m = moves.m[mi];
+ if (pos.getPiece(m.to) == (pos.whiteMove ? Piece.BKING : Piece.WKING)) {
+ moveGen.returnMoveList(moves);
+ int score = MATE0-ply;
+ if (log != null) log.logNodeEnd(sti.nodeIdx, score, TTEntry.T_EXACT, evalScore, hKey);
+ return score; // King capture
+ }
+ int newCaptureSquare = -1;
+ boolean isCapture = (pos.getPiece(m.to) != Piece.EMPTY);
+ boolean isPromotion = (m.promoteTo != Piece.EMPTY);
+ int sVal = Integer.MIN_VALUE;
+ // FIXME! Test extending pawn pushes to 7:th rank
+ boolean mayReduce = (m.score < 53) && (!isCapture || m.score < 0) && !isPromotion;
+ boolean givesCheck = MoveGen.givesCheck(pos, m);
+ boolean doFutility = false;
+ if (futilityPrune && mayReduce && haveLegalMoves) {
+ if (!givesCheck && !passedPawnPush(pos, m))
+ doFutility = true;
+ }
+ int score;
+ if (doFutility) {
+ score = futilityScore;
+ } else {
+ int moveExtend = 0;
+ if (posExtend == 0) {
+ final int pV = Evaluate.pV;
+ if ((m.to == recaptureSquare)) {
+ if (sVal == Integer.MIN_VALUE) sVal = SEE(m);
+ int tVal = Evaluate.pieceValue[pos.getPiece(m.to)];
+ if (sVal > tVal - pV / 2)
+ moveExtend = plyScale;
+ }
+ if ((moveExtend < plyScale) && isCapture && (pos.wMtrlPawns + pos.bMtrlPawns > pV)) {
+ // Extend if going into pawn endgame
+ int capVal = Evaluate.pieceValue[pos.getPiece(m.to)];
+ if (pos.whiteMove) {
+ if ((pos.wMtrl == pos.wMtrlPawns) && (pos.bMtrl - pos.bMtrlPawns == capVal))
+ moveExtend = plyScale;
+ } else {
+ if ((pos.bMtrl == pos.bMtrlPawns) && (pos.wMtrl - pos.wMtrlPawns == capVal))
+ moveExtend = plyScale;
+ }
+ }
+ }
+ int extend = Math.max(posExtend, moveExtend);
+ int lmr = 0;
+ if ((depth >= 3*plyScale) && mayReduce && (extend == 0)) {
+ if (!givesCheck && !passedPawnPush(pos, m)) {
+ lmrCount++;
+ if ((lmrCount > 3) && (depth > 3*plyScale) && !isCapture) {
+ lmr = 2*plyScale;
+ } else {
+ lmr = 1*plyScale;
+ }
+ }
+ }
+ int newDepth = depth - plyScale + extend - lmr;
+ if (isCapture && (givesCheck || (depth + extend) > plyScale)) {
+ // Compute recapture target square, but only if we are not going
+ // into q-search at the next ply.
+ int fVal = Evaluate.pieceValue[pos.getPiece(m.from)];
+ int tVal = Evaluate.pieceValue[pos.getPiece(m.to)];
+ final int pV = Evaluate.pV;
+ if (Math.abs(tVal - fVal) < pV / 2) { // "Equal" capture
+ sVal = SEE(m);
+ if (Math.abs(sVal) < pV / 2)
+ newCaptureSquare = m.to;
+ }
+ }
+ posHashList[posHashListSize++] = pos.zobristHash();
+ pos.makeMove(m, ui);
+ nodes++;
+ totalNodes++;
+ sti.currentMove = m;
+/* int nodes0 = nodes;
+ int qNodes0 = qNodes;
+ if ((ply < 3) && (newDepth > plyScale)) {
+ System.out.printf("%2d %5s %5d %5d %6s %6s ",
+ mi, "-", alpha, beta, "-", "-");
+ for (int i = 0; i < ply; i++)
+ System.out.printf(" ");
+ System.out.printf("%-6s...\n", TextIO.moveToUCIString(m));
+ } */
+ sti.lmr = lmr;
+ score = -negaScout(-b, -alpha, ply + 1, newDepth, newCaptureSquare, givesCheck);
+ if (((lmr > 0) && (score > alpha)) ||
+ ((score > alpha) && (score < beta) && (b != beta) && (score != illegalScore))) {
+ sti.lmr = 0;
+ newDepth += lmr;
+ score = -negaScout(-beta, -alpha, ply + 1, newDepth, newCaptureSquare, givesCheck);
+ }
+/* if (ply <= 3) {
+ System.out.printf("%2d %5d %5d %5d %6d %6d ",
+ mi, score, alpha, beta, nodes-nodes0, qNodes-qNodes0);
+ for (int i = 0; i < ply; i++)
+ System.out.printf(" ");
+ System.out.printf("%-6s\n", TextIO.moveToUCIString(m));
+ }*/
+ posHashListSize--;
+ pos.unMakeMove(m, ui);
+ }
+ if (weak && haveLegalMoves)
+ if (weakPlaySkipMove(pos, m, ply))
+ score = illegalScore;
+ m.score = score;
+
+ if (score != illegalScore) {
+ haveLegalMoves = true;
+ }
+ bestScore = Math.max(bestScore, score);
+ if (score > alpha) {
+ alpha = score;
+ bestMove = mi;
+ sti.bestMove.from = m.from;
+ sti.bestMove.to = m.to;
+ sti.bestMove.promoteTo = m.promoteTo;
+ }
+ if (alpha >= beta) {
+ if (pos.getPiece(m.to) == Piece.EMPTY) {
+ kt.addKiller(ply, m);
+ ht.addSuccess(pos, m, depth/plyScale);
+ for (int mi2 = mi - 1; mi2 >= 0; mi2--) {
+ Move m2 = moves.m[mi2];
+ if (pos.getPiece(m2.to) == Piece.EMPTY)
+ ht.addFail(pos, m2, depth/plyScale);
+ }
+ }
+ tt.insert(hKey, m, TTEntry.T_GE, ply, depth, evalScore);
+ moveGen.returnMoveList(moves);
+ if (log != null) log.logNodeEnd(sti.nodeIdx, alpha, TTEntry.T_GE, evalScore, hKey);
+ return alpha;
+ }
+ b = alpha + 1;
+ }
+ if (!haveLegalMoves && !inCheck) {
+ moveGen.returnMoveList(moves);
+ if (log != null) log.logNodeEnd(sti.nodeIdx, 0, TTEntry.T_EXACT, evalScore, hKey);
+ return 0; // Stale-mate
+ }
+ if (bestMove >= 0) {
+ tt.insert(hKey, moves.m[bestMove], TTEntry.T_EXACT, ply, depth, evalScore);
+ if (log != null) log.logNodeEnd(sti.nodeIdx, bestScore, TTEntry.T_EXACT, evalScore, hKey);
+ } else {
+ emptyMove.score = bestScore;
+ tt.insert(hKey, emptyMove, TTEntry.T_LE, ply, depth, evalScore);
+ if (log != null) log.logNodeEnd(sti.nodeIdx, bestScore, TTEntry.T_LE, evalScore, hKey);
+ }
+ moveGen.returnMoveList(moves);
+ return bestScore;
+ }
+
+ /** Return true if move should be skipped in order to make engine play weaker. */
+ private final boolean weakPlaySkipMove(Position pos, Move m, int ply) {
+ long rndL = pos.zobristHash() ^ Position.psHashKeys[0][m.from] ^
+ Position.psHashKeys[0][m.to] ^ randomSeed;
+ double rnd = ((rndL & 0x7fffffffffffffffL) % 1000000000) / 1e9;
+
+ double s = strength * 1e-3;
+ double offs = 4 - 15 * s;
+ double effPly = ply * Evaluate.interpolate(pos.wMtrl + pos.bMtrl, 0, 30, Evaluate.qV * 4, 100) * 1e-2;
+ double t = effPly + offs;
+ double p = 1/(1+Math.exp(t)); // Probability to "see" move
+ boolean easyMove = ((pos.getPiece(m.to) != Piece.EMPTY) ||
+ (ply < 2) || (searchTreeInfo[ply-2].currentMove.to == m.from));
+ if (easyMove)
+ p = 1-(1-p)*(1-p);
+ if (rnd > p)
+ return true;
+ return false;
+ }
+
+ private static final boolean passedPawnPush(Position pos, Move m) {
+ int p = pos.getPiece(m.from);
+ if (pos.whiteMove) {
+ if (p != Piece.WPAWN)
+ return false;
+ if ((BitBoard.wPawnBlockerMask[m.to] & pos.pieceTypeBB[Piece.BPAWN]) != 0)
+ return false;
+ return m.to >= 40;
+ } else {
+ if (p != Piece.BPAWN)
+ return false;
+ if ((BitBoard.bPawnBlockerMask[m.to] & pos.pieceTypeBB[Piece.WPAWN]) != 0)
+ return false;
+ return m.to <= 23;
+ }
+ }
+
+ /**
+ * Quiescence search. Only non-losing captures are searched.
+ */
+ final private int quiesce(int alpha, int beta, int ply, int depth, final boolean inCheck) {
+ int score;
+ if (inCheck) {
+ score = -(MATE0 - (ply+1));
+ } else {
+ if ((depth == 0) && (q0Eval != UNKNOWN_SCORE)) {
+ score = q0Eval;
+ } else {
+ score = eval.evalPos(pos);
+ if (depth == 0)
+ q0Eval = score;
+ }
+ }
+ if (score >= beta) {
+ if ((depth == 0) && (score < MATE0 - ply)) {
+ if (MoveGen.canTakeKing(pos)) {
+ // To make stale-mate detection work
+ score = MATE0 - ply;
+ }
+ }
+ return score;
+ }
+ final int evalScore = score;
+ if (score > alpha)
+ alpha = score;
+ int bestScore = score;
+ final boolean tryChecks = (depth > -3);
+ MoveGen.MoveList moves;
+ if (inCheck) {
+ moves = moveGen.checkEvasions(pos);
+ } else if (tryChecks) {
+ moves = moveGen.pseudoLegalCapturesAndChecks(pos);
+ } else {
+ moves = moveGen.pseudoLegalCaptures(pos);
+ }
+ scoreMoveListMvvLva(moves);
+ UndoInfo ui = searchTreeInfo[ply].undoInfo;
+ for (int mi = 0; mi < moves.size; mi++) {
+ if (mi < 8) {
+ // If the first 8 moves didn't fail high, this is probably an ALL-node,
+ // so spending more effort on move ordering is probably wasted time.
+ selectBest(moves, mi);
+ }
+ Move m = moves.m[mi];
+ if (pos.getPiece(m.to) == (pos.whiteMove ? Piece.BKING : Piece.WKING)) {
+ moveGen.returnMoveList(moves);
+ return MATE0-ply; // King capture
+ }
+ boolean givesCheck = false;
+ boolean givesCheckComputed = false;
+ if (inCheck) {
+ // Allow all moves
+ } else {
+ if ((pos.getPiece(m.to) == Piece.EMPTY) && (m.promoteTo == Piece.EMPTY)) {
+ // Non-capture
+ if (!tryChecks)
+ continue;
+ givesCheck = MoveGen.givesCheck(pos, m);
+ givesCheckComputed = true;
+ if (!givesCheck)
+ continue;
+ if (negSEE(m)) // Needed because m.score is not computed for non-captures
+ continue;
+ } else {
+ if (negSEE(m))
+ continue;
+ int capt = Evaluate.pieceValue[pos.getPiece(m.to)];
+ int prom = Evaluate.pieceValue[m.promoteTo];
+ int optimisticScore = evalScore + capt + prom + 200;
+ if (optimisticScore < alpha) { // Delta pruning
+ if ((pos.wMtrlPawns > 0) && (pos.wMtrl > capt + pos.wMtrlPawns) &&
+ (pos.bMtrlPawns > 0) && (pos.bMtrl > capt + pos.bMtrlPawns)) {
+ if (depth -1 > -4) {
+ givesCheck = MoveGen.givesCheck(pos, m);
+ givesCheckComputed = true;
+ }
+ if (!givesCheck) {
+ if (optimisticScore > bestScore)
+ bestScore = optimisticScore;
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ if (!givesCheckComputed) {
+ if (depth - 1 > -4) {
+ givesCheck = MoveGen.givesCheck(pos, m);
+ }
+ }
+ final boolean nextInCheck = (depth - 1) > -4 ? givesCheck : false;
+
+ pos.makeMove(m, ui);
+ qNodes++;
+ totalNodes++;
+ score = -quiesce(-beta, -alpha, ply + 1, depth - 1, nextInCheck);
+ pos.unMakeMove(m, ui);
+ if (score > bestScore) {
+ bestScore = score;
+ if (score > alpha) {
+ alpha = score;
+ if (alpha >= beta) {
+ moveGen.returnMoveList(moves);
+ return alpha;
+ }
+ }
+ }
+ }
+ moveGen.returnMoveList(moves);
+ return bestScore;
+ }
+
+ /** Return >0, 0, <0, depending on the sign of SEE(m). */
+ final public int signSEE(Move m) {
+ int p0 = Evaluate.pieceValue[pos.getPiece(m.from)];
+ int p1 = Evaluate.pieceValue[pos.getPiece(m.to)];
+ if (p0 < p1)
+ return 1;
+ return SEE(m);
+ }
+
+ /** Return true if SEE(m) < 0. */
+ final public boolean negSEE(Move m) {
+ int p0 = Evaluate.pieceValue[pos.getPiece(m.from)];
+ int p1 = Evaluate.pieceValue[pos.getPiece(m.to)];
+ if (p1 >= p0)
+ return false;
+ return SEE(m) < 0;
+ }
+
+ private int[] captures = new int[64]; // Value of captured pieces
+ private UndoInfo seeUi = new UndoInfo();
+
+ /**
+ * Static exchange evaluation function.
+ * @return SEE score for m. Positive value is good for the side that makes the first move.
+ */
+ final public int SEE(Move m) {
+ final int kV = Evaluate.kV;
+
+ final int square = m.to;
+ if (square == pos.getEpSquare()) {
+ captures[0] = Evaluate.pV;
+ } else {
+ captures[0] = Evaluate.pieceValue[pos.getPiece(square)];
+ if (captures[0] == kV)
+ return kV;
+ }
+ int nCapt = 1; // Number of entries in captures[]
+
+ pos.makeSEEMove(m, seeUi);
+ boolean white = pos.whiteMove;
+ int valOnSquare = Evaluate.pieceValue[pos.getPiece(square)];
+ long occupied = pos.whiteBB | pos.blackBB;
+ while (true) {
+ int bestValue = Integer.MAX_VALUE;
+ long atk;
+ if (white) {
+ atk = BitBoard.bPawnAttacks[square] & pos.pieceTypeBB[Piece.WPAWN] & occupied;
+ if (atk != 0) {
+ bestValue = Evaluate.pV;
+ } else {
+ atk = BitBoard.knightAttacks[square] & pos.pieceTypeBB[Piece.WKNIGHT] & occupied;
+ if (atk != 0) {
+ bestValue = Evaluate.nV;
+ } else {
+ long bAtk = BitBoard.bishopAttacks(square, occupied) & occupied;
+ atk = bAtk & pos.pieceTypeBB[Piece.WBISHOP];
+ if (atk != 0) {
+ bestValue = Evaluate.bV;
+ } else {
+ long rAtk = BitBoard.rookAttacks(square, occupied) & occupied;
+ atk = rAtk & pos.pieceTypeBB[Piece.WROOK];
+ if (atk != 0) {
+ bestValue = Evaluate.rV;
+ } else {
+ atk = (bAtk | rAtk) & pos.pieceTypeBB[Piece.WQUEEN];
+ if (atk != 0) {
+ bestValue = Evaluate.qV;
+ } else {
+ atk = BitBoard.kingAttacks[square] & pos.pieceTypeBB[Piece.WKING] & occupied;
+ if (atk != 0) {
+ bestValue = kV;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ atk = BitBoard.wPawnAttacks[square] & pos.pieceTypeBB[Piece.BPAWN] & occupied;
+ if (atk != 0) {
+ bestValue = Evaluate.pV;
+ } else {
+ atk = BitBoard.knightAttacks[square] & pos.pieceTypeBB[Piece.BKNIGHT] & occupied;
+ if (atk != 0) {
+ bestValue = Evaluate.nV;
+ } else {
+ long bAtk = BitBoard.bishopAttacks(square, occupied) & occupied;
+ atk = bAtk & pos.pieceTypeBB[Piece.BBISHOP];
+ if (atk != 0) {
+ bestValue = Evaluate.bV;
+ } else {
+ long rAtk = BitBoard.rookAttacks(square, occupied) & occupied;
+ atk = rAtk & pos.pieceTypeBB[Piece.BROOK];
+ if (atk != 0) {
+ bestValue = Evaluate.rV;
+ } else {
+ atk = (bAtk | rAtk) & pos.pieceTypeBB[Piece.BQUEEN];
+ if (atk != 0) {
+ bestValue = Evaluate.qV;
+ } else {
+ atk = BitBoard.kingAttacks[square] & pos.pieceTypeBB[Piece.BKING] & occupied;
+ if (atk != 0) {
+ bestValue = kV;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ captures[nCapt++] = valOnSquare;
+ if (valOnSquare == kV)
+ break;
+ valOnSquare = bestValue;
+ occupied &= ~(atk & -atk);
+ white = !white;
+ }
+ pos.unMakeSEEMove(m, seeUi);
+
+ int score = 0;
+ for (int i = nCapt - 1; i > 0; i--) {
+ score = Math.max(0, captures[i] - score);
+ }
+ return captures[0] - score;
+ }
+
+ /**
+ * Compute scores for each move in a move list, using SEE, killer and history information.
+ * @param moves List of moves to score.
+ */
+ final void scoreMoveList(MoveGen.MoveList moves, int ply) {
+ scoreMoveList(moves, ply, 0);
+ }
+ final void scoreMoveList(MoveGen.MoveList moves, int ply, int startIdx) {
+ for (int i = startIdx; i < moves.size; i++) {
+ Move m = moves.m[i];
+ boolean isCapture = (pos.getPiece(m.to) != Piece.EMPTY) || (m.promoteTo != Piece.EMPTY);
+ int score = 0;
+ if (isCapture) {
+ int seeScore = isCapture ? signSEE(m) : 0;
+ int v = pos.getPiece(m.to);
+ int a = pos.getPiece(m.from);
+ score = Evaluate.pieceValue[v]/10 * 1000 - Evaluate.pieceValue[a]/10;
+ if (seeScore > 0)
+ score += 2000000;
+ else if (seeScore == 0)
+ score += 1000000;
+ else
+ score -= 1000000;
+ score *= 100;
+ }
+ int ks = kt.getKillerScore(ply, m);
+ if (ks > 0) {
+ score += ks + 50;
+ } else {
+ int hs = ht.getHistScore(pos, m);
+ score += hs;
+ }
+ m.score = score;
+ }
+ }
+ private final void scoreMoveListMvvLva(MoveGen.MoveList moves) {
+ for (int i = 0; i < moves.size; i++) {
+ Move m = moves.m[i];
+ int v = pos.getPiece(m.to);
+ int a = pos.getPiece(m.from);
+ m.score = Evaluate.pieceValue[v] * 10000 - Evaluate.pieceValue[a];
+ }
+ }
+
+ /**
+ * Find move with highest score and move it to the front of the list.
+ */
+ final static void selectBest(MoveGen.MoveList moves, int startIdx) {
+ int bestIdx = startIdx;
+ int bestScore = moves.m[bestIdx].score;
+ for (int i = startIdx + 1; i < moves.size; i++) {
+ int sc = moves.m[i].score;
+ if (sc > bestScore) {
+ bestIdx = i;
+ bestScore = sc;
+ }
+ }
+ if (bestIdx != startIdx) {
+ Move m = moves.m[startIdx];
+ moves.m[startIdx] = moves.m[bestIdx];
+ moves.m[bestIdx] = m;
+ }
+ }
+
+ /** If hashMove exists in the move list, move the hash move to the front of the list. */
+ final static boolean selectHashMove(MoveGen.MoveList moves, Move hashMove) {
+ if (hashMove == null) {
+ return false;
+ }
+ for (int i = 0; i < moves.size; i++) {
+ Move m = moves.m[i];
+ if (m.equals(hashMove)) {
+ moves.m[i] = moves.m[0];
+ moves.m[0] = m;
+ m.score = 10000;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public final static boolean canClaimDraw50(Position pos) {
+ return (pos.halfMoveClock >= 100);
+ }
+
+ public final static boolean canClaimDrawRep(Position pos, long[] posHashList, int posHashListSize, int posHashFirstNew) {
+ int reps = 0;
+ for (int i = posHashListSize - 4; i >= 0; i -= 2) {
+ if (pos.zobristHash() == posHashList[i]) {
+ reps++;
+ if (i >= posHashFirstNew) {
+ reps++;
+ break;
+ }
+ }
+ }
+ return (reps >= 2);
+ }
+
+ private final void initNodeStats() {
+ nodes = qNodes = 0;
+ nodesPlyVec = new int[20];
+ nodesDepthVec = new int[20];
+ for (int i = 0; i < 20; i++) {
+ nodesPlyVec[i] = 0;
+ nodesDepthVec[i] = 0;
+ }
+ }
+}
diff --git a/CuckooChessEngine/src/chess/TextIO.java b/CuckooChessEngine/src/chess/TextIO.java
new file mode 100644
index 0000000..402856d
--- /dev/null
+++ b/CuckooChessEngine/src/chess/TextIO.java
@@ -0,0 +1,612 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+/**
+ *
+ * @author petero
+ */
+public class TextIO {
+ static public final String startPosFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
+
+ /** Parse a FEN string and return a chess Position object. */
+ public static final Position readFEN(String fen) throws ChessParseError {
+ Position pos = new Position();
+ String[] words = fen.split(" ");
+ if (words.length < 2) {
+ throw new ChessParseError("Too few spaces");
+ }
+
+ // Piece placement
+ int row = 7;
+ int col = 0;
+ for (int i = 0; i < words[0].length(); i++) {
+ char c = words[0].charAt(i);
+ switch (c) {
+ case '1': col += 1; break;
+ case '2': col += 2; break;
+ case '3': col += 3; break;
+ case '4': col += 4; break;
+ case '5': col += 5; break;
+ case '6': col += 6; break;
+ case '7': col += 7; break;
+ case '8': col += 8; break;
+ case '/': row--; col = 0; break;
+ case 'P': safeSetPiece(pos, col, row, Piece.WPAWN); col++; break;
+ case 'N': safeSetPiece(pos, col, row, Piece.WKNIGHT); col++; break;
+ case 'B': safeSetPiece(pos, col, row, Piece.WBISHOP); col++; break;
+ case 'R': safeSetPiece(pos, col, row, Piece.WROOK); col++; break;
+ case 'Q': safeSetPiece(pos, col, row, Piece.WQUEEN); col++; break;
+ case 'K': safeSetPiece(pos, col, row, Piece.WKING); col++; break;
+ case 'p': safeSetPiece(pos, col, row, Piece.BPAWN); col++; break;
+ case 'n': safeSetPiece(pos, col, row, Piece.BKNIGHT); col++; break;
+ case 'b': safeSetPiece(pos, col, row, Piece.BBISHOP); col++; break;
+ case 'r': safeSetPiece(pos, col, row, Piece.BROOK); col++; break;
+ case 'q': safeSetPiece(pos, col, row, Piece.BQUEEN); col++; break;
+ case 'k': safeSetPiece(pos, col, row, Piece.BKING); col++; break;
+ default: throw new ChessParseError("Invalid piece");
+ }
+ }
+ if (words[1].length() == 0) {
+ throw new ChessParseError("Invalid side");
+ }
+ pos.setWhiteMove(words[1].charAt(0) == 'w');
+
+ // Castling rights
+ int castleMask = 0;
+ if (words.length > 2) {
+ for (int i = 0; i < words[2].length(); i++) {
+ char c = words[2].charAt(i);
+ switch (c) {
+ case 'K':
+ castleMask |= (1 << Position.H1_CASTLE);
+ break;
+ case 'Q':
+ castleMask |= (1 << Position.A1_CASTLE);
+ break;
+ case 'k':
+ castleMask |= (1 << Position.H8_CASTLE);
+ break;
+ case 'q':
+ castleMask |= (1 << Position.A8_CASTLE);
+ break;
+ case '-':
+ break;
+ default:
+ throw new ChessParseError("Invalid castling flags");
+ }
+ }
+ }
+ pos.setCastleMask(castleMask);
+
+ if (words.length > 3) {
+ // En passant target square
+ String epString = words[3];
+ if (!epString.equals("-")) {
+ if (epString.length() < 2) {
+ throw new ChessParseError("Invalid en passant square");
+ }
+ pos.setEpSquare(getSquare(epString));
+ }
+ }
+
+ try {
+ if (words.length > 4) {
+ pos.halfMoveClock = Integer.parseInt(words[4]);
+ }
+ if (words.length > 5) {
+ pos.fullMoveCounter = Integer.parseInt(words[5]);
+ }
+ } catch (NumberFormatException nfe) {
+ // Ignore errors here, since the fields are optional
+ }
+
+ // Each side must have exactly one king
+ int wKings = 0;
+ int bKings = 0;
+ for (int x = 0; x < 8; x++) {
+ for (int y = 0; y < 8; y++) {
+ int p = pos.getPiece(Position.getSquare(x, y));
+ if (p == Piece.WKING) {
+ wKings++;
+ } else if (p == Piece.BKING) {
+ bKings++;
+ }
+ }
+ }
+ if (wKings != 1) {
+ throw new ChessParseError("White must have exactly one king");
+ }
+ if (bKings != 1) {
+ throw new ChessParseError("Black must have exactly one king");
+ }
+
+ // Make sure king can not be captured
+ Position pos2 = new Position(pos);
+ pos2.setWhiteMove(!pos.whiteMove);
+ if (MoveGen.inCheck(pos2)) {
+ throw new ChessParseError("King capture possible");
+ }
+
+ fixupEPSquare(pos);
+
+ return pos;
+ }
+
+ /** Remove pseudo-legal EP square if it is not legal, ie would leave king in check. */
+ public static final void fixupEPSquare(Position pos) {
+ int epSquare = pos.getEpSquare();
+ if (epSquare >= 0) {
+ MoveGen.MoveList moves = MoveGen.instance.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ boolean epValid = false;
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ if (m.to == epSquare) {
+ if (pos.getPiece(m.from) == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) {
+ epValid = true;
+ break;
+ }
+ }
+ }
+ if (!epValid) {
+ pos.setEpSquare(-1);
+ }
+ }
+ }
+
+ private static final void safeSetPiece(Position pos, int col, int row, int p) throws ChessParseError {
+ if (row < 0) throw new ChessParseError("Too many rows");
+ if (col > 7) throw new ChessParseError("Too many columns");
+ if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) {
+ if ((row == 0) || (row == 7))
+ throw new ChessParseError("Pawn on first/last rank");
+ }
+ pos.setPiece(Position.getSquare(col, row), p);
+ }
+
+ /** Return a FEN string corresponding to a chess Position object. */
+ public static final String toFEN(Position pos) {
+ StringBuilder ret = new StringBuilder();
+ // Piece placement
+ for (int r = 7; r >=0; r--) {
+ int numEmpty = 0;
+ for (int c = 0; c < 8; c++) {
+ int p = pos.getPiece(Position.getSquare(c, r));
+ if (p == Piece.EMPTY) {
+ numEmpty++;
+ } else {
+ if (numEmpty > 0) {
+ ret.append(numEmpty);
+ numEmpty = 0;
+ }
+ switch (p) {
+ case Piece.WKING: ret.append('K'); break;
+ case Piece.WQUEEN: ret.append('Q'); break;
+ case Piece.WROOK: ret.append('R'); break;
+ case Piece.WBISHOP: ret.append('B'); break;
+ case Piece.WKNIGHT: ret.append('N'); break;
+ case Piece.WPAWN: ret.append('P'); break;
+ case Piece.BKING: ret.append('k'); break;
+ case Piece.BQUEEN: ret.append('q'); break;
+ case Piece.BROOK: ret.append('r'); break;
+ case Piece.BBISHOP: ret.append('b'); break;
+ case Piece.BKNIGHT: ret.append('n'); break;
+ case Piece.BPAWN: ret.append('p'); break;
+ default: throw new RuntimeException();
+ }
+ }
+ }
+ if (numEmpty > 0) {
+ ret.append(numEmpty);
+ }
+ if (r > 0) {
+ ret.append('/');
+ }
+ }
+ ret.append(pos.whiteMove ? " w " : " b ");
+
+ // Castling rights
+ boolean anyCastle = false;
+ if (pos.h1Castle()) {
+ ret.append('K');
+ anyCastle = true;
+ }
+ if (pos.a1Castle()) {
+ ret.append('Q');
+ anyCastle = true;
+ }
+ if (pos.h8Castle()) {
+ ret.append('k');
+ anyCastle = true;
+ }
+ if (pos.a8Castle()) {
+ ret.append('q');
+ anyCastle = true;
+ }
+ if (!anyCastle) {
+ ret.append('-');
+ }
+
+ // En passant target square
+ {
+ ret.append(' ');
+ if (pos.getEpSquare() >= 0) {
+ int x = Position.getX(pos.getEpSquare());
+ int y = Position.getY(pos.getEpSquare());
+ ret.append((char)(x + 'a'));
+ ret.append((char)(y + '1'));
+ } else {
+ ret.append('-');
+ }
+ }
+
+ // Move counters
+ ret.append(' ');
+ ret.append(pos.halfMoveClock);
+ ret.append(' ');
+ ret.append(pos.fullMoveCounter);
+
+ return ret.toString();
+ }
+
+ /**
+ * Convert a chess move to human readable form.
+ * @param pos The chess position.
+ * @param move The executed move.
+ * @param longForm If true, use long notation, eg Ng1-f3.
+ * Otherwise, use short notation, eg Nf3
+ */
+ public static final String moveToString(Position pos, Move move, boolean longForm) {
+ MoveGen.MoveList moves = MoveGen.instance.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ return moveToString(pos, move, longForm, moves);
+ }
+ private static final String moveToString(Position pos, Move move, boolean longForm, MoveGen.MoveList moves) {
+ StringBuilder ret = new StringBuilder();
+ int wKingOrigPos = Position.getSquare(4, 0);
+ int bKingOrigPos = Position.getSquare(4, 7);
+ if (move.from == wKingOrigPos && pos.getPiece(wKingOrigPos) == Piece.WKING) {
+ // Check white castle
+ if (move.to == Position.getSquare(6, 0)) {
+ ret.append("O-O");
+ } else if (move.to == Position.getSquare(2, 0)) {
+ ret.append("O-O-O");
+ }
+ } else if (move.from == bKingOrigPos && pos.getPiece(bKingOrigPos) == Piece.BKING) {
+ // Check white castle
+ if (move.to == Position.getSquare(6, 7)) {
+ ret.append("O-O");
+ } else if (move.to == Position.getSquare(2, 7)) {
+ ret.append("O-O-O");
+ }
+ }
+ if (ret.length() == 0) {
+ int p = pos.getPiece(move.from);
+ ret.append(pieceToChar(p));
+ int x1 = Position.getX(move.from);
+ int y1 = Position.getY(move.from);
+ int x2 = Position.getX(move.to);
+ int y2 = Position.getY(move.to);
+ if (longForm) {
+ ret.append((char)(x1 + 'a'));
+ ret.append((char) (y1 + '1'));
+ ret.append(isCapture(pos, move) ? 'x' : '-');
+ } else {
+ if (p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) {
+ if (isCapture(pos, move)) {
+ ret.append((char) (x1 + 'a'));
+ }
+ } else {
+ int numSameTarget = 0;
+ int numSameFile = 0;
+ int numSameRow = 0;
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ if (m == null)
+ break;
+ if ((pos.getPiece(m.from) == p) && (m.to == move.to)) {
+ numSameTarget++;
+ if (Position.getX(m.from) == x1)
+ numSameFile++;
+ if (Position.getY(m.from) == y1)
+ numSameRow++;
+ }
+ }
+ if (numSameTarget < 2) {
+ // No file/row info needed
+ } else if (numSameFile < 2) {
+ ret.append((char) (x1 + 'a')); // Only file info needed
+ } else if (numSameRow < 2) {
+ ret.append((char) (y1 + '1')); // Only row info needed
+ } else {
+ ret.append((char) (x1 + 'a')); // File and row info needed
+ ret.append((char) (y1 + '1'));
+ }
+ }
+ if (isCapture(pos, move)) {
+ ret.append('x');
+ }
+ }
+ ret.append((char) (x2 + 'a'));
+ ret.append((char) (y2 + '1'));
+ if (move.promoteTo != Piece.EMPTY) {
+ ret.append(pieceToChar(move.promoteTo));
+ }
+ }
+ UndoInfo ui = new UndoInfo();
+ if (MoveGen.givesCheck(pos, move)) {
+ pos.makeMove(move, ui);
+ MoveGen.MoveList nextMoves = MoveGen.instance.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, nextMoves);
+ if (nextMoves.size == 0) {
+ ret.append('#');
+ } else {
+ ret.append('+');
+ }
+ pos.unMakeMove(move, ui);
+ }
+
+ return ret.toString();
+ }
+
+ /** Convert a move object to UCI string format. */
+ public static final String moveToUCIString(Move m) {
+ String ret = squareToString(m.from);
+ ret += squareToString(m.to);
+ switch (m.promoteTo) {
+ case Piece.WQUEEN:
+ case Piece.BQUEEN:
+ ret += "q";
+ break;
+ case Piece.WROOK:
+ case Piece.BROOK:
+ ret += "r";
+ break;
+ case Piece.WBISHOP:
+ case Piece.BBISHOP:
+ ret += "b";
+ break;
+ case Piece.WKNIGHT:
+ case Piece.BKNIGHT:
+ ret += "n";
+ break;
+ default:
+ break;
+ }
+ return ret;
+ }
+
+ /**
+ * Convert a string to a Move object.
+ * @return A move object, or null if move has invalid syntax
+ */
+ public static final Move uciStringToMove(String move) {
+ Move m = null;
+ if ((move.length() < 4) || (move.length() > 5))
+ return m;
+ int fromSq = TextIO.getSquare(move.substring(0, 2));
+ int toSq = TextIO.getSquare(move.substring(2, 4));
+ if ((fromSq < 0) || (toSq < 0)) {
+ return m;
+ }
+ char prom = ' ';
+ boolean white = true;
+ if (move.length() == 5) {
+ prom = move.charAt(4);
+ if (Position.getY(toSq) == 7) {
+ white = true;
+ } else if (Position.getY(toSq) == 0) {
+ white = false;
+ } else {
+ return m;
+ }
+ }
+ int promoteTo;
+ switch (prom) {
+ case ' ':
+ promoteTo = Piece.EMPTY;
+ break;
+ case 'q':
+ promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN;
+ break;
+ case 'r':
+ promoteTo = white ? Piece.WROOK : Piece.BROOK;
+ break;
+ case 'b':
+ promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP;
+ break;
+ case 'n':
+ promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT;
+ break;
+ default:
+ return m;
+ }
+ m = new Move(fromSq, toSq, promoteTo);
+ return m;
+ }
+
+ private static final boolean isCapture(Position pos, Move move) {
+ if (pos.getPiece(move.to) == Piece.EMPTY) {
+ int p = pos.getPiece(move.from);
+ if ((p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) && (move.to == pos.getEpSquare())) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Convert a chess move string to a Move object.
+ * Any prefix of the string representation of a valid move counts as a legal move string,
+ * as long as the string only matches one valid move.
+ */
+ public static final Move stringToMove(Position pos, String strMove) {
+ strMove = strMove.replaceAll("=", "");
+ Move move = null;
+ if (strMove.length() == 0)
+ return move;
+ MoveGen.MoveList moves = MoveGen.instance.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ {
+ char lastChar = strMove.charAt(strMove.length() - 1);
+ if ((lastChar == '#') || (lastChar == '+')) {
+ MoveGen.MoveList subMoves = new MoveGen.MoveList();
+ int len = 0;
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ String str1 = TextIO.moveToString(pos, m, true, moves);
+ if (str1.charAt(str1.length() - 1) == lastChar) {
+ subMoves.m[len++] = m;
+ }
+ }
+ subMoves.size = len;
+ moves = subMoves;
+ strMove = normalizeMoveString(strMove);
+ }
+ }
+
+ for (int i = 0; i < 2; i++) {
+ // Search for full match
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ String str1 = normalizeMoveString(TextIO.moveToString(pos, m, true, moves));
+ String str2 = normalizeMoveString(TextIO.moveToString(pos, m, false, moves));
+ if (i == 0) {
+ if (strMove.equals(str1) || strMove.equals(str2)) {
+ return m;
+ }
+ } else {
+ if (strMove.toLowerCase().equals(str1.toLowerCase()) ||
+ strMove.toLowerCase().equals(str2.toLowerCase())) {
+ return m;
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < 2; i++) {
+ // Search for unique substring match
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ String str1 = normalizeMoveString(TextIO.moveToString(pos, m, true));
+ String str2 = normalizeMoveString(TextIO.moveToString(pos, m, false));
+ boolean match;
+ if (i == 0) {
+ match = (str1.startsWith(strMove) || str2.startsWith(strMove));
+ } else {
+ match = (str1.toLowerCase().startsWith(strMove.toLowerCase()) ||
+ str2.toLowerCase().startsWith(strMove.toLowerCase()));
+ }
+ if (match) {
+ if (move != null) {
+ return null; // More than one match, not ok
+ } else {
+ move = m;
+ }
+ }
+ }
+ if (move != null)
+ return move;
+ }
+ return move;
+ }
+
+ /**
+ * Convert a string, such as "e4" to a square number.
+ * @return The square number, or -1 if not a legal square.
+ */
+ public static final int getSquare(String s) {
+ int x = s.charAt(0) - 'a';
+ int y = s.charAt(1) - '1';
+ if ((x < 0) || (x > 7) || (y < 0) || (y > 7))
+ return -1;
+ return Position.getSquare(x, y);
+ }
+
+ /**
+ * Convert a square number to a string, such as "e4".
+ */
+ public static final String squareToString(int square) {
+ StringBuilder ret = new StringBuilder();
+ int x = Position.getX(square);
+ int y = Position.getY(square);
+ ret.append((char) (x + 'a'));
+ ret.append((char) (y + '1'));
+ return ret.toString();
+ }
+
+ /**
+ * Create an ascii representation of a position.
+ */
+ public static final String asciiBoard(Position pos) {
+ StringBuilder ret = new StringBuilder(400);
+ String nl = String.format("%n");
+ ret.append(" +----+----+----+----+----+----+----+----+"); ret.append(nl);
+ for (int y = 7; y >= 0; y--) {
+ ret.append(" |");
+ for (int x = 0; x < 8; x++) {
+ ret.append(' ');
+ int p = pos.getPiece(Position.getSquare(x, y));
+ if (p == Piece.EMPTY) {
+ boolean dark = Position.darkSquare(x, y);
+ ret.append(dark ? ".. |" : " |");
+ } else {
+ ret.append(Piece.isWhite(p) ? ' ' : '*');
+ String pieceName = pieceToChar(p);
+ if (pieceName.length() == 0)
+ pieceName = "P";
+ ret.append(pieceName);
+ ret.append(" |");
+ }
+ }
+ ret.append(nl);
+ ret.append(" +----+----+----+----+----+----+----+----+");
+ ret.append(nl);
+ }
+ return ret.toString();
+ }
+
+ /**
+ * Convert move string to lower case and remove special check/mate symbols.
+ */
+ private static final String normalizeMoveString(String str) {
+ if (str.length() > 0) {
+ char lastChar = str.charAt(str.length() - 1);
+ if ((lastChar == '#') || (lastChar == '+')) {
+ str = str.substring(0, str.length() - 1);
+ }
+ }
+ return str;
+ }
+
+ private final static String pieceToChar(int p) {
+ switch (p) {
+ case Piece.WQUEEN: case Piece.BQUEEN: return "Q";
+ case Piece.WROOK: case Piece.BROOK: return "R";
+ case Piece.WBISHOP: case Piece.BBISHOP: return "B";
+ case Piece.WKNIGHT: case Piece.BKNIGHT: return "N";
+ case Piece.WKING: case Piece.BKING: return "K";
+ }
+ return "";
+ }
+}
diff --git a/CuckooChessEngine/src/chess/TranspositionTable.java b/CuckooChessEngine/src/chess/TranspositionTable.java
new file mode 100644
index 0000000..a2f384c
--- /dev/null
+++ b/CuckooChessEngine/src/chess/TranspositionTable.java
@@ -0,0 +1,343 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author petero
+ */
+public class TranspositionTable {
+ static final public class TTEntry {
+ long key; // Zobrist hash key
+ private short move; // from + (to<<6) + (promote<<12)
+ private short score; // Score from search
+ private short depthSlot; // Search depth (bit 0-14) and hash slot (bit 15).
+ byte generation; // Increase when OTB position changes
+ public byte type; // exact score, lower bound, upper bound
+ short evalScore; // Score from static evaluation
+ // FIXME!!! Test storing both upper and lower bound in each hash entry.
+
+ static public final int T_EXACT = 0; // Exact score
+ static public final int T_GE = 1; // True score >= this.score
+ static public final int T_LE = 2; // True score <= this.score
+ static public final int T_EMPTY = 3; // Empty hash slot
+
+ /** Return true if this object is more valuable than the other, false otherwise. */
+ public final boolean betterThan(TTEntry other, int currGen) {
+ if ((generation == currGen) != (other.generation == currGen)) {
+ return generation == currGen; // Old entries are less valuable
+ }
+ if ((type == T_EXACT) != (other.type == T_EXACT)) {
+ return type == T_EXACT; // Exact score more valuable than lower/upper bound
+ }
+ if (getDepth() != other.getDepth()) {
+ return getDepth() > other.getDepth(); // Larger depth is more valuable
+ }
+ return false; // Otherwise, pretty much equally valuable
+ }
+
+ /** Return true if entry is good enough to spend extra time trying to avoid overwriting it. */
+ public final boolean valuable(int currGen) {
+ if (generation != currGen)
+ return false;
+ return (type == T_EXACT) || (getDepth() > 3 * Search.plyScale);
+ }
+
+ public final void getMove(Move m) {
+ m.from = move & 63;
+ m.to = (move >> 6) & 63;
+ m.promoteTo = (move >> 12) & 15;
+ }
+ public final void setMove(Move move) {
+ this.move = (short)(move.from + (move.to << 6) + (move.promoteTo << 12));
+ }
+
+ /** Get the score from the hash entry, and convert from "mate in x" to "mate at ply". */
+ public final int getScore(int ply) {
+ int sc = score;
+ if (sc > Search.MATE0 - 1000) {
+ sc -= ply;
+ } else if (sc < -(Search.MATE0 - 1000)) {
+ sc += ply;
+ }
+ return sc;
+ }
+
+ /** Convert score from "mate at ply" to "mate in x", and store in hash entry. */
+ public final void setScore(int score, int ply) {
+ if (score > Search.MATE0 - 1000) {
+ score += ply;
+ } else if (score < -(Search.MATE0 - 1000)) {
+ score -= ply;
+ }
+ this.score = (short)score;
+ }
+
+ /** Get depth from the hash entry. */
+ public final int getDepth() {
+ return depthSlot & 0x7fff;
+ }
+
+ /** Set depth. */
+ public final void setDepth(int d) {
+ depthSlot &= 0x8000;
+ depthSlot |= ((short)d) & 0x7fff;
+ }
+
+ final int getHashSlot() {
+ return depthSlot >>> 15;
+ }
+
+ public final void setHashSlot(int s) {
+ depthSlot &= 0x7fff;
+ depthSlot |= (s << 15);
+ }
+ }
+ TTEntry[] table;
+ TTEntry emptySlot;
+ byte generation;
+
+ /** Constructor. Creates an empty transposition table with numEntries slots. */
+ public TranspositionTable(int log2Size) {
+ final int numEntries = (1 << log2Size);
+ table = new TTEntry[numEntries];
+ for (int i = 0; i < numEntries; i++) {
+ TTEntry ent = new TTEntry();
+ ent.key = 0;
+ ent.depthSlot = 0;
+ ent.type = TTEntry.T_EMPTY;
+ table[i] = ent;
+ }
+ emptySlot = new TTEntry();
+ emptySlot.type = TTEntry.T_EMPTY;
+ generation = 0;
+ }
+
+ public final void insert(long key, Move sm, int type, int ply, int depth, int evalScore) {
+ if (depth < 0) depth = 0;
+ int idx0 = h0(key);
+ int idx1 = h1(key);
+ TTEntry ent = table[idx0];
+ byte hashSlot = 0;
+ if (ent.key != key) {
+ ent = table[idx1];
+ hashSlot = 1;
+ }
+ if (ent.key != key) {
+ if (table[idx1].betterThan(table[idx0], generation)) {
+ ent = table[idx0];
+ hashSlot = 0;
+ }
+ if (ent.valuable(generation)) {
+ int altEntIdx = (ent.getHashSlot() == 0) ? h1(ent.key) : h0(ent.key);
+ if (ent.betterThan(table[altEntIdx], generation)) {
+ TTEntry altEnt = table[altEntIdx];
+ altEnt.key = ent.key;
+ altEnt.move = ent.move;
+ altEnt.score = ent.score;
+ altEnt.depthSlot = ent.depthSlot;
+ altEnt.generation = (byte)ent.generation;
+ altEnt.type = ent.type;
+ altEnt.setHashSlot(1 - ent.getHashSlot());
+ altEnt.evalScore = ent.evalScore;
+ }
+ }
+ }
+ boolean doStore = true;
+ if ((ent.key == key) && (ent.getDepth() > depth) && (ent.type == type)) {
+ if (type == TTEntry.T_EXACT) {
+ doStore = false;
+ } else if ((type == TTEntry.T_GE) && (sm.score <= ent.score)) {
+ doStore = false;
+ } else if ((type == TTEntry.T_LE) && (sm.score >= ent.score)) {
+ doStore = false;
+ }
+ }
+ if (doStore) {
+ if ((ent.key != key) || (sm.from != sm.to))
+ ent.setMove(sm);
+ ent.key = key;
+ ent.setScore(sm.score, ply);
+ ent.setDepth(depth);
+ ent.generation = (byte)generation;
+ ent.type = (byte)type;
+ ent.setHashSlot(hashSlot);
+ ent.evalScore = (short)evalScore;
+ }
+ }
+
+ /** Retrieve an entry from the hash table corresponding to "pos". */
+ public final TTEntry probe(long key) {
+ int idx0 = h0(key);
+ TTEntry ent = table[idx0];
+ if (ent.key == key) {
+ ent.generation = (byte)generation;
+ return ent;
+ }
+ int idx1 = h1(key);
+ ent = table[idx1];
+ if (ent.key == key) {
+ ent.generation = (byte)generation;
+ return ent;
+ }
+ return emptySlot;
+ }
+
+ /**
+ * Increase hash table generation. This means that subsequent inserts will be considered
+ * more valuable than the entries currently present in the hash table.
+ */
+ public final void nextGeneration() {
+ generation++;
+ }
+
+ /** Clear the transposition table. */
+ public final void clear() {
+ for (TTEntry ent : table) {
+ ent.type = TTEntry.T_EMPTY;
+ }
+ }
+
+ /**
+ * Extract a list of PV moves, starting from "rootPos" and first move "m".
+ */
+ public final ArrayList extractPVMoves(Position rootPos, Move m) {
+ Position pos = new Position(rootPos);
+ m = new Move(m);
+ ArrayList ret = new ArrayList();
+ UndoInfo ui = new UndoInfo();
+ List hashHistory = new ArrayList();
+ MoveGen moveGen = new MoveGen();
+ while (true) {
+ ret.add(m);
+ pos.makeMove(m, ui);
+ if (hashHistory.contains(pos.zobristHash())) {
+ break;
+ }
+ hashHistory.add(pos.zobristHash());
+ TTEntry ent = probe(pos.historyHash());
+ if (ent.type == TTEntry.T_EMPTY) {
+ break;
+ }
+ m = new Move(0,0,0);
+ ent.getMove(m);
+ MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ boolean contains = false;
+ for (int mi = 0; mi < moves.size; mi++)
+ if (moves.m[mi].equals(m)) {
+ contains = true;
+ break;
+ }
+ if (!contains)
+ break;
+ }
+ return ret;
+ }
+
+ /** Extract the PV starting from pos, using hash entries, both exact scores and bounds. */
+ public final String extractPV(Position pos) {
+ StringBuilder ret = new StringBuilder(100);
+ pos = new Position(pos); // To avoid modifying the input parameter
+ boolean first = true;
+ TTEntry ent = probe(pos.historyHash());
+ UndoInfo ui = new UndoInfo();
+ ArrayList hashHistory = new ArrayList();
+ boolean repetition = false;
+ MoveGen moveGen = MoveGen.instance;
+ while (ent.type != TTEntry.T_EMPTY) {
+ String type = "";
+ if (ent.type == TTEntry.T_LE) {
+ type = "<";
+ } else if (ent.type == TTEntry.T_GE) {
+ type = ">";
+ }
+ Move m = new Move(0,0,0);
+ ent.getMove(m);
+ MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ boolean contains = false;
+ for (int mi = 0; mi < moves.size; mi++)
+ if (moves.m[mi].equals(m)) {
+ contains = true;
+ break;
+ }
+ if (!contains)
+ break;
+ String moveStr = TextIO.moveToString(pos, m, false);
+ if (repetition)
+ break;
+ if (!first) {
+ ret.append(" ");
+ }
+ ret.append(type);
+ ret.append(moveStr);
+ pos.makeMove(m, ui);
+ if (hashHistory.contains(pos.zobristHash())) {
+ repetition = true;
+ }
+ hashHistory.add(pos.zobristHash());
+ ent = probe(pos.historyHash());
+ first = false;
+ }
+ return ret.toString();
+ }
+
+ /** Print hash table statistics. */
+ public final void printStats() {
+ int unused = 0;
+ int thisGen = 0;
+ List depHist = new ArrayList();
+ final int maxDepth = 20*8;
+ for (int i = 0; i < maxDepth; i++) {
+ depHist.add(0);
+ }
+ for (TTEntry ent : table) {
+ if (ent.type == TTEntry.T_EMPTY) {
+ unused++;
+ } else {
+ if (ent.generation == generation) {
+ thisGen++;
+ }
+ if (ent.getDepth() < maxDepth) {
+ depHist.set(ent.getDepth(), depHist.get(ent.getDepth()) + 1);
+ }
+ }
+ }
+ double w = 100.0 / table.length;
+ System.out.printf("Hash stats: size:%d unused:%d (%.2f%%) thisGen:%d (%.2f%%)\n",
+ table.length, unused, unused*w, thisGen, thisGen*w);
+ for (int i = 0; i < maxDepth; i++) {
+ int c = depHist.get(i);
+ if (c > 0)
+ System.out.printf("%3d %8d (%6.2f%%)\n", i, c, c*w);
+ }
+ }
+
+ private final int h0(long key) {
+ return (int)(key & (table.length - 1));
+ }
+
+ private final int h1(long key) {
+ return (int)((key >> 32) & (table.length - 1));
+ }
+}
diff --git a/CuckooChessEngine/src/chess/TreeLogger.java b/CuckooChessEngine/src/chess/TreeLogger.java
new file mode 100644
index 0000000..c36cb89
--- /dev/null
+++ b/CuckooChessEngine/src/chess/TreeLogger.java
@@ -0,0 +1,617 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import chess.TranspositionTable.TTEntry;
+
+public final class TreeLogger {
+ private byte[] entryBuffer = new byte[16];
+ private ByteBuffer bb = ByteBuffer.wrap(entryBuffer);
+
+ // Used in write mode
+ private FileOutputStream os = null;
+ private BufferedOutputStream bos = null;
+ private long nextIndex = 0;
+
+ // Used in analyze mode
+ private MappedByteBuffer mapBuf = null;
+ private FileChannel fc = null;
+ private int numEntries = 0;
+
+ private TreeLogger() {
+ }
+
+ /** Get a logger object set up for writing to a log file. */
+ public static final TreeLogger getWriter(String filename, Position pos) {
+ try {
+ TreeLogger log = new TreeLogger();
+ log.os = new FileOutputStream(filename);
+ log.bos = new BufferedOutputStream(log.os, 65536);
+ log.writeHeader(pos);
+ log.nextIndex = 0;
+ return log;
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException();
+ }
+ }
+
+ private final void writeHeader(Position pos) {
+ try {
+ byte[] fen = TextIO.toFEN(pos).getBytes();
+ bos.write((byte)(fen.length));
+ bos.write(fen);
+ byte[] pad = new byte[128-1-fen.length];
+ for (int i = 0; i < pad.length; i++)
+ pad[i] = 0;
+ bos.write(pad);
+ } catch (IOException e) {
+ throw new RuntimeException();
+ }
+ }
+
+ /** Get a logger object set up for analyzing a log file. */
+ public static final TreeLogger getAnalyzer(String filename) {
+ try {
+ TreeLogger log = new TreeLogger();
+ RandomAccessFile raf;
+ raf = new RandomAccessFile(filename, "rw");
+ log.fc = raf.getChannel();
+ long len = raf.length();
+ log.numEntries = (int) ((len - 128) / 16);
+ log.mapBuf = log.fc.map(MapMode.READ_WRITE, 0, len);
+ log.computeForwardPointers();
+ return log;
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException();
+ } catch (IOException e) {
+ throw new RuntimeException();
+ }
+ }
+
+ public final void close() {
+ try {
+ if (bos != null) bos.close();
+ if (fc != null) fc.close();
+ } catch (IOException e) {
+ }
+ }
+
+ /* This is the on-disk format. Big-endian byte-order is used.
+ * First there is one header entry. Then there is a set of start/end entries.
+ * A StartEntry can be identified by its first 4 bytes (endIndex/startIndex)
+ * being either -1 (endIndex not computed), or > the entry index.
+ *
+ * private static final class Header {
+ * byte fenLen; // Used length of fen array
+ * byte[] fen; // 126 bytes, 0-padded
+ * byte flags; // bit 0: 1 if endIndex has been computed for all StartEntries.
+ * }
+ *
+ * private static final class StartEntry {
+ * int endIndex;
+ * int parentIndex; // -1 for root node
+ * short move;
+ * short alpha;
+ * short beta;
+ * byte ply;
+ * byte depth;
+ * }
+ *
+ * private static final class EndEntry {
+ * int startIndex;
+ * short score;
+ * short scoreType;
+ * short evalScore;
+ * byte[] hashKey; // lower 6 bytes of position hash key
+ * }
+ */
+
+ // ----------------------------------------------------------------------------
+ // Functions used for tree logging
+
+ /**
+ * Log information when entering a search node.
+ * @param parentId Index of parent node.
+ * @param m Move made to go from parent node to this node
+ * @param alpha Search parameter
+ * @param beta Search parameter
+ * @param ply Search parameter
+ * @param depth Search parameter
+ * @return node index
+ */
+ final long logNodeStart(long parentIndex, Move m, int alpha, int beta, int ply, int depth) {
+ bb.putInt ( 0, (int)-1);
+ bb.putInt ( 4, (int)parentIndex);
+ bb.putShort( 8, (short)(m.from + (m.to << 6) + (m.promoteTo << 12)));
+ bb.putShort(10, (short)alpha);
+ bb.putShort(12, (short)beta);
+ bb.put (14, (byte)ply);
+ bb.put (15, (byte)depth);
+ try {
+ bos.write(bb.array());
+ } catch (IOException e) {
+ throw new RuntimeException();
+ }
+ return nextIndex++;
+ }
+
+ /**
+ * @param startIndex Pointer to corresponding start node entry.
+ * @param score Computed score for this node.
+ * @param scoreType See TranspositionTable, T_EXACT, T_GE, T_LE.
+ * @param evalScore Score returned by evaluation function at this node, if known.
+ * @return node index
+ */
+ final long logNodeEnd(long startIndex, int score, int scoreType, int evalScore, long hashKey) {
+ bb.putInt ( 0, (int)startIndex);
+ bb.putShort( 4, (short)score);
+ bb.putShort( 6, (short)scoreType);
+ bb.putLong( 8, hashKey);
+ bb.putShort( 8, (short)evalScore); // Overwrites first two byte of hashKey
+ try {
+ bos.write(bb.array());
+ } catch (IOException e) {
+ throw new RuntimeException();
+ }
+ return nextIndex++;
+ }
+
+ // ----------------------------------------------------------------------------
+ // Functions used for tree analyzing
+
+ private static final int indexToFileOffs(int index) {
+ return 128 + index * 16;
+ }
+
+ /** Compute endIndex for all StartNode entries. */
+ private final void computeForwardPointers() {
+ if ((mapBuf.get(127) & (1<<7)) != 0)
+ return;
+ System.out.printf("Computing forward pointers...\n");
+ StartEntry se = new StartEntry();
+ EndEntry ee = new EndEntry();
+ for (int i = 0; i < numEntries; i++) {
+ boolean isStart = readEntry(i, se, ee);
+ if (!isStart) {
+ int offs = indexToFileOffs(ee.startIndex);
+ mapBuf.putInt(offs, i);
+ }
+ }
+ mapBuf.put(127, (byte)(1 << 7));
+ mapBuf.force();
+ System.out.printf("Computing forward pointers... done\n");
+ }
+
+ /** Get FEN string for root node position. */
+ private final String getRootNodeFEN() {
+ int len = mapBuf.get(0);
+ byte[] fenB = new byte[len];
+ for (int i = 0; i < len; i++)
+ fenB[i] = mapBuf.get(1+i);
+ String ret = new String(fenB);
+ return ret;
+ }
+
+ static final class StartEntry {
+ int endIndex;
+ int parentIndex; // -1 for root node
+ Move move;
+ short alpha;
+ short beta;
+ byte ply;
+ byte depth;
+ }
+ static final class EndEntry {
+ int startIndex;
+ short score;
+ short scoreType;
+ short evalScore;
+ long hashKey; // Note! Upper 2 bytes are not valid (ie 0)
+ }
+
+ /** Read a start/end entry.
+ * @return True if entry was a start entry, false if it was an end entry. */
+ private final boolean readEntry(int index, StartEntry se, EndEntry ee) {
+ int offs = indexToFileOffs(index);
+ for (int i = 0; i < 16; i++)
+ bb.put(i, mapBuf.get(offs + i));
+ int otherIndex = bb.getInt(0);
+ boolean isStartEntry = (otherIndex == -1) || (otherIndex > index);
+ if (isStartEntry) {
+ se.endIndex = otherIndex;
+ se.parentIndex = bb.getInt(4);
+ int m = bb.getShort(8);
+ se.move = new Move(m & 63, (m >> 6) & 63, (m >> 12) & 15);
+ se.alpha = bb.getShort(10);
+ se.beta = bb.getShort(12);
+ se.ply = bb.get(14);
+ se.depth = bb.get(15);
+ } else {
+ ee.startIndex = otherIndex;
+ ee.score = bb.getShort(4);
+ ee.scoreType = bb.getShort(6);
+ ee.evalScore = bb.getShort(8);
+ ee.hashKey = bb.getLong(8) & 0x0000ffffffffffffL;
+ }
+ return isStartEntry;
+ }
+
+ // ----------------------------------------------------------------------------
+ // Functions used for the interactive tree browser
+
+ public static final void main(String[] args) throws IOException {
+ if (args.length != 1) {
+ System.out.printf("Usage: progname filename\n");
+ System.exit(1);
+ }
+ TreeLogger an = getAnalyzer(args[0]);
+ try {
+ Position rootPos = TextIO.readFEN(an.getRootNodeFEN());
+ an.mainLoop(rootPos);
+ } catch (ChessParseError e) {
+ throw new RuntimeException();
+ }
+ an.close();
+ }
+
+ private final void mainLoop(Position rootPos) throws IOException {
+ int currIndex = -1;
+ BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+ String prevStr = "";
+
+ boolean doPrint = true;
+ while (true) {
+ if (doPrint) {
+ ArrayList moves = getMoveSequence(currIndex);
+ for (Move m : moves)
+ System.out.printf(" %s", TextIO.moveToUCIString(m));
+ System.out.printf("\n");
+ printNodeInfo(rootPos, currIndex);
+ Position pos = getPosition(rootPos, currIndex);
+ System.out.print(TextIO.asciiBoard(pos));
+ System.out.printf("%s\n", TextIO.toFEN(pos));
+ System.out.printf("%16x\n", pos.historyHash());
+ if (currIndex >= 0) {
+ ArrayList children = findChildren(currIndex);
+ for (Integer c : children)
+ printNodeInfo(rootPos, c);
+ }
+ }
+ doPrint = true;
+ System.out.printf("Command:");
+ String cmdStr = in.readLine();
+ if (cmdStr == null)
+ return;
+ if (cmdStr.length() == 0)
+ cmdStr = prevStr;
+ if (cmdStr.startsWith("q")) {
+ return;
+ } else if (cmdStr.startsWith("?")) {
+ printHelp();
+ doPrint = false;
+ } else if (isMove(cmdStr)) {
+ ArrayList children = findChildren(currIndex);
+ String m = cmdStr;
+ StartEntry se = new StartEntry();
+ EndEntry ee = new EndEntry();
+ ArrayList found = new ArrayList();
+ for (Integer c : children) {
+ readEntries(c, se, ee);
+ if (TextIO.moveToUCIString(se.move).equals(m))
+ found.add(c);
+ }
+ if (found.size() == 0) {
+ System.out.printf("No such move\n");
+ doPrint = false;
+ } else if (found.size() > 1) {
+ System.out.printf("Ambiguous move\n");
+ for (Integer c : found)
+ printNodeInfo(rootPos, c);
+ doPrint = false;
+ } else {
+ currIndex = found.get(0);
+ }
+ } else if (cmdStr.startsWith("u")) {
+ int n = getArg(cmdStr, 1);
+ for (int i = 0; i < n; i++)
+ currIndex = findParent(currIndex);
+ } else if (cmdStr.startsWith("l")) {
+ ArrayList children = findChildren(currIndex);
+ String m = getArgStr(cmdStr, "");
+ for (Integer c : children)
+ printNodeInfo(rootPos, c, m);
+ doPrint = false;
+ } else if (cmdStr.startsWith("n")) {
+ ArrayList nodes = getNodeSequence(currIndex);
+ for (int node : nodes)
+ printNodeInfo(rootPos, node);
+ doPrint = false;
+ } else if (cmdStr.startsWith("d")) {
+ ArrayList nVec = getArgs(cmdStr, 0);
+ for (int n : nVec) {
+ ArrayList children = findChildren(currIndex);
+ if ((n >= 0) && (n < children.size())) {
+ currIndex = children.get(n);
+ } else
+ break;
+ }
+ } else if (cmdStr.startsWith("p")) {
+ ArrayList moves = getMoveSequence(currIndex);
+ for (Move m : moves)
+ System.out.printf(" %s", TextIO.moveToUCIString(m));
+ System.out.printf("\n");
+ doPrint = false;
+ } else if (cmdStr.startsWith("h")) {
+ long hashKey = getPosition(rootPos, currIndex).historyHash();
+ hashKey = getHashKey(cmdStr, hashKey);
+ ArrayList nodes = getNodeForHashKey(hashKey);
+ for (int node : nodes)
+ printNodeInfo(rootPos, node);
+ doPrint = false;
+ } else {
+ try {
+ int i = Integer.parseInt(cmdStr);
+ if ((i >= -1) && (i < numEntries))
+ currIndex = i;
+ } catch (NumberFormatException e) {
+ }
+ }
+ prevStr = cmdStr;
+ }
+ }
+
+ private final boolean isMove(String cmdStr) {
+ if (cmdStr.length() != 4)
+ return false;
+ cmdStr = cmdStr.toLowerCase();
+ for (int i = 0; i < 4; i++) {
+ int c = cmdStr.charAt(i);
+ if ((i == 0) || (i == 2)) {
+ if ((c < 'a') || (c > 'h'))
+ return false;
+ } else {
+ if ((c < '1') || (c > '8'))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Return all nodes with a given hash key. */
+ private final ArrayList getNodeForHashKey(long hashKey) {
+ hashKey &= 0x0000ffffffffffffL;
+ ArrayList ret = new ArrayList();
+ StartEntry se = new StartEntry();
+ EndEntry ee = new EndEntry();
+ for (int index = 0; index < numEntries; index++) {
+ boolean isStart = readEntry(index, se, ee);
+ if (!isStart) {
+ if (ee.hashKey == hashKey) {
+ int sIdx = ee.startIndex;
+ ret.add(sIdx);
+ }
+ }
+ }
+ Collections.sort(ret);
+ return ret;
+ }
+
+ /** Get hash key from an input string. */
+ private final long getHashKey(String s, long defKey) {
+ long key = defKey;
+ int idx = s.indexOf(' ');
+ if (idx > 0) {
+ s = s.substring(idx + 1);
+ if (s.startsWith("0x"))
+ s = s.substring(2);
+ try {
+ key = Long.parseLong(s, 16);
+ } catch (NumberFormatException e) {
+ }
+ }
+ return key;
+ }
+
+ /** Get integer parameter from an input string. */
+ private static final int getArg(String s, int defVal) {
+ try {
+ int idx = s.indexOf(' ');
+ if (idx > 0) {
+ return Integer.parseInt(s.substring(idx+1));
+ }
+ } catch (NumberFormatException e) {
+ }
+ return defVal;
+ }
+
+ /** Get a list of integer parameters from an input string. */
+ final ArrayList getArgs(String s, int defVal) {
+ ArrayList ret = new ArrayList();
+ String[] split = s.split(" ");
+ try {
+ for (int i = 1; i < split.length; i++)
+ ret.add(Integer.parseInt(split[i]));
+ } catch (NumberFormatException e) {
+ ret.clear();
+ }
+ if (ret.size() == 0)
+ ret.add(defVal);
+ return ret;
+ }
+
+ /** Get a string parameter from an input string. */
+ private static final String getArgStr(String s, String defVal) {
+ int idx = s.indexOf(' ');
+ if (idx > 0)
+ return s.substring(idx+1);
+ return defVal;
+ }
+
+ private final void printHelp() {
+ System.out.printf(" p - Print move sequence\n");
+ System.out.printf(" n - Print node info corresponding to move sequence\n");
+ System.out.printf(" l [move] - List child nodes, optionally only for one move\n");
+ System.out.printf(" d [n1 [n2...]] - Go to child \"n\"\n");
+ System.out.printf(" move - Go to child \"move\", if unique\n");
+ System.out.printf(" u [levels] - Move up\n");
+ System.out.printf(" h [key] - Find nodes with current (or given) hash key\n");
+ System.out.printf(" num - Go to node \"num\"\n");
+ System.out.printf(" q - Quit\n");
+ System.out.printf(" ? - Print this help\n");
+ }
+
+ /** Read start/end entries for a tree node. Return true if the end entry exists. */
+ private final boolean readEntries(int index, StartEntry se, EndEntry ee) {
+ boolean isStart = readEntry(index, se, ee);
+ if (isStart) {
+ int eIdx = se.endIndex;
+ if (eIdx >= 0) {
+ readEntry(eIdx, null, ee);
+ } else {
+ return false;
+ }
+ } else {
+ int sIdx = ee.startIndex;
+ readEntry(sIdx, se, null);
+ }
+ return true;
+ }
+
+ /** Find the parent node to a node. */
+ private final int findParent(int index) {
+ if (index >= 0) {
+ StartEntry se = new StartEntry();
+ EndEntry ee = new EndEntry();
+ readEntries(index, se, ee);
+ index = se.parentIndex;
+ }
+ return index;
+ }
+
+ /** Find all children of a node. */
+ private final ArrayList findChildren(int index) {
+ ArrayList ret = new ArrayList();
+ StartEntry se = new StartEntry();
+ EndEntry ee = new EndEntry();
+ int child = index + 1;
+ while ((child >= 0) && (child < numEntries)) {
+ boolean haveEE = readEntries(child, se, ee);
+ if (se.parentIndex == index)
+ ret.add(child);
+ if (!haveEE)
+ break;
+ if (child != ee.startIndex)
+ break; // two end entries in a row, no more children
+// if (se.parentIndex != index)
+// break;
+ child = se.endIndex + 1;
+ }
+ return ret;
+ }
+
+ /** Get node position in parents children list. */
+ private final int getChildNo(int index) {
+ ArrayList childs = findChildren(findParent(index));
+ for (int i = 0; i < childs.size(); i++)
+ if (childs.get(i) == index)
+ return i;
+ return -1;
+ }
+
+ /** Get list of nodes from root position to a node. */
+ private final ArrayList getNodeSequence(int index) {
+ ArrayList nodes = new ArrayList();
+ nodes.add(index);
+ while (index >= 0) {
+ index = findParent(index);
+ nodes.add(index);
+ }
+ Collections.reverse(nodes);
+ return nodes;
+ }
+
+ /** Find list of moves from root node to a node. */
+ private final ArrayList getMoveSequence(int index) {
+ ArrayList moves = new ArrayList();
+ StartEntry se = new StartEntry();
+ EndEntry ee = new EndEntry();
+ while (index >= 0) {
+ readEntries(index, se, ee);
+ moves.add(se.move);
+ index = findParent(index);
+ }
+ Collections.reverse(moves);
+ return moves;
+ }
+
+ /** Find the position corresponding to a node. */
+ private final Position getPosition(Position rootPos, int index) {
+ ArrayList moves = getMoveSequence(index);
+ Position ret = new Position(rootPos);
+ UndoInfo ui = new UndoInfo();
+ for (Move m : moves)
+ ret.makeMove(m, ui);
+ return ret;
+ }
+
+ private final void printNodeInfo(Position rootPos, int index) {
+ printNodeInfo(rootPos, index, "");
+ }
+ private final void printNodeInfo(Position rootPos, int index, String filterMove) {
+ if (index < 0) { // Root node
+ System.out.printf("%8d entries:%d\n", index, numEntries);
+ } else {
+ StartEntry se = new StartEntry();
+ EndEntry ee = new EndEntry();
+ boolean haveEE = readEntries(index, se, ee);
+ String m = TextIO.moveToUCIString(se.move);
+ if ((filterMove.length() > 0) && !m.equals(filterMove))
+ return;
+ System.out.printf("%3d %8d %s a:%6d b:%6d p:%2d d:%2d", getChildNo(index), index,
+ m, se.alpha, se.beta, se.ply, se.depth);
+ if (haveEE) {
+ int subTreeNodes = (se.endIndex - ee.startIndex - 1) / 2;
+ String type;
+ switch (ee.scoreType) {
+ case TTEntry.T_EXACT: type = "= "; break;
+ case TTEntry.T_GE : type = ">="; break;
+ case TTEntry.T_LE : type = "<="; break;
+ default : type = " "; break;
+ }
+ System.out.printf(" s:%s%6d e:%6d sub:%d", type, ee.score, ee.evalScore,
+ subTreeNodes);
+ }
+ System.out.printf("\n");
+ }
+ }
+}
diff --git a/CuckooChessEngine/src/chess/TwoReturnValues.java b/CuckooChessEngine/src/chess/TwoReturnValues.java
new file mode 100644
index 0000000..cb76a0b
--- /dev/null
+++ b/CuckooChessEngine/src/chess/TwoReturnValues.java
@@ -0,0 +1,33 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+/**
+ * A small helper class that makes it possible to return two values from a function.
+ * @author petero
+ */
+public final class TwoReturnValues {
+ public final T1 first;
+ public final T2 second;
+
+ TwoReturnValues(T1 first, T2 second) {
+ this.first = first;
+ this.second = second;
+ }
+}
diff --git a/CuckooChessEngine/src/chess/UndoInfo.java b/CuckooChessEngine/src/chess/UndoInfo.java
new file mode 100644
index 0000000..f44744d
--- /dev/null
+++ b/CuckooChessEngine/src/chess/UndoInfo.java
@@ -0,0 +1,31 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+/**
+ * Contains enough information to undo a previous move.
+ * Set by makeMove(). Used by unMakeMove().
+ * @author petero
+ */
+public class UndoInfo {
+ int capturedPiece;
+ int castleMask;
+ int epSquare;
+ int halfMoveClock;
+}
diff --git a/CuckooChessEngine/src/guibase/ChessController.java b/CuckooChessEngine/src/guibase/ChessController.java
new file mode 100644
index 0000000..5748560
--- /dev/null
+++ b/CuckooChessEngine/src/guibase/ChessController.java
@@ -0,0 +1,549 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package guibase;
+
+import chess.ChessParseError;
+import chess.ComputerPlayer;
+import chess.Game;
+import chess.HumanPlayer;
+import chess.Move;
+import chess.MoveGen;
+import chess.Piece;
+import chess.Player;
+import chess.Position;
+import chess.Search;
+import chess.TextIO;
+import chess.UndoInfo;
+import chess.Game.GameState;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Scanner;
+
+/**
+ * The glue between the chess engine and the GUI.
+ * @author petero
+ */
+public class ChessController {
+ Player humanPlayer;
+ ComputerPlayer computerPlayer;
+ Game game;
+ GUIInterface gui;
+ boolean humanIsWhite;
+ Thread computerThread;
+ int threadStack; // Thread stack size, or zero to use OS default
+
+ // Search statistics
+ String thinkingPV;
+
+ class SearchListener implements Search.Listener {
+ int currDepth = 0;
+ int currMoveNr = 0;
+ String currMove = "";
+ int currNodes = 0;
+ int currNps = 0;
+ int currTime = 0;
+
+ int pvDepth = 0;
+ int pvScore = 0;
+ boolean pvIsMate = false;
+ boolean pvUpperBound = false;
+ boolean pvLowerBound = false;
+ String pvStr = "";
+
+ private void setSearchInfo() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(String.format("%n[%d] ", pvDepth));
+ if (pvUpperBound) {
+ buf.append("<=");
+ } else if (pvLowerBound) {
+ buf.append(">=");
+ }
+ if (pvIsMate) {
+ buf.append(String.format("m%d", pvScore));
+ } else {
+ buf.append(String.format("%.2f", pvScore / 100.0));
+ }
+ buf.append(pvStr);
+ buf.append(String.format("%n"));
+ buf.append(String.format("d:%d %d:%s t:%.2f n:%d nps:%d", currDepth,
+ currMoveNr, currMove, currTime / 1000.0, currNodes, currNps));
+ final String newPV = buf.toString();
+ gui.runOnUIThread(new Runnable() {
+ public void run() {
+ thinkingPV = newPV;
+ setThinkingPV();
+ }
+ });
+ }
+
+ public void notifyDepth(int depth) {
+ currDepth = depth;
+ setSearchInfo();
+ }
+
+ public void notifyCurrMove(Move m, int moveNr) {
+ currMove = TextIO.moveToString(new Position(game.pos), m, false);
+ currMoveNr = moveNr;
+ setSearchInfo();
+ }
+
+ public void notifyPV(int depth, int score, int time, int nodes, int nps, boolean isMate,
+ boolean upperBound, boolean lowerBound, ArrayList pv) {
+ pvDepth = depth;
+ pvScore = score;
+ currTime = time;
+ currNodes = nodes;
+ currNps = nps;
+ pvIsMate = isMate;
+ pvUpperBound = upperBound;
+ pvLowerBound = lowerBound;
+
+ StringBuilder buf = new StringBuilder();
+ Position pos = new Position(game.pos);
+ UndoInfo ui = new UndoInfo();
+ for (Move m : pv) {
+ buf.append(String.format(" %s", TextIO.moveToString(pos, m, false)));
+ pos.makeMove(m, ui);
+ }
+ pvStr = buf.toString();
+ setSearchInfo();
+ }
+
+ public void notifyStats(int nodes, int nps, int time) {
+ currNodes = nodes;
+ currNps = nps;
+ currTime = time;
+ setSearchInfo();
+ }
+ }
+ SearchListener listener;
+
+ public ChessController(GUIInterface gui) {
+ this.gui = gui;
+ listener = new SearchListener();
+ thinkingPV = "";
+ threadStack = 0;
+ }
+
+ public void setThreadStackSize(int size) {
+ threadStack = size;
+ }
+
+ public final void newGame(boolean humanIsWhite, int ttLogSize, boolean verbose) {
+ stopComputerThinking();
+ this.humanIsWhite = humanIsWhite;
+ humanPlayer = new HumanPlayer();
+ computerPlayer = new ComputerPlayer();
+ computerPlayer.verbose = verbose;
+ computerPlayer.setTTLogSize(ttLogSize);
+ computerPlayer.setListener(listener);
+ if (humanIsWhite) {
+ game = new Game(humanPlayer, computerPlayer);
+ } else {
+ game = new Game(computerPlayer, humanPlayer);
+ }
+ }
+ public final void startGame() {
+ gui.setSelection(-1);
+ updateGUI();
+ startComputerThinking();
+ }
+
+ public final void setPosHistory(List posHistStr) {
+ try {
+ String fen = posHistStr.get(0);
+ Position pos = TextIO.readFEN(fen);
+ game.processString("new");
+ game.pos = pos;
+ String[] strArr = posHistStr.get(1).split(" ");
+ final int arrLen = strArr.length;
+ for (int i = 0; i < arrLen; i++) {
+ game.processString(strArr[i]);
+ }
+ int numUndo = Integer.parseInt(posHistStr.get(2));
+ for (int i = 0; i < numUndo; i++) {
+ game.processString("undo");
+ }
+ } catch (ChessParseError e) {
+ // Just ignore invalid positions
+ }
+ }
+
+ public final List getPosHistory() {
+ return game.getPosHistory();
+ }
+
+ public String getFEN() {
+ return TextIO.toFEN(game.pos);
+ }
+
+ /** Convert current game to PGN format. */
+ public String getPGN() {
+ StringBuilder pgn = new StringBuilder();
+ List posHist = getPosHistory();
+ String fen = posHist.get(0);
+ String moves = game.getMoveListString(true);
+ if (game.getGameState() == GameState.ALIVE)
+ moves += " *";
+ int year, month, day;
+ {
+ Calendar now = GregorianCalendar.getInstance();
+ year = now.get(Calendar.YEAR);
+ month = now.get(Calendar.MONTH) + 1;
+ day = now.get(Calendar.DAY_OF_MONTH);
+ }
+ pgn.append(String.format("[Date \"%04d.%02d.%02d\"]%n", year, month, day));
+ String white = "Player";
+ String black = ComputerPlayer.engineName;
+ if (!humanIsWhite) {
+ String tmp = white; white = black; black = tmp;
+ }
+ pgn.append(String.format("[White \"%s\"]%n", white));
+ pgn.append(String.format("[Black \"%s\"]%n", black));
+ pgn.append(String.format("[Result \"%s\"]%n", game.getPGNResultString()));
+ if (!fen.equals(TextIO.startPosFEN)) {
+ pgn.append(String.format("[FEN \"%s\"]%n", fen));
+ pgn.append("[SetUp \"1\"]\n");
+ }
+ pgn.append("\n");
+ String[] strArr = moves.split(" ");
+ int currLineLength = 0;
+ final int arrLen = strArr.length;
+ for (int i = 0; i < arrLen; i++) {
+ String move = strArr[i].trim();
+ int moveLen = move.length();
+ if (moveLen > 0) {
+ if (currLineLength + 1 + moveLen >= 80) {
+ pgn.append("\n");
+ pgn.append(move);
+ currLineLength = moveLen;
+ } else {
+ if (currLineLength > 0) {
+ pgn.append(" ");
+ currLineLength++;
+ }
+ pgn.append(move);
+ currLineLength += moveLen;
+ }
+ }
+ }
+ pgn.append("\n\n");
+ return pgn.toString();
+ }
+
+ public void setPGN(String pgn) throws ChessParseError {
+ // First pass, remove comments
+ {
+ StringBuilder out = new StringBuilder();
+ Scanner sc = new Scanner(pgn);
+ sc.useDelimiter("");
+ while (sc.hasNext()) {
+ String c = sc.next();
+ if (c.equals("{")) {
+ sc.skip("[^}]*}");
+ } else if (c.equals(";")) {
+ sc.skip("[^\n]*\n");
+ } else {
+ out.append(c);
+ }
+ }
+ pgn = out.toString();
+ }
+
+ // Parse tag section
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ Scanner sc = new Scanner(pgn);
+ sc.useDelimiter("\\s+");
+ while (sc.hasNext("\\[.*")) {
+ String tagName = sc.next();
+ if (tagName.length() > 1) {
+ tagName = tagName.substring(1);
+ } else {
+ tagName = sc.next();
+ }
+ String tagValue = sc.findWithinHorizon(".*\\]", 0);
+ tagValue = tagValue.trim();
+ if (tagValue.charAt(0) == '"')
+ tagValue = tagValue.substring(1);
+ if (tagValue.charAt(tagValue.length()-1) == ']')
+ tagValue = tagValue.substring(0, tagValue.length() - 1);
+ if (tagValue.charAt(tagValue.length()-1) == '"')
+ tagValue = tagValue.substring(0, tagValue.length() - 1);
+ if (tagName.equals("FEN")) {
+ pos = TextIO.readFEN(tagValue);
+ }
+ }
+ game.processString("new");
+ game.pos = pos;
+
+ // Handle (ignore) recursive annotation variations
+ {
+ StringBuilder out = new StringBuilder();
+ sc.useDelimiter("");
+ int level = 0;
+ while (sc.hasNext()) {
+ String c = sc.next();
+ if (c.equals("(")) {
+ level++;
+ } else if (c.equals(")")) {
+ level--;
+ } else if (level == 0) {
+ out.append(c);
+ }
+ }
+ pgn = out.toString();
+ }
+
+ // Parse move text section
+ sc = new Scanner(pgn);
+ sc.useDelimiter("\\s+");
+ while (sc.hasNext()) {
+ String strMove = sc.next();
+ strMove = strMove.replaceFirst("\\$?[0-9]*\\.*([^?!]*)[?!]*", "$1");
+ if (strMove.length() == 0) continue;
+ Move m = TextIO.stringToMove(game.pos, strMove);
+ if (m == null)
+ break;
+ game.processString(strMove);
+ }
+ }
+
+ public void setFENOrPGN(String fenPgn) throws ChessParseError {
+ try {
+ Position pos = TextIO.readFEN(fenPgn);
+ game.processString("new");
+ game.pos = pos;
+ } catch (ChessParseError e) {
+ // Try read as PGN instead
+ setPGN(fenPgn);
+ }
+ gui.setSelection(-1);
+ updateGUI();
+ startComputerThinking();
+ }
+
+ /** Set color for human player. Doesn't work when computer is thinking. */
+ public final void setHumanWhite(final boolean humanIsWhite) {
+ if (computerThread != null)
+ return;
+ if (this.humanIsWhite != humanIsWhite) {
+ this.humanIsWhite = humanIsWhite;
+ game.processString("swap");
+ startComputerThinking();
+ }
+ }
+
+ public final boolean humansTurn() {
+ return game.pos.whiteMove == humanIsWhite;
+ }
+ public final boolean computerThinking() {
+ return computerThread != null;
+ }
+
+ public final void takeBackMove() {
+ if (humansTurn()) {
+ if (game.getLastMove() != null) {
+ game.processString("undo");
+ if (game.getLastMove() != null) {
+ game.processString("undo");
+ } else {
+ game.processString("redo");
+ }
+ updateGUI();
+ setSelection();
+ }
+ } else if (game.getGameState() != Game.GameState.ALIVE) {
+ if (game.getLastMove() != null) {
+ game.processString("undo");
+ if (!humansTurn()) {
+ if (game.getLastMove() != null) {
+ game.processString("undo");
+ } else {
+ game.processString("redo");
+ }
+ }
+ updateGUI();
+ setSelection();
+ }
+ }
+ }
+
+ public final void redoMove() {
+ if (humansTurn()) {
+ game.processString("redo");
+ game.processString("redo");
+ updateGUI();
+ setSelection();
+ }
+ }
+
+ public final void humanMove(Move m) {
+ if (humansTurn()) {
+ if (doMove(m)) {
+ updateGUI();
+ startComputerThinking();
+ } else {
+ gui.setSelection(-1);
+ }
+ }
+ }
+
+ Move promoteMove;
+ public final void reportPromotePiece(int choice) {
+ final boolean white = game.pos.whiteMove;
+ int promoteTo;
+ switch (choice) {
+ case 1:
+ promoteTo = white ? Piece.WROOK : Piece.BROOK;
+ break;
+ case 2:
+ promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP;
+ break;
+ case 3:
+ promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT;
+ break;
+ default:
+ promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN;
+ break;
+ }
+ promoteMove.promoteTo = promoteTo;
+ Move m = promoteMove;
+ promoteMove = null;
+ humanMove(m);
+ }
+
+ /**
+ * Move a piece from one square to another.
+ * @return True if the move was legal, false otherwise.
+ */
+ final private boolean doMove(Move move) {
+ Position pos = game.pos;
+ MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moves);
+ int promoteTo = move.promoteTo;
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ if ((m.from == move.from) && (m.to == move.to)) {
+ if ((m.promoteTo != Piece.EMPTY) && (promoteTo == Piece.EMPTY)) {
+ promoteMove = m;
+ gui.requestPromotePiece();
+ return false;
+ }
+ if (m.promoteTo == promoteTo) {
+ String strMove = TextIO.moveToString(pos, m, false);
+ game.processString(strMove);
+ return true;
+ }
+ }
+ }
+ gui.reportInvalidMove(move);
+ return false;
+ }
+
+
+ final private void updateGUI() {
+ setStatusString();
+ setMoveList();
+ setThinkingPV();
+ gui.setPosition(game.pos);
+ }
+
+ final private void setStatusString() {
+ String str = game.pos.whiteMove ? "White's move" : "Black's move";
+ if (computerThread != null) str += " (thinking)";
+ if (game.getGameState() != Game.GameState.ALIVE) {
+ str = game.getGameStateString();
+ }
+ gui.setStatusString(str);
+ }
+
+ public final void setMoveList() {
+ String str = game.getMoveListString(true);
+ gui.setMoveListString(str);
+ }
+
+ public final void setThinkingPV() {
+ String str = "";
+ if (gui.showThinking()) {
+ str = thinkingPV;
+ }
+ gui.setThinkingString(str);
+ }
+
+ final private void setSelection() {
+ Move m = game.getLastMove();
+ int sq = (m != null) ? m.to : -1;
+ gui.setSelection(sq);
+ }
+
+
+ private void startComputerThinking() {
+ if (game.pos.whiteMove != humanIsWhite) {
+ if (computerThread == null) {
+ Runnable run = new Runnable() {
+ public void run() {
+ computerPlayer.timeLimit(gui.timeLimit(), gui.timeLimit(), gui.randomMode());
+ final String cmd = computerPlayer.getCommand(new Position(game.pos),
+ game.haveDrawOffer(), game.getHistory());
+ gui.runOnUIThread(new Runnable() {
+ public void run() {
+ game.processString(cmd);
+ thinkingPV = "";
+ updateGUI();
+ setSelection();
+ stopComputerThinking();
+ }
+ });
+ }
+ };
+ if (threadStack > 0) {
+ ThreadGroup tg = new ThreadGroup("searcher");
+ computerThread = new Thread(tg, run, "searcher", threadStack);
+ } else {
+ computerThread = new Thread(run);
+ }
+ thinkingPV = "";
+ updateGUI();
+ computerThread.start();
+ }
+ }
+ }
+
+ public synchronized void stopComputerThinking() {
+ if (computerThread != null) {
+ computerPlayer.timeLimit(0, 0, false);
+ try {
+ computerThread.join();
+ } catch (InterruptedException ex) {
+ System.out.printf("Could not stop thread%n");
+ }
+ computerThread = null;
+ updateGUI();
+ }
+ }
+
+ public synchronized void setTimeLimit() {
+ if (computerThread != null) {
+ computerPlayer.timeLimit(gui.timeLimit(), gui.timeLimit(), gui.randomMode());
+ }
+ }
+}
diff --git a/CuckooChessEngine/src/guibase/GUIInterface.java b/CuckooChessEngine/src/guibase/GUIInterface.java
new file mode 100644
index 0000000..54062bf
--- /dev/null
+++ b/CuckooChessEngine/src/guibase/GUIInterface.java
@@ -0,0 +1,58 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package guibase;
+
+import chess.Move;
+import chess.Position;
+
+public interface GUIInterface {
+
+ /** Update the displayed board position. */
+ public void setPosition(Position pos);
+
+ /** Mark square i as selected. Set to -1 to clear selection. */
+ public void setSelection(int sq);
+
+ /** Set the status text. */
+ public void setStatusString(String str);
+
+ /** Update the list of moves. */
+ public void setMoveListString(String str);
+
+ /** Update the computer thinking information. */
+ public void setThinkingString(String str);
+
+ /** Get the current time limit. */
+ public int timeLimit();
+
+ /** Get "random move" setting. */
+ public boolean randomMode();
+
+ /** Return true if "show thinking" is enabled. */
+ public boolean showThinking();
+
+ /** Ask what to promote a pawn to. Should call reportPromotePiece() when done. */
+ public void requestPromotePiece();
+
+ /** Run code on the GUI thread. */
+ public void runOnUIThread(Runnable runnable);
+
+ /** Report that user attempted to make an invalid move. */
+ public void reportInvalidMove(Move m);
+}
diff --git a/CuckooChessEngine/src/kpk.bitbase b/CuckooChessEngine/src/kpk.bitbase
new file mode 100644
index 0000000..6b8d6b6
Binary files /dev/null and b/CuckooChessEngine/src/kpk.bitbase differ
diff --git a/CuckooChessEngine/src/krkp.winmasks b/CuckooChessEngine/src/krkp.winmasks
new file mode 100644
index 0000000..62bf392
Binary files /dev/null and b/CuckooChessEngine/src/krkp.winmasks differ
diff --git a/CuckooChessEngine/test/chess/BitBoardTest.java b/CuckooChessEngine/test/chess/BitBoardTest.java
new file mode 100644
index 0000000..d04b2aa
--- /dev/null
+++ b/CuckooChessEngine/test/chess/BitBoardTest.java
@@ -0,0 +1,136 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import static org.junit.Assert.*;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class BitBoardTest {
+ public BitBoardTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /** Test of kingAttacks, of class BitBoard. */
+ @Test
+ public void testKingAttacks() throws ChessParseError {
+ System.out.println("kingAttacks");
+ assertEquals(5, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("g1")]));
+ assertEquals(3, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("h1")]));
+ assertEquals(3, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("a1")]));
+ assertEquals(5, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("a2")]));
+ assertEquals(3, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("h8")]));
+ assertEquals(5, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("a6")]));
+ assertEquals(8, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("b2")]));
+ }
+
+ /** Test of knightAttacks, of class BitBoard. */
+ @Test
+ public void testKnightAttacks() throws ChessParseError {
+ System.out.println("knightAttacks");
+ assertEquals(3, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("g1")]));
+ assertEquals(2, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("a1")]));
+ assertEquals(2, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("h1")]));
+ assertEquals(4, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("h6")]));
+ assertEquals(4, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("b7")]));
+ assertEquals(8, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("c6")]));
+ assertEquals((1L< 1) || (Math.abs(dy) > 1)) {
+ assertTrue(BitBoard.squaresBetween[sq1][sq2] != 0);
+ } else {
+ assertEquals(0, BitBoard.squaresBetween[sq1][sq2]);
+ }
+ }
+ }
+ }
+ }
+
+ assertEquals(0x0040201008040200L, BitBoard.squaresBetween[0][63]);
+ assertEquals(0x000000001C000000L, BitBoard.squaresBetween[TextIO.getSquare("b4")][TextIO.getSquare("f4")]);
+ }
+
+ /**
+ * If there is a piece type that can move from "from" to "to", return the
+ * corresponding direction, 8*dy+dx.
+ */
+ private static final int computeDirection(int from, int to) {
+ int dx = Position.getX(to) - Position.getX(from);
+ int dy = Position.getY(to) - Position.getY(from);
+ if (dx == 0) { // Vertical rook direction
+ if (dy == 0) return 0;
+ return (dy > 0) ? 8 : -8;
+ }
+ if (dy == 0) // Horizontal rook direction
+ return (dx > 0) ? 1 : -1;
+ if (Math.abs(dx) == Math.abs(dy)) // Bishop direction
+ return ((dy > 0) ? 8 : -8) + (dx > 0 ? 1 : -1);
+ if (Math.abs(dx * dy) == 2) // Knight direction
+ return dy * 8 + dx;
+ return 0;
+ }
+
+ @Test
+ public void testGetDirection() {
+ System.out.println("getDirection");
+ for (int from = 0; from < 64; from++) {
+ for (int to = 0; to < 64; to++) {
+ assertEquals(computeDirection(from, to), BitBoard.getDirection(from, to));
+ }
+ }
+ }
+
+ @Test
+ public void testTrailingZeros() {
+ System.out.println("trailingZeros");
+ for (int i = 0; i < 64; i++) {
+ long mask = 1L << i;
+ assertEquals(i, BitBoard.numberOfTrailingZeros(mask));
+ }
+ }
+}
diff --git a/CuckooChessEngine/test/chess/BookTest.java b/CuckooChessEngine/test/chess/BookTest.java
new file mode 100644
index 0000000..418c47a
--- /dev/null
+++ b/CuckooChessEngine/test/chess/BookTest.java
@@ -0,0 +1,85 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class BookTest {
+
+ public BookTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /**
+ * Test of getBookMove method, of class Book.
+ */
+ @Test
+ public void testGetBookMove() throws ChessParseError {
+ System.out.println("getBookMove");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ Book book = new Book(true);
+ Move move = book.getBookMove(pos);
+ checkValid(pos, move);
+ }
+
+ /**
+ * Test of getAllBookMoves method, of class Book.
+ */
+ @Test
+ public void testGetAllBookMoves() throws ChessParseError {
+ System.out.println("getAllBookMoves");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ Book book = new Book(true);
+ String moveListString = book.getAllBookMoves(pos);
+ String[] strMoves = moveListString.split("\\([0-9]*\\) ");
+ assertTrue(strMoves.length > 1);
+ for (String strMove : strMoves) {
+ Move m = TextIO.stringToMove(pos, strMove);
+ checkValid(pos, m);
+ }
+ }
+
+ /** Check that move is a legal move in position pos. */
+ private void checkValid(Position pos, Move move) {
+ assertTrue(move != null);
+ MoveGen.MoveList moveList = new MoveGen().pseudoLegalMoves(pos);
+ MoveGen.removeIllegal(pos, moveList);
+ boolean contains = false;
+ for (int mi = 0; mi < moveList.size; mi++)
+ if (moveList.m[mi].equals(move)) {
+ contains = true;
+ break;
+ }
+ assertTrue(contains);
+ }
+}
diff --git a/CuckooChessEngine/test/chess/ComputerPlayerTest.java b/CuckooChessEngine/test/chess/ComputerPlayerTest.java
new file mode 100644
index 0000000..0db030a
--- /dev/null
+++ b/CuckooChessEngine/test/chess/ComputerPlayerTest.java
@@ -0,0 +1,114 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.util.ArrayList;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class ComputerPlayerTest {
+
+ public ComputerPlayerTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /**
+ * Test of getCommand method, of class ComputerPlayer.
+ */
+ @Test
+ public void testGetCommand() throws ChessParseError {
+ System.out.println("getCommand");
+ ArrayList nullHist = new ArrayList();
+
+ Position pos = TextIO.readFEN("7k/5Q2/p5K1/8/8/8/8/8 b - - 99 80");
+ ComputerPlayer cp = new ComputerPlayer();
+ cp.maxDepth = 1;
+ cp.maxTimeMillis = -1;
+ cp.verbose = false;
+ String result = cp.getCommand(pos, false, nullHist);
+ assertEquals("a5", result); // Only one legal move
+
+ pos = TextIO.readFEN("7k/5Q2/p5K1/8/8/8/8/8 b - - 100 80");
+ result = cp.getCommand(pos, false, nullHist);
+ assertEquals("draw 50", result); // Should claim draw without making a move
+
+ pos = TextIO.readFEN("3k4/1R6/R7/8/8/8/8/1K6 w - - 100 80");
+ result = cp.getCommand(pos, false, nullHist);
+ assertEquals("Ra8#", result); // Can claim draw, but should not
+
+ pos = TextIO.readFEN("8/1R5k/R7/8/8/8/B7/1K6 b - - 99 80");
+ result = cp.getCommand(pos, false, nullHist);
+ assertEquals("draw 50 Kh8", result); // Should claim draw by 50-move rule
+
+ // Only one possible move. Should realize that draw claim is possible, but very bad
+ pos = TextIO.readFEN("6Nk/8/5K1R/q7/q7/q7/8/8 b - - 100 80");
+ result = cp.getCommand(pos, false, nullHist);
+ assertEquals("Kxg8", result);
+ }
+
+ /**
+ * Test of draw by repetition, of class ComputerPlayer.
+ */
+ @Test
+ public void testDrawRep() throws ChessParseError {
+ System.out.println("drawRep");
+ Game game = new Game(new HumanPlayer(), new HumanPlayer());
+ ComputerPlayer cp = new ComputerPlayer();
+ cp.maxDepth = 3;
+ cp.maxTimeMillis = -1;
+ cp.verbose = false;
+ game.processString("setpos 7k/5RR1/8/8/8/8/q3q3/2K5 w - - 0 1");
+ game.processString("Rh7");
+ game.processString("Kg8");
+ game.processString("Rhg7");
+ String result = cp.getCommand(new Position(game.pos), false, game.getHistory());
+ assertEquals("Kh8", result); // Not valid to claim draw here
+ game.processString("Kh8");
+ game.processString("Rh7");
+ game.processString("Kg8");
+ game.processString("Rhg7");
+ result = cp.getCommand(new Position(game.pos), false, game.getHistory());
+ assertEquals("draw rep Kh8", result); // Can't win, but can claim draw.
+
+ game.processString("setpos 7k/R7/1R6/8/8/8/8/K7 w - - 0 1");
+ game.processString("Ra8");
+ game.processString("Kh7");
+ result = cp.getCommand(new Position(game.pos), false, game.getHistory());
+ assertEquals("Ra7+", result); // Ra7 is mate-in-two
+ game.processString("Ra7");
+ game.processString("Kh8");
+ game.processString("Ra8");
+ game.processString("Kh7");
+ result = cp.getCommand(new Position(game.pos), false, game.getHistory());
+ assertTrue(!result.equals("Ra7+")); // Ra7 now leads to a draw by repetition
+ }
+}
diff --git a/CuckooChessEngine/test/chess/EvaluateTest.java b/CuckooChessEngine/test/chess/EvaluateTest.java
new file mode 100644
index 0000000..aff7f8b
--- /dev/null
+++ b/CuckooChessEngine/test/chess/EvaluateTest.java
@@ -0,0 +1,492 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class EvaluateTest {
+
+ public EvaluateTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /**
+ * Test of evalPos method, of class Evaluate.
+ */
+ @Test
+ public void testEvalPos() throws ChessParseError {
+ System.out.println("evalPos");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(TextIO.stringToMove(pos, "e4"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "e5"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Nf3"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Nc6"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Bb5"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Nge7"), ui);
+ assertTrue(moveScore(pos, "O-O") > 0); // Castling is good
+ assertTrue(moveScore(pos, "Ke2") < 0); // Losing right to castle is bad
+ assertTrue(moveScore(pos, "Kf1") < 0);
+ assertTrue(moveScore(pos, "Rg1") < 0);
+ assertTrue(moveScore(pos, "Rf1") < 0);
+
+ pos = TextIO.readFEN("8/8/8/1r3k2/4pP2/4P3/8/4K2R w K - 0 1");
+ assertEquals(true, pos.h1Castle());
+ int cs1 = evalWhite(pos);
+ pos.setCastleMask(pos.getCastleMask() & ~(1 << Position.H1_CASTLE));
+ assertEquals(false, pos.h1Castle());
+ int cs2 = evalWhite(pos);
+ assertTrue(cs2 >= cs1); // No bonus for useless castle right
+
+ // Test rook open file bonus
+ pos = TextIO.readFEN("r4rk1/1pp1qppp/3b1n2/4p3/2B1P1b1/1QN2N2/PP3PPP/R3R1K1 w - - 0 1");
+ int ms1 = moveScore(pos, "Red1");
+ int ms2 = moveScore(pos, "Rec1");
+ int ms3 = moveScore(pos, "Rac1");
+ int ms4 = moveScore(pos, "Rad1");
+ assertTrue(ms1 > 0); // Good to have rook on open file
+ assertTrue(ms2 > 0); // Good to have rook on half-open file
+ assertTrue(ms1 > ms2); // Open file better than half-open file
+ assertTrue(ms3 > 0);
+ assertTrue(ms4 > 0);
+ assertTrue(ms4 > ms1);
+ assertTrue(ms3 > ms2);
+
+ pos = TextIO.readFEN("r3kb1r/p3pp1p/bpPq1np1/4N3/2pP4/2N1PQ2/P1PB1PPP/R3K2R b KQkq - 0 12");
+ assertTrue(moveScore(pos, "O-O-O") > 0); // Black long castle is bad for black
+ pos.makeMove(TextIO.stringToMove(pos, "O-O-O"), ui);
+ assertTrue(moveScore(pos, "O-O") > 0); // White short castle is good for white
+
+ pos = TextIO.readFEN("8/3k4/2p5/1pp5/1P1P4/3K4/8/8 w - - 0 1");
+ int sc1 = moveScore(pos, "bxc5");
+ int sc2 = moveScore(pos, "dxc5");
+ assertTrue(sc1 < sc2); // Don't give opponent a passed pawn.
+
+ pos = TextIO.readFEN("8/pp1bk3/8/8/8/8/PPPBK3/8 w - - 0 1");
+ sc1 = evalWhite(pos);
+ pos.setPiece(Position.getSquare(3, 1), Piece.EMPTY);
+ pos.setPiece(Position.getSquare(3, 0), Piece.WBISHOP);
+ sc2 = evalWhite(pos);
+ assertTrue(sc2 > sc1); // Easier to win if bishops on same color
+
+ // Test bishop mobility
+ pos = TextIO.readFEN("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3");
+ sc1 = moveScore(pos, "Bd3");
+ sc2 = moveScore(pos, "Bc4");
+ assertTrue(sc2 > sc1);
+ }
+
+ /**
+ * Test of pieceSquareEval method, of class Evaluate.
+ */
+ @Test
+ public void testPieceSquareEval() throws ChessParseError {
+ System.out.println("pieceSquareEval");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ int score = evalWhite(pos);
+ assertEquals(0, score); // Should be zero, by symmetry
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(TextIO.stringToMove(pos, "e4"), ui);
+ score = evalWhite(pos);
+ assertTrue(score > 0); // Centralizing a pawn is a good thing
+ pos.makeMove(TextIO.stringToMove(pos, "e5"), ui);
+ score = evalWhite(pos);
+ assertEquals(0, score); // Should be zero, by symmetry
+ assertTrue(moveScore(pos, "Nf3") > 0); // Developing knight is good
+ pos.makeMove(TextIO.stringToMove(pos, "Nf3"), ui);
+ assertTrue(moveScore(pos, "Nc6") < 0); // Developing knight is good
+ pos.makeMove(TextIO.stringToMove(pos, "Nc6"), ui);
+ assertTrue(moveScore(pos, "Bb5") > 0); // Developing bishop is good
+ pos.makeMove(TextIO.stringToMove(pos, "Bb5"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Nge7"), ui);
+ assertTrue(moveScore(pos, "Qe2") > 0); // Queen away from edge is good
+ score = evalWhite(pos);
+ pos.makeMove(TextIO.stringToMove(pos, "Bxc6"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Nxc6"), ui);
+ int score2 = evalWhite(pos);
+ assertTrue(score2 < score); // Bishop worth more than knight in this case
+
+ pos = TextIO.readFEN("5k2/4nppp/p1n5/1pp1p3/4P3/2P1BN2/PP3PPP/3R2K1 w - - 0 1");
+ assertTrue(moveScore(pos, "Rd7") > 0); // Rook on 7:th rank is good
+ assertTrue(moveScore(pos, "Rd8") <= 0); // Rook on 8:th rank not particularly good
+ pos.setPiece(TextIO.getSquare("a1"), Piece.WROOK);
+ assertTrue(moveScore(pos, "Rac1") > 0); // Rook on c-f files considered good
+
+ pos = TextIO.readFEN("r4rk1/pppRRppp/1q4b1/n7/8/2N3B1/PPP1QPPP/6K1 w - - 0 1");
+ score = evalWhite(pos);
+ assertTrue(score > 100); // Two rooks on 7:th rank is very good
+ }
+
+ /**
+ * Test of tradeBonus method, of class Evaluate.
+ */
+ @Test
+ public void testTradeBonus() throws ChessParseError {
+ System.out.println("tradeBonus");
+ String fen = "8/5k2/6r1/2p1p3/3p4/2P2N2/3PPP2/4K1R1 w - - 0 1";
+ Position pos = TextIO.readFEN(fen);
+ int score1 = evalWhite(pos);
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(TextIO.stringToMove(pos, "Rxg6"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Kxg6"), ui);
+ int score2 = evalWhite(pos);
+ assertTrue(score2 > score1); // White ahead, trading pieces is good
+
+ pos = TextIO.readFEN(fen);
+ pos.makeMove(TextIO.stringToMove(pos, "cxd4"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "cxd4"), ui);
+ score2 = evalWhite(pos);
+ assertTrue(score2 < score1); // White ahead, trading pawns is bad
+
+ pos = TextIO.readFEN("8/8/1b2b3/4kp2/5N2/4NKP1/6B1/8 w - - 0 62");
+ score1 = evalWhite(pos);
+ pos.makeMove(TextIO.stringToMove(pos, "Nxe6"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Kxe6"), ui);
+ score2 = evalWhite(pos);
+ assertTrue(score2 > score1); // White ahead, trading pieces is good
+ }
+
+ /**
+ * Test of material method, of class Evaluate.
+ */
+ @Test
+ public void testMaterial() throws ChessParseError {
+ System.out.println("material");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ assertEquals(0, Evaluate.material(pos));
+
+ final int pV = Evaluate.pV;
+ final int qV = Evaluate.qV;
+ assertTrue(pV != 0);
+ assertTrue(qV != 0);
+ assertTrue(qV > pV);
+
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(TextIO.stringToMove(pos, "e4"), ui);
+ assertEquals(0, Evaluate.material(pos));
+ pos.makeMove(TextIO.stringToMove(pos, "d5"), ui);
+ assertEquals(0, Evaluate.material(pos));
+ pos.makeMove(TextIO.stringToMove(pos, "exd5"), ui);
+ assertEquals(pV, Evaluate.material(pos));
+ pos.makeMove(TextIO.stringToMove(pos, "Qxd5"), ui);
+ assertEquals(0, Evaluate.material(pos));
+ pos.makeMove(TextIO.stringToMove(pos, "Nc3"), ui);
+ assertEquals(0, Evaluate.material(pos));
+ pos.makeMove(TextIO.stringToMove(pos, "Qxd2"), ui);
+ assertEquals(-pV, Evaluate.material(pos));
+ pos.makeMove(TextIO.stringToMove(pos, "Qxd2"), ui);
+ assertEquals(-pV+qV, Evaluate.material(pos));
+ }
+
+ /**
+ * Test of kingSafety method, of class Evaluate.
+ */
+ @Test
+ public void testKingSafety() throws ChessParseError {
+ System.out.println("kingSafety");
+ Position pos = TextIO.readFEN("r3kb1r/p1p1pppp/b2q1n2/4N3/3P4/2N1PQ2/P2B1PPP/R3R1K1 w kq - 0 1");
+ int s1 = evalWhite(pos);
+ pos.setPiece(TextIO.getSquare("g7"), Piece.EMPTY);
+ pos.setPiece(TextIO.getSquare("b7"), Piece.BPAWN);
+ int s2 = evalWhite(pos);
+ assertTrue(s2 < s1); // Half-open g-file is bad for white
+
+ // Trapping rook with own king is bad
+ pos = TextIO.readFEN("rnbqk1nr/pppp1ppp/8/8/1bBpP3/8/PPP2PPP/RNBQK1NR w KQkq - 2 4");
+ s1 = evalWhite(pos);
+ pos.setPiece(TextIO.getSquare("e1"), Piece.EMPTY);
+ pos.setPiece(TextIO.getSquare("f1"), Piece.WKING);
+ s2 = evalWhite(pos);
+ assertTrue(s2 < s1);
+
+ pos = TextIO.readFEN("rnbqk1nr/pppp1ppp/8/8/1bBpPB2/8/PPP1QPPP/RN1K2NR w kq - 0 1");
+ s1 = evalWhite(pos);
+ pos.setPiece(TextIO.getSquare("d1"), Piece.EMPTY);
+ pos.setPiece(TextIO.getSquare("c1"), Piece.WKING);
+ s2 = evalWhite(pos);
+ assertTrue(s2 < s1);
+ }
+
+ /**
+ * Test of endGameEval method, of class Evaluate.
+ */
+ @Test
+ public void testEndGameEval() throws ChessParseError {
+ System.out.println("endGameEval");
+ Position pos = new Position();
+ pos.setPiece(Position.getSquare(4, 1), Piece.WKING);
+ pos.setPiece(Position.getSquare(4, 6), Piece.BKING);
+ int score = evalWhite(pos);
+ assertEquals(0, score);
+
+ pos.setPiece(Position.getSquare(3, 1), Piece.WBISHOP);
+ score = evalWhite(pos);
+ assertTrue(Math.abs(score) < 50); // Insufficient material to mate
+
+ pos.setPiece(Position.getSquare(3, 1), Piece.WKNIGHT);
+ score = evalWhite(pos);
+ assertTrue(Math.abs(score) < 50); // Insufficient material to mate
+
+ pos.setPiece(Position.getSquare(3, 1), Piece.WROOK);
+ score = evalWhite(pos);
+ final int rV = Evaluate.rV;
+ assertTrue(Math.abs(score) > rV + 100); // Enough material to force mate
+
+ pos.setPiece(Position.getSquare(3, 6), Piece.BBISHOP);
+ score = evalWhite(pos);
+ final int bV = Evaluate.bV;
+ assertTrue(score >= 0);
+ assertTrue(score < rV - bV); // Insufficient excess material to mate
+
+ pos.setPiece(Position.getSquare(5, 6), Piece.BROOK);
+ score = evalWhite(pos);
+ assertTrue(score <= 0);
+ assertTrue(-score < bV);
+
+ pos.setPiece(Position.getSquare(2, 6), Piece.BBISHOP);
+ score = evalWhite(pos);
+ assertTrue(-score > bV * 2 + 100);
+
+ // KrpKn is win for white
+ pos = TextIO.readFEN("8/3bk3/8/8/8/3P4/3RK3/8 w - - 0 1");
+ score = evalWhite(pos);
+ final int pV = Evaluate.pV;
+ assertTrue(score > rV + pV - bV - 100);
+
+ // KNNK is a draw
+ pos = TextIO.readFEN("8/8/4k3/8/8/3NK3/3N4/8 w - - 0 1");
+ score = evalWhite(pos);
+ assertTrue(Math.abs(score) < 50);
+
+ pos = TextIO.readFEN("8/8/3k4/8/8/3NK3/2B5/8 b - - 0 1");
+ score = evalWhite(pos);
+ final int nV = Evaluate.nV;
+ assertTrue(score > bV + nV + 150); // KBNK is won, should have a bonus
+ score = moveScore(pos, "Kc6");
+ assertTrue(score > 0); // Black king going into wrong corner, good for white
+ score = moveScore(pos, "Ke6");
+ assertTrue(score < 0); // Black king going away from wrong corner, good for black
+
+ // KRN vs KR is generally drawn
+ pos = TextIO.readFEN("rk/p/8/8/8/8/NKR/8 w - - 0 1");
+ score = evalWhite(pos);
+ assertTrue(score < nV - 2 * pV);
+ }
+
+ /**
+ * Test of endGameEval method, of class Evaluate.
+ */
+ @Test
+ public void testPassedPawns() throws ChessParseError {
+ System.out.println("passedPawns");
+ Position pos = TextIO.readFEN("8/8/8/P3k/8/8/p/K w");
+ int score = evalWhite(pos);
+ assertTrue(score > 300); // Unstoppable passed pawn
+ pos.whiteMove = false;
+ score = evalWhite(pos);
+ assertTrue(score <= 0); // Not unstoppable
+
+ pos = TextIO.readFEN("4R3/8/8/3K4/8/4pk2/8/8 w - - 0 1");
+ score = evalWhite(pos);
+ pos.setPiece(TextIO.getSquare("d5"), Piece.EMPTY);
+ pos.setPiece(TextIO.getSquare("d4"), Piece.WKING);
+ int score2 = evalWhite(pos);
+ assertTrue(score2 > score); // King closer to passed pawn promotion square
+
+ // Connected passed pawn test. Disabled because it didn't help in tests
+// pos = TextIO.readFEN("4k3/8/8/4P3/3P1K2/8/8/8 w - - 0 1");
+// score = evalWhite(pos);
+// pos.setPiece(TextIO.getSquare("d4"), Piece.EMPTY);
+// pos.setPiece(TextIO.getSquare("d5"), Piece.WPAWN);
+// score2 = evalWhite(pos);
+// assertTrue(score2 > score); // Advancing passed pawn is good
+ }
+
+ /**
+ * Test of endGameEval method, of class Evaluate.
+ */
+ @Test
+ public void testBishAndRookPawns() throws ChessParseError {
+ System.out.println("bishAndRookPawns");
+ final int pV = Evaluate.pV;
+ final int bV = Evaluate.bV;
+ final int winScore = pV + bV;
+ final int drawish = (pV + bV) / 20;
+ Position pos = TextIO.readFEN("k7/8/8/8/2B5/2K5/P7/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+
+ pos = TextIO.readFEN("k7/8/8/8/3B4/2K5/P7/8 w - - 0 1");
+ assertTrue(evalWhite(pos) < drawish);
+
+ pos = TextIO.readFEN("8/2k5/8/8/3B4/2K5/P7/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+
+ pos = TextIO.readFEN("8/2k5/8/8/3B4/2K4P/8/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+
+ pos = TextIO.readFEN("8/2k5/8/8/4B3/2K4P/8/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+
+ pos = TextIO.readFEN("8/6k1/8/8/4B3/2K4P/8/8 w - - 0 1");
+ assertTrue(evalWhite(pos) < drawish);
+
+ pos = TextIO.readFEN("8/6k1/8/8/4B3/2K4P/7P/8 w - - 0 1");
+ assertTrue(evalWhite(pos) < drawish);
+
+ pos = TextIO.readFEN("8/6k1/8/8/2B1B3/2K4P/7P/8 w - - 0 1");
+ assertTrue(evalWhite(pos) < drawish);
+
+ pos = TextIO.readFEN("8/6k1/8/2B5/4B3/2K4P/7P/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+
+ pos = TextIO.readFEN("8/6k1/8/8/4B3/2K4P/P7/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+
+ pos = TextIO.readFEN("8/6k1/8/8/4B3/2K3PP/8/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+ }
+
+ @Test
+ public void testTrappedBishop() throws ChessParseError {
+ Position pos = TextIO.readFEN("r2q1rk1/ppp2ppp/3p1n2/8/3P4/1P1Q1NP1/b1P2PBP/2KR3R w - - 0 1");
+ assertTrue(evalWhite(pos) > 0); // Black has trapped bishop
+
+ pos = TextIO.readFEN("r2q2k1/pp1b1p1p/2p2np1/3p4/3P4/1BNQ2P1/PPPB1P1b/2KR4 w - - 0 1");
+ assertTrue(evalWhite(pos) > 0); // Black has trapped bishop
+ }
+
+ /**
+ * Test of endGameEval method, of class Evaluate.
+ */
+ @Test
+ public void testKQKP() throws ChessParseError {
+ System.out.println("KQKP");
+ final int pV = Evaluate.pV;
+ final int qV = Evaluate.qV;
+ final int winScore = qV - pV - 200;
+ final int drawish = (pV + qV) / 20;
+
+ // Pawn on a2
+ Position pos = TextIO.readFEN("8/8/1K6/8/8/Q7/p7/1k6 w - - 0 1");
+ assertTrue(evalWhite(pos) < drawish);
+ pos = TextIO.readFEN("8/8/8/1K6/8/Q7/p7/1k6 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+ pos = TextIO.readFEN("3Q4/8/8/8/K7/8/1kp5/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+
+ // Pawn on c2
+ pos = TextIO.readFEN("3Q4/8/8/8/3K4/8/1kp5/8 w - - 0 1");
+ assertTrue(evalWhite(pos) < drawish);
+ pos = TextIO.readFEN("3Q4/8/8/8/8/4K3/1kp5/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+ }
+
+ @Test
+ public void testKRKP() throws ChessParseError {
+ System.out.println("KRKP");
+ final int pV = Evaluate.pV;
+ final int rV = Evaluate.rV;
+ final int winScore = rV - pV;
+ final int drawish = (pV + rV) / 20;
+ Position pos = TextIO.readFEN("6R1/8/8/8/5K2/2kp4/8/8 w - - 0 1");
+ assertTrue(evalWhite(pos) > winScore);
+ pos.whiteMove = !pos.whiteMove;
+ assertTrue(evalWhite(pos) < drawish);
+ }
+
+ @Test
+ public void testCantWin() throws ChessParseError {
+ Position pos = TextIO.readFEN("8/8/8/3k4/3p4/3K4/4N3/8 w - - 0 1");
+ int score1 = evalWhite(pos);
+ assertTrue(score1 < 0);
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(TextIO.stringToMove(pos, "Nxd4"), ui);
+ int score2 = evalWhite(pos);
+ assertTrue(score2 <= 0);
+ assertTrue(score2 > score1);
+ }
+
+ /** Return static evaluation score for white, regardless of whose turn it is to move. */
+ final static int evalWhite(Position pos) {
+ Evaluate eval = new Evaluate();
+ int ret = eval.evalPos(pos);
+ Position symPos = swapColors(pos);
+ int symScore = eval.evalPos(symPos);
+ assertEquals(ret, symScore);
+ if (!pos.whiteMove) {
+ ret = -ret;
+ }
+ return ret;
+ }
+
+ final static Position swapColors(Position pos) {
+ Position sym = new Position();
+ sym.whiteMove = !pos.whiteMove;
+ for (int x = 0; x < 8; x++) {
+ for (int y = 0; y < 8; y++) {
+ int p = pos.getPiece(Position.getSquare(x, y));
+ p = Piece.isWhite(p) ? Piece.makeBlack(p) : Piece.makeWhite(p);
+ sym.setPiece(Position.getSquare(x, 7-y), p);
+ }
+ }
+
+ int castleMask = 0;
+ if (pos.a1Castle()) castleMask |= 1 << Position.A8_CASTLE;
+ if (pos.h1Castle()) castleMask |= 1 << Position.H8_CASTLE;
+ if (pos.a8Castle()) castleMask |= 1 << Position.A1_CASTLE;
+ if (pos.h8Castle()) castleMask |= 1 << Position.H1_CASTLE;
+ sym.setCastleMask(castleMask);
+
+ if (pos.getEpSquare() >= 0) {
+ int x = Position.getX(pos.getEpSquare());
+ int y = Position.getY(pos.getEpSquare());
+ sym.setEpSquare(Position.getSquare(x, 7-y));
+ }
+
+ sym.halfMoveClock = pos.halfMoveClock;
+ sym.fullMoveCounter = pos.fullMoveCounter;
+
+ return sym;
+ }
+
+ /** Compute change in eval score for white after making "moveStr" in position "pos". */
+ private final int moveScore(Position pos, String moveStr) {
+ int score1 = evalWhite(pos);
+ Position tmpPos = new Position(pos);
+ UndoInfo ui = new UndoInfo();
+ tmpPos.makeMove(TextIO.stringToMove(tmpPos, moveStr), ui);
+ int score2 = evalWhite(tmpPos);
+// System.out.printf("move:%s s1:%d s2:%d\n", moveStr, score1, score2);
+ return score2 - score1;
+ }
+}
diff --git a/CuckooChessEngine/test/chess/GameTest.java b/CuckooChessEngine/test/chess/GameTest.java
new file mode 100644
index 0000000..ae090da
--- /dev/null
+++ b/CuckooChessEngine/test/chess/GameTest.java
@@ -0,0 +1,441 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class GameTest {
+
+ public GameTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /**
+ * Test of haveDrawOffer method, of class Game.
+ */
+ @Test
+ public void testHaveDrawOffer() {
+ System.out.println("haveDrawOffer");
+ Game game = new Game(new HumanPlayer(), new HumanPlayer());
+ assertEquals(false, game.haveDrawOffer());
+
+ boolean res = game.processString("e4");
+ assertEquals(true, res);
+ assertEquals(false, game.haveDrawOffer());
+
+ res = game.processString("draw offer e5");
+ assertEquals(true, res);
+ assertEquals(true, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw offer does not imply draw
+ assertEquals(Piece.BPAWN, game.pos.getPiece(Position.getSquare(4, 4))); // e5 move made
+
+ res = game.processString("draw offer Nf3");
+ assertEquals(true, res);
+ assertEquals(true, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw offer does not imply draw
+ assertEquals(Piece.WKNIGHT, game.pos.getPiece(Position.getSquare(5, 2))); // Nf3 move made
+
+ res = game.processString("Nc6");
+ assertEquals(true, res);
+ assertEquals(false, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ assertEquals(Piece.BKNIGHT, game.pos.getPiece(Position.getSquare(2, 5))); // Nc6 move made
+
+ res = game.processString("draw offer Bb5");
+ assertEquals(true, res);
+ assertEquals(true, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ assertEquals(Piece.WBISHOP, game.pos.getPiece(Position.getSquare(1, 4))); // Bb5 move made
+
+ res = game.processString("draw accept");
+ assertEquals(true, res);
+ assertEquals(Game.GameState.DRAW_AGREE, game.getGameState()); // Draw by agreement
+
+ res = game.processString("undo");
+ assertEquals(true, res);
+ assertEquals(Piece.EMPTY, game.pos.getPiece(Position.getSquare(1, 4))); // Bb5 move undone
+ assertEquals(false, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ res = game.processString("undo");
+ assertEquals(true, res);
+ assertEquals(Piece.EMPTY, game.pos.getPiece(Position.getSquare(2, 5))); // Nc6 move undone
+ assertEquals(true, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+
+ res = game.processString("redo");
+ assertEquals(true, res);
+ assertEquals(Piece.BKNIGHT, game.pos.getPiece(Position.getSquare(2, 5))); // Nc6 move redone
+ assertEquals(false, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ res = game.processString("redo");
+ assertEquals(true, res);
+ assertEquals(Piece.WBISHOP, game.pos.getPiece(Position.getSquare(1, 4))); // Bb5 move redone
+ assertEquals(true, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ res = game.processString("redo");
+ assertEquals(true, res);
+ assertEquals(true, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // Can't redo draw accept
+
+ // Test draw offer in connection with invalid move
+ res = game.processString("new");
+ assertEquals(true, res);
+ assertEquals(false, game.haveDrawOffer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+
+ res = game.processString("draw offer e5");
+ assertEquals(true, res);
+ assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos)); // Move invalid, not executed
+ res = game.processString("e4");
+ assertEquals(true, res);
+ assertEquals(true, game.haveDrawOffer()); // Previous draw offer still valid
+ assertEquals(Piece.WPAWN, game.pos.getPiece(Position.getSquare(4, 3))); // e4 move made
+
+ // Undo/redo shall clear "pendingDrawOffer".
+ game.processString("new");
+ game.processString("e4");
+ game.processString("draw offer e4"); // Invalid black move
+ assertEquals(true, game.pendingDrawOffer);
+ game.processString("undo");
+ game.processString("redo");
+ game.processString("e5");
+ assertEquals(true,game.pos.whiteMove);
+ assertEquals(false, game.haveDrawOffer());
+ }
+
+ /**
+ * Test of draw by 50 move rule, of class Game.
+ */
+ @Test
+ public void testDraw50() {
+ System.out.println("draw50");
+ Game game = new Game(new HumanPlayer(), new HumanPlayer());
+ assertEquals(false, game.haveDrawOffer());
+ boolean res = game.processString("draw 50");
+ assertEquals(true, res);
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw claim invalid
+ res = game.processString("e4");
+ assertEquals(true, game.haveDrawOffer()); // Invalid claim converted to draw offer
+
+ String cmd = "setpos 8/4k3/8/P7/8/8/8/1N2K2R w K - 99 83";
+ res = game.processString(cmd);
+ assertEquals(true, res);
+ res = game.processString("draw 50");
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw claim invalid
+
+ game.processString(cmd);
+ game.processString("draw 50 Nc3");
+ assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Draw claim valid
+ assertEquals("Game over, draw by 50 move rule! [Nc3]", game.getGameStateString());
+
+ game.processString(cmd);
+ game.processString("draw 50 a6");
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // Pawn move resets counter
+ assertEquals(Piece.WPAWN, game.pos.getPiece(Position.getSquare(0, 5))); // Move a6 made
+
+ game.processString(cmd);
+ game.processString("draw 50 O-O");
+ assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Castling doesn't reset counter
+
+ game.processString(cmd);
+ game.processString("draw 50 Kf2");
+ assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Loss of castling right doesn't reset counter
+
+ game.processString(cmd);
+ game.processString("draw 50 Ke3");
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // Ke3 is invalid
+ assertEquals(true,game.pos.whiteMove);
+ game.processString("a6");
+ assertEquals(true, game.haveDrawOffer()); // Previous invalid claim converted to offer
+ game.processString("draw 50");
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // 50 move counter reset.
+ res = game.processString("draw accept");
+ assertEquals(true, res);
+ assertEquals(Game.GameState.DRAW_AGREE, game.getGameState()); // Can accept previous implicit offer
+
+ cmd = "setpos 3k4/R7/3K4/8/8/8/8/8 w - - 99 78";
+ game.processString(cmd);
+ game.processString("Ra8");
+ assertEquals(Game.GameState.WHITE_MATE, game.getGameState());
+ game.processString("draw 50");
+ assertEquals(Game.GameState.WHITE_MATE, game.getGameState()); // Can't claim draw when game over
+ assertEquals(Game.GameState.ALIVE, game.drawState);
+ }
+
+ /**
+ * Test of draw by repetition, of class Game.
+ */
+ @Test
+ public void testDrawRep() {
+ System.out.println("drawRep");
+ Game game = new Game(new HumanPlayer(), new HumanPlayer());
+ assertEquals(false, game.haveDrawOffer());
+ game.processString("Nc3");
+ game.processString("Nc6");
+ game.processString("Nb1");
+ game.processString("Nb8");
+ game.processString("Nf3");
+ game.processString("Nf6");
+ game.processString("Ng1");
+ assertEquals(false, game.haveDrawOffer());
+ game.processString("draw rep");
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // Claim not valid, one more move needed
+ game.processString("draw rep Nc6");
+ assertEquals(Game.GameState.ALIVE, game.getGameState()); // Claim not valid, wrong move claimed
+ assertEquals(Piece.BKNIGHT, game.pos.getPiece(Position.getSquare(2, 5))); // Move Nc6 made
+ assertEquals(true, game.haveDrawOffer());
+ game.processString("undo");
+ assertEquals(false, game.haveDrawOffer());
+ assertEquals(Piece.EMPTY, game.pos.getPiece(Position.getSquare(2, 5)));
+ game.processString("draw rep Ng8");
+ assertEquals(Game.GameState.DRAW_REP, game.getGameState());
+ assertEquals(Piece.EMPTY, game.pos.getPiece(Position.getSquare(6, 7))); // Ng8 not played
+
+ // Test draw by repetition when a "potential ep square but not real ep square" position is present.
+ game.processString("new");
+ game.processString("e4"); // e3 is not a real epSquare here
+ game.processString("Nf6");
+ game.processString("Nf3");
+ game.processString("Ng8");
+ game.processString("Ng1");
+ game.processString("Nf6");
+ game.processString("Nf3");
+ game.processString("Ng8");
+ game.processString("draw rep Ng1");
+ assertEquals(Game.GameState.DRAW_REP, game.getGameState());
+
+ // Now check the case when e3 *is* an epSquare
+ game.processString("new");
+ game.processString("Nf3");
+ game.processString("d5");
+ game.processString("Ng1");
+ game.processString("d4");
+ game.processString("e4"); // Here e3 is a real epSquare
+ game.processString("Nf6");
+ game.processString("Nf3");
+ game.processString("Ng8");
+ game.processString("Ng1");
+ game.processString("Nf6");
+ game.processString("Nf3");
+ game.processString("Ng8");
+ game.processString("draw rep Ng1");
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+
+ // EP capture not valid because it would leave the king in check. Therefore
+ // the position has been repeated three times at the end of the move sequence.
+ game.processString("setpos 4k2n/8/8/8/4p3/8/3P4/3KR2N w - - 0 1");
+ game.processString("d4");
+ game.processString("Ng6");
+ game.processString("Ng3");
+ game.processString("Nh8");
+ game.processString("Nh1");
+ game.processString("Ng6");
+ game.processString("Ng3");
+ game.processString("Nh8");
+ game.processString("draw rep Nh1");
+ assertEquals(Game.GameState.DRAW_REP, game.getGameState());
+ }
+
+ /**
+ * Test of resign command, of class Game.
+ */
+ @Test
+ public void testResign() {
+ System.out.println("resign");
+ Game game = new Game(new HumanPlayer(), new HumanPlayer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ game.processString("f3");
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ game.processString("resign");
+ assertEquals(Game.GameState.RESIGN_BLACK, game.getGameState());
+ game.processString("undo");
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ game.processString("f3");
+ game.processString("e5");
+ game.processString("resign");
+ assertEquals(Game.GameState.RESIGN_WHITE, game.getGameState());
+ game.processString("undo");
+ game.processString("e5");
+ game.processString("g4");
+ game.processString("Qh4");
+ assertEquals(Game.GameState.BLACK_MATE, game.getGameState());
+ game.processString("resign");
+ assertEquals(Game.GameState.BLACK_MATE, game.getGameState()); // Can't resign after game over
+ }
+
+ /**
+ * Test of processString method, of class Game.
+ */
+ @Test
+ public void testProcessString() throws ChessParseError {
+ System.out.println("processString");
+ Game game = new Game(new HumanPlayer(), new HumanPlayer());
+ assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos));
+ boolean res = game.processString("Nf3");
+ assertEquals(true, res);
+ assertEquals(1, game.pos.halfMoveClock);
+ assertEquals(1, game.pos.fullMoveCounter);
+ res = game.processString("d5");
+ assertEquals(true, res);
+ assertEquals(0, game.pos.halfMoveClock);
+ assertEquals(2, game.pos.fullMoveCounter);
+
+ res = game.processString("undo");
+ assertEquals(true, res);
+ assertEquals(1, game.pos.halfMoveClock);
+ assertEquals(1, game.pos.fullMoveCounter);
+ res = game.processString("undo");
+ assertEquals(true, res);
+ assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos));
+ res = game.processString("undo");
+ assertEquals(true, res);
+ assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos));
+
+ res = game.processString("redo");
+ assertEquals(true, res);
+ assertEquals(1, game.pos.halfMoveClock);
+ assertEquals(1, game.pos.fullMoveCounter);
+ res = game.processString("redo");
+ assertEquals(true, res);
+ assertEquals(0, game.pos.halfMoveClock);
+ assertEquals(2, game.pos.fullMoveCounter);
+ res = game.processString("redo");
+ assertEquals(true, res);
+ assertEquals(0, game.pos.halfMoveClock);
+ assertEquals(2, game.pos.fullMoveCounter);
+
+ res = game.processString("new");
+ assertEquals(true, res);
+ assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos));
+
+ String fen = "8/8/8/4k3/8/8/2p5/5K2 b - - 47 68";
+ Position pos = TextIO.readFEN(fen);
+ res = game.processString("setpos " + fen);
+ assertEquals(true, res);
+ assertEquals(pos, game.pos);
+
+ res = game.processString("junk");
+ assertEquals(false, res);
+ }
+
+ /**
+ * Test of getGameState method, of class Game.
+ */
+ @Test
+ public void testGetGameState() {
+ System.out.println("getGameState");
+ Game game = new Game(new HumanPlayer(), new HumanPlayer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ game.processString("f3");
+ game.processString("e5");
+ game.processString("g4");
+ game.processString("Qh4");
+ assertEquals(Game.GameState.BLACK_MATE, game.getGameState());
+
+ game.processString("setpos 5k2/5P2/5K2/8/8/8/8/8 b - - 0 1");
+ assertEquals(Game.GameState.BLACK_STALEMATE, game.getGameState());
+ }
+
+ /**
+ * Test of insufficientMaterial method, of class Game.
+ */
+ @Test
+ public void testInsufficientMaterial() {
+ System.out.println("insufficientMaterial");
+ Game game = new Game(new HumanPlayer(), new HumanPlayer());
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ game.processString("setpos 4k3/8/8/8/8/8/8/4K3 w - - 0 1");
+ assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState());
+ final int a1 = Position.getSquare(0, 0);
+ game.pos.setPiece(a1, Piece.WROOK);
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ game.pos.setPiece(a1, Piece.BQUEEN);
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ game.pos.setPiece(a1, Piece.WPAWN);
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ game.pos.setPiece(a1, Piece.BKNIGHT);
+ assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState());
+ game.pos.setPiece(a1, Piece.WBISHOP);
+ assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState());
+
+ final int c1 = Position.getSquare(2, 0);
+ game.pos.setPiece(c1, Piece.WKNIGHT);
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ game.pos.setPiece(c1, Piece.BBISHOP);
+ assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState());
+ game.pos.setPiece(c1, Piece.WBISHOP);
+ assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState());
+
+ final int b2 = Position.getSquare(1, 1);
+ game.pos.setPiece(b2, Piece.WBISHOP);
+ assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState());
+ game.pos.setPiece(b2, Piece.BBISHOP);
+ assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState());
+
+ final int b3 = Position.getSquare(1, 2);
+ game.pos.setPiece(b3, Piece.WBISHOP);
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+
+ // Can't force mate with KNNK, but still not an automatic draw.
+ game.processString("setpos 8/8/8/8/8/8/8/K3nnk1 w - - 0 1");
+ assertEquals(Game.GameState.ALIVE, game.getGameState());
+ }
+
+ /**
+ * Test of perfT method, of class Game.
+ */
+ @Test
+ public void testPerfT() {
+ System.out.println("perfT");
+ Game game = new Game(new HumanPlayer(), new HumanPlayer());
+ game.processString("new");
+ doTestPerfT(game.pos, 5, new long[]{20,400,8902,197281,4865609,119060324,3195901860L,84998978956L});
+
+ game.processString("setpos 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -");
+ doTestPerfT(game.pos, 5, new long[]{14, 191, 2812, 43238, 674624, 11030083, 178633661});
+
+ game.processString("setpos r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -");
+ doTestPerfT(game.pos, 4, new long[]{48,2039,97862,4085603,193690690});
+ }
+
+ private void doTestPerfT(Position pos, int maxDepth, long[] expectedNodeCounts) {
+ for (int d = 1; d <= maxDepth; d++) {
+ MoveGen moveGen = new MoveGen();
+ long t0 = System.nanoTime();
+ long nodes = Game.perfT(moveGen, pos, d);
+ long t1 = System.nanoTime();
+ System.out.printf("perft(%d) = %d, t=%.6fs\n", d, nodes, (t1 - t0)*1e-9);
+ assertEquals(expectedNodeCounts[d-1], nodes);
+ }
+ }
+}
diff --git a/CuckooChessEngine/test/chess/HistoryTest.java b/CuckooChessEngine/test/chess/HistoryTest.java
new file mode 100644
index 0000000..684a181
--- /dev/null
+++ b/CuckooChessEngine/test/chess/HistoryTest.java
@@ -0,0 +1,85 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class HistoryTest {
+
+ public HistoryTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() {
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ /**
+ * Test of getHistScore method, of class History.
+ */
+ @Test
+ public void testGetHistScore() throws ChessParseError {
+ System.out.println("getHistScore");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ History hs = new History();
+ Move m1 = TextIO.stringToMove(pos, "e4");
+ Move m2 = TextIO.stringToMove(pos, "d4");
+ assertEquals(0, hs.getHistScore(pos, m1));
+
+ hs.addSuccess(pos, m1, 1);
+ assertEquals(1 * 49 / 1, hs.getHistScore(pos, m1));
+ assertEquals(0, hs.getHistScore(pos, m2));
+
+ hs.addSuccess(pos, m1, 1);
+ assertEquals(1 * 49 / 1, hs.getHistScore(pos, m1));
+ assertEquals(0, hs.getHistScore(pos, m2));
+
+ hs.addFail(pos, m1, 1);
+ assertEquals(2 * 49 / 3, hs.getHistScore(pos, m1));
+ assertEquals(0, hs.getHistScore(pos, m2));
+
+ hs.addFail(pos, m1, 1);
+ assertEquals(2 * 49 / 4, hs.getHistScore(pos, m1));
+ assertEquals(0, hs.getHistScore(pos, m2));
+
+ hs.addSuccess(pos, m2, 1);
+ assertEquals(2 * 49 / 4, hs.getHistScore(pos, m1));
+ assertEquals(1 * 49 / 1, hs.getHistScore(pos, m2));
+ }
+}
diff --git a/CuckooChessEngine/test/chess/KillerTableTest.java b/CuckooChessEngine/test/chess/KillerTableTest.java
new file mode 100644
index 0000000..4b38b4e
--- /dev/null
+++ b/CuckooChessEngine/test/chess/KillerTableTest.java
@@ -0,0 +1,95 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class KillerTableTest {
+
+ public KillerTableTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /**
+ * Test of addKiller method, of class KillerTable.
+ */
+ @Test
+ public void testAddKiller() {
+ System.out.println("addKiller");
+ KillerTable kt = new KillerTable();
+ Move m = new Move(TextIO.getSquare("b1"), TextIO.getSquare("b5"), Piece.EMPTY);
+ kt.addKiller(3, m);
+ kt.addKiller(7, m);
+ kt.addKiller(3, m);
+ kt.addKiller(3, m);
+ }
+
+ /**
+ * Test of getKillerScore method, of class KillerTable.
+ */
+ @Test
+ public void testGetKillerScore() {
+ System.out.println("getKillerScore");
+ KillerTable kt = new KillerTable();
+ Move m1 = new Move(TextIO.getSquare("b1"), TextIO.getSquare("b5"), Piece.EMPTY);
+ Move m2 = new Move(TextIO.getSquare("c1"), TextIO.getSquare("d2"), Piece.EMPTY);
+ Move m3 = new Move(TextIO.getSquare("e1"), TextIO.getSquare("g1"), Piece.EMPTY);
+ kt.addKiller(0, m1);
+ assertEquals(4, kt.getKillerScore(0, m1));
+ assertEquals(0, kt.getKillerScore(0, m2));
+ assertEquals(0, kt.getKillerScore(0, new Move(m2)));
+ kt.addKiller(0, m1);
+ assertEquals(4, kt.getKillerScore(0, m1));
+ kt.addKiller(0, m2);
+ assertEquals(4, kt.getKillerScore(0, m2));
+ assertEquals(4, kt.getKillerScore(0, new Move(m2))); // Must compare by value
+ assertEquals(3, kt.getKillerScore(0, m1));
+ kt.addKiller(0, new Move(m2));
+ assertEquals(4, kt.getKillerScore(0, m2));
+ assertEquals(3, kt.getKillerScore(0, m1));
+ assertEquals(0, kt.getKillerScore(0, m3));
+ kt.addKiller(0, m3);
+ assertEquals(0, kt.getKillerScore(0, m1));
+ assertEquals(3, kt.getKillerScore(0, m2));
+ assertEquals(4, kt.getKillerScore(0, m3));
+
+ assertEquals(0, kt.getKillerScore(1, m3));
+ assertEquals(2, kt.getKillerScore(2, m3));
+ assertEquals(0, kt.getKillerScore(3, m3));
+ assertEquals(0, kt.getKillerScore(4, m3));
+
+ kt.addKiller(2, m2);
+ assertEquals(4, kt.getKillerScore(2, m2));
+ assertEquals(3, kt.getKillerScore(0, m2));
+ }
+}
diff --git a/CuckooChessEngine/test/chess/MoveGenTest.java b/CuckooChessEngine/test/chess/MoveGenTest.java
new file mode 100644
index 0000000..b7023e4
--- /dev/null
+++ b/CuckooChessEngine/test/chess/MoveGenTest.java
@@ -0,0 +1,543 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class MoveGenTest {
+
+ public MoveGenTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /**
+ * Test of pseudoLegalMoves method, of class MoveGen.
+ */
+ @Test
+ public void testPseudoLegalMoves() throws ChessParseError {
+ System.out.println("pseudoLegalMoves");
+ String fen = "8/3k4/8/2n2pP1/1P6/1NB5/2QP4/R3K2R w KQ f6 0 2";
+ Position pos = TextIO.readFEN(fen);
+ assertEquals(fen, TextIO.toFEN(pos));
+ List strMoves = getMoveList(pos, false);
+ assertTrue(strMoves.contains("a1d1"));
+ assertTrue(!strMoves.contains("a1e1"));
+ assertTrue(!strMoves.contains("a1f1"));
+ assertTrue(strMoves.contains("a1a7"));
+ assertTrue(strMoves.contains("e1f2"));
+ assertTrue(!strMoves.contains("e1g3"));
+ assertTrue(strMoves.contains("c3f6"));
+ assertTrue(!strMoves.contains("b3d2"));
+
+ // Test castling
+ assertTrue(strMoves.contains("e1g1"));
+ assertTrue(strMoves.contains("e1c1"));
+ assertEquals(49, strMoves.size());
+
+ pos.setPiece(Position.getSquare(4,3), Piece.BROOK);
+ strMoves = getMoveList(pos, false);
+ assertTrue(!strMoves.contains("e1g1")); // In check, no castling possible
+ assertTrue(!strMoves.contains("e1c1"));
+
+ pos.setPiece(Position.getSquare(4, 3), Piece.EMPTY);
+ pos.setPiece(Position.getSquare(5, 3), Piece.BROOK);
+ strMoves = getMoveList(pos, false);
+ assertTrue(!strMoves.contains("e1g1")); // f1 attacked, short castle not possible
+ assertTrue(strMoves.contains("e1c1"));
+
+ pos.setPiece(Position.getSquare(5, 3), Piece.EMPTY);
+ pos.setPiece(Position.getSquare(6, 3), Piece.BBISHOP);
+ strMoves = getMoveList(pos, false);
+ assertTrue(strMoves.contains("e1g1")); // d1 attacked, long castle not possible
+ assertTrue(!strMoves.contains("e1c1"));
+
+ pos.setPiece(Position.getSquare(6, 3), Piece.EMPTY);
+ pos.setCastleMask(1 << Position.A1_CASTLE);
+ strMoves = getMoveList(pos, false);
+ assertTrue(!strMoves.contains("e1g1")); // short castle right has been lost
+ assertTrue(strMoves.contains("e1c1"));
+ }
+
+ /**
+ * Test of pseudoLegalMoves method, of class MoveGen. Pawn moves.
+ */
+ @Test
+ public void testPawnMoves() throws ChessParseError {
+ System.out.println("pawnMoves");
+ String fen = "1r2k3/P1pppp1p/8/1pP3p1/1nPp2P1/n4p1P/1P2PP2/4KBNR w K b6 0 1";
+ Position pos = TextIO.readFEN(fen);
+ assertEquals(fen, TextIO.toFEN(pos));
+ List strMoves = getMoveList(pos, false);
+ assertTrue(strMoves.contains("c5b6")); // En passant capture
+ assertTrue(strMoves.contains("a7a8q")); // promotion
+ assertTrue(strMoves.contains("a7a8n")); // under promotion
+ assertTrue(strMoves.contains("a7b8r")); // capture promotion
+ assertTrue(strMoves.contains("b2b3")); // pawn single move
+ assertTrue(strMoves.contains("b2a3")); // pawn capture to the left
+ assertTrue(strMoves.contains("e2e4")); // pawn double move
+ assertTrue(strMoves.contains("e2f3")); // pawn capture to the right
+ assertEquals(22, strMoves.size());
+
+ pos.setEpSquare(-1);
+ strMoves = getMoveList(pos, false);
+ assertEquals(21, strMoves.size()); // No ep, one less move possible
+
+ // Check black pawn moves
+ pos.setWhiteMove(false);
+ strMoves = getMoveList(pos, false);
+ assertTrue(strMoves.contains("f3e2"));
+ assertTrue(strMoves.contains("d4d3"));
+ assertTrue(strMoves.contains("e7e6"));
+ assertTrue(strMoves.contains("e7e5"));
+ assertEquals(28, strMoves.size());
+
+ // Check black pawn promotion
+ pos.setPiece(Position.getSquare(0,1), Piece.BPAWN);
+ strMoves = getMoveList(pos, false);
+ assertTrue(strMoves.contains("a2a1q"));
+ assertTrue(strMoves.contains("a2a1r"));
+ assertTrue(strMoves.contains("a2a1n"));
+ assertTrue(strMoves.contains("a2a1b"));
+ }
+
+ /**
+ * Test of inCheck method, of class MoveGen.
+ */
+ @Test
+ public void testInCheck() {
+ System.out.println("inCheck");
+ Position pos = new Position();
+ pos.setPiece(Position.getSquare(4,2), Piece.WKING);
+ pos.setPiece(Position.getSquare(4,7), Piece.BKING);
+ assertEquals(false, MoveGen.inCheck(pos));
+
+ pos.setPiece(Position.getSquare(3,3), Piece.BQUEEN);
+ assertEquals(true, MoveGen.inCheck(pos));
+ pos.setPiece(Position.getSquare(3,3), Piece.BROOK);
+ assertEquals(false, MoveGen.inCheck(pos));
+ pos.setPiece(Position.getSquare(3,3), Piece.BPAWN);
+ assertEquals(true, MoveGen.inCheck(pos));
+
+ pos.setPiece(Position.getSquare(3,3), Piece.EMPTY);
+ pos.setPiece(Position.getSquare(5,3), Piece.WQUEEN);
+ assertEquals(false, MoveGen.inCheck(pos));
+
+ pos.setPiece(Position.getSquare(4, 6), Piece.BROOK);
+ assertEquals(true, MoveGen.inCheck(pos));
+ pos.setPiece(Position.getSquare(4, 4), Piece.WPAWN);
+ assertEquals(false, MoveGen.inCheck(pos));
+
+ pos.setPiece(Position.getSquare(2, 3), Piece.BKNIGHT);
+ assertEquals(true, MoveGen.inCheck(pos));
+
+ pos.setPiece(Position.getSquare(2, 3), Piece.EMPTY);
+ pos.setPiece(Position.getSquare(0, 4), Piece.BKNIGHT);
+ assertEquals(false, MoveGen.inCheck(pos));
+ }
+
+ /**
+ * Test of givesCheck method, of class MoveGen.
+ */
+ @Test
+ public void testGivesCheck() throws ChessParseError {
+ System.out.println("givesCheck");
+ Position pos = new Position();
+ UndoInfo ui = new UndoInfo();
+ pos.setPiece(TextIO.getSquare("e3"), Piece.WKING);
+ pos.setPiece(TextIO.getSquare("e8"), Piece.BKING);
+ pos.setPiece(TextIO.getSquare("c2"), Piece.WROOK);
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Rc8")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Rc6")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Rc7")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Re2")));
+
+ pos.setPiece(TextIO.getSquare("c2"), Piece.EMPTY);
+ pos.setPiece(TextIO.getSquare("e2"), Piece.WROOK);
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kd3")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kd4")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Ke4")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kf2")));
+
+ pos.setPiece(TextIO.getSquare("e4"), Piece.WBISHOP);
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Bd5")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Bc6")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kd3")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Re1")));
+
+ pos = TextIO.readFEN("4k3/3p4/8/8/4B3/2K5/4R3/8 w - - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Bc6")));
+ pos = TextIO.readFEN("4k3/8/5K2/8/6N1/8/8/8 w - - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Ke6")));
+ assertTrue(!MoveGen.givesCheck(pos, new Move(TextIO.getSquare("f6"),
+ TextIO.getSquare("e7"),
+ Piece.EMPTY)));
+
+ pos = TextIO.readFEN("8/2k5/8/4N3/8/2K3B1/8/8 w - - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kc4")));
+ pos.setPiece(TextIO.getSquare("g3"), Piece.WROOK);
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7")));
+ pos.setPiece(TextIO.getSquare("g3"), Piece.WQUEEN);
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7")));
+ pos.setPiece(TextIO.getSquare("g3"), Piece.WKNIGHT);
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7")));
+ pos.setPiece(TextIO.getSquare("g3"), Piece.WPAWN);
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7")));
+ pos.setPiece(TextIO.getSquare("c3"), Piece.EMPTY);
+ pos.setPiece(TextIO.getSquare("g3"), Piece.WKING);
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7")));
+
+ pos = TextIO.readFEN("8/2k5/3p4/4N3/8/2K3B1/8/8 w - - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7")));
+
+ pos = TextIO.readFEN("8/2k5/8/4N3/8/6q1/2K5/8 w - - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7")));
+ pos = TextIO.readFEN("8/2k5/8/4N3/8/8/2K5/8 w - - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7")));
+ pos = TextIO.readFEN("2nk4/3P4/8/8/3R4/8/2K5/8 w - - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc8N")));
+
+ pos = TextIO.readFEN("8/2k5/2p5/1P1P4/8/2K5/8/8 w - - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "d6")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "bxc6")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b6")));
+
+ pos = TextIO.readFEN("8/8/R1PkP2R/8/8/2K5/8/8 w - - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c7")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e7")));
+
+ // Test pawn promotion
+ pos = TextIO.readFEN("8/1P6/2kP4/8/8/2K5/8/8 w - - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "d7")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b8Q")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b8N")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b8R")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b8B")));
+
+ pos = TextIO.readFEN("8/2P1P3/2k5/8/8/2K5/8/8 w - - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e8Q")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e8N")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e8R")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e8B")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c8Q")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c8N")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c8R")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c8B")));
+
+ // Test castling
+ pos = TextIO.readFEN("8/8/8/8/5k2/8/8/R3K2R w KQ - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O")));
+ pos = TextIO.readFEN("8/8/8/8/6k1/8/8/R3K2R w KQ - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O")));
+ pos = TextIO.readFEN("8/8/8/8/3k4/8/8/R3K2R w KQ - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O")));
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O")));
+ pos = TextIO.readFEN("8/8/8/8/5k2/8/5P2/R3K2R w KQ - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O")));
+ pos = TextIO.readFEN("8/8/8/8/8/8/8/R3K2k w Q - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O")));
+ pos = TextIO.readFEN("8/8/8/8/8/8/8/2k1K2R w K - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O")));
+ pos.setPiece(TextIO.getSquare("d1"), Piece.WKNIGHT);
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O")));
+
+ // Test en passant
+ pos = TextIO.readFEN("8/1kp5/8/3P4/8/8/8/4K3 b - - 0 1");
+ pos.makeMove(TextIO.stringToMove(pos, "c5"), ui);
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6")));
+
+ pos = TextIO.readFEN("3k4/2p5/8/3P4/8/8/3R4/4K3 b - - 0 1");
+ pos.makeMove(TextIO.stringToMove(pos, "c5"), ui);
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6")));
+
+ pos = TextIO.readFEN("5k2/2p5/8/3P4/8/B7/8/4K3 b - - 0 1");
+ pos.makeMove(TextIO.stringToMove(pos, "c5"), ui);
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6")));
+
+ pos = TextIO.readFEN("5k2/2p5/8/3P4/1P6/B7/8/4K3 b - - 0 1");
+ pos.makeMove(TextIO.stringToMove(pos, "c5"), ui);
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6")));
+
+ pos = TextIO.readFEN("8/2p5/8/R2P1k2/8/8/8/4K3 b - - 0 1");
+ pos.makeMove(TextIO.stringToMove(pos, "c5"), ui);
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6")));
+
+ // Black pawn moves
+ pos = TextIO.readFEN("8/2p5/8/R4k2/1K6/8/8/8 b - - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c5")));
+ pos = TextIO.readFEN("8/2p5/8/R4k2/2K5/8/8/8 b - - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c5")));
+ pos = TextIO.readFEN("8/2p5/8/R4k2/3K4/8/8/8 b - - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c5")));
+
+ // Black castling
+ pos = TextIO.readFEN("r3k2r/8/8/5K2/8/8/8/8 b kq - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O")));
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O")));
+ pos = TextIO.readFEN("r3k2r/8/8/6K1/8/8/8/8 b kq - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O")));
+ pos = TextIO.readFEN("r3k2r/8/8/2K5/8/8/8/8 b kq - 0 1");
+ assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O")));
+ pos = TextIO.readFEN("r3k2r/8/8/3K4/8/8/8/8 b kq - 0 1");
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O")));
+
+ // Black en passant
+ pos = TextIO.readFEN("8/8/4k3/8/4p3/8/5PK1/8 w - - 0 1");
+ pos.makeMove(TextIO.stringToMove(pos, "f4"), ui);
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "exf3")));
+
+ pos = TextIO.readFEN("8/8/4k3/8/K3p1r1/8/5P2/8 w - - 0 1");
+ pos.makeMove(TextIO.stringToMove(pos, "f4"), ui);
+ assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "exf3")));
+ }
+
+ /**
+ * Test of removeIllegal method, of class MoveGen.
+ */
+ @Test
+ public void testRemoveIllegal() throws ChessParseError {
+ System.out.println("removeIllegal");
+ Position pos = TextIO.readFEN("8/3k4/8/2n1rpP1/1P6/1NB5/2QP4/R3K2R w KQ f6 0 1");
+ List strMoves = getMoveList(pos, true);
+ assertTrue(strMoves.contains("c2e4"));
+ assertTrue(strMoves.contains("c3e5"));
+ assertTrue(strMoves.contains("e1d1"));
+ assertTrue(strMoves.contains("e1f1"));
+ assertTrue(strMoves.contains("e1f2"));
+ assertEquals(5, strMoves.size());
+
+ pos = TextIO.readFEN("4k3/8/8/2KPp1r1/8/8/8/8 w - e6 0 2");
+ strMoves = getMoveList(pos, true);
+ assertTrue(!strMoves.contains("d5e6"));
+ assertEquals(7, strMoves.size());
+
+ pos = TextIO.readFEN("8/6p1/4p3/2k1Pp1B/4KP1p/6rP/8/8 w - f6 0 55");
+ strMoves = getMoveList(pos, true);
+ assertTrue(strMoves.contains("e5f6"));
+ assertEquals(1, strMoves.size());
+ }
+
+ /**
+ * Test that if king capture is possible, only a king capture move is returned in the move list.
+ */
+ @Test
+ public void testKingCapture() throws ChessParseError {
+ System.out.println("kingCapture");
+ Position pos = TextIO.readFEN("8/4k3/8/8/8/8/8/4RK2 b - - 0 1");
+ pos.setWhiteMove(true);
+ List strMoves = getMoveList(pos, false);
+ assertEquals(1, strMoves.size());
+ assertEquals("e1e7", strMoves.get(0));
+
+ pos.setPiece(Position.getSquare(0, 2), Piece.WBISHOP);
+ pos.setPiece(Position.getSquare(4, 1), Piece.WPAWN);
+ strMoves = getMoveList(pos, false);
+ assertEquals(1, strMoves.size());
+ assertEquals("a3e7", strMoves.get(0));
+
+ pos.setPiece(Position.getSquare(1, 3), Piece.WPAWN);
+ pos.setPiece(Position.getSquare(5, 5), Piece.WPAWN);
+ strMoves = getMoveList(pos, false);
+ assertEquals(1, strMoves.size());
+ assertEquals("f6e7", strMoves.get(0));
+ }
+
+ /** Test that captureList and captureAndcheckList are generated correctly. */
+ @Test
+ public void testCaptureList() throws ChessParseError {
+ System.out.println("captureList");
+ Position pos = TextIO.readFEN("rnbqkbnr/ppp2ppp/3p1p2/R7/4N3/8/PPPPQPPP/2B1KB1R w Kkq - 0 1");
+ getMoveList(pos, false);
+
+ pos = TextIO.readFEN("rnb1kbn1/ppp1qppp/5p2/4p3/3N3r/3P4/PPP2PPP/R1BQKB1R b KQq - 0 1");
+ getMoveList(pos, false);
+
+ pos = TextIO.readFEN("rnb1k1n1/ppp1qppp/5p2/b3p3/1r1N4/3P4/PPP2PPP/R1BQKB1R b KQq - 0 1");
+ getMoveList(pos, false);
+
+ pos = TextIO.readFEN("8/8/8/8/3k4/8/4P3/4K3 w - - 0 1");
+ getMoveList(pos, false);
+
+ pos = TextIO.readFEN("8/8/8/3k4/8/8/4P3/4K3 w - - 0 1");
+ getMoveList(pos, false);
+
+ pos = TextIO.readFEN("8/8/8/3k4/4p3/8/3KP3/8 b - - 0 1");
+ getMoveList(pos, false);
+
+ pos = TextIO.readFEN("3k4/r2p2K1/8/8/8/8/8/8 b - - 0 1");
+ getMoveList(pos, false);
+ }
+
+ @Test
+ public void testCheckEvasions() throws ChessParseError {
+ System.out.println("checkEvasions");
+ Position pos = TextIO.readFEN("n7/8/8/7k/5pP1/5K2/8/8 b - g3 0 1");
+ getMoveList(pos, false);
+
+ pos = TextIO.readFEN("rn1qkbnr/pppB1ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 0 1");
+ getMoveList(pos, false);
+
+ // King captures must be included in check evasions
+ pos = TextIO.readFEN("r1bq2r1/pp3pbk/2p1p1P1/8/3P4/2PB1N2/PP3PPR/2KR4 b - - 0 1");
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(TextIO.uciStringToMove("g7h6"), ui);
+ getMoveList(pos, false);
+ List evList = getCheckEvasions(pos, false);
+ assertTrue(evList.contains("g6h7"));
+
+ pos = TextIO.readFEN("1R6/1brk2p1/2P1p2p/p3Pp2/P7/6P1/1P4P1/2R3K1 b - - 0 1");
+ getMoveList(pos, false);
+ evList = getCheckEvasions(pos, false);
+ assertTrue(evList.contains("b7c6"));
+ }
+
+ private List getMoveList(Position pos, boolean onlyLegal) {
+ Position swap = EvaluateTest.swapColors(pos);
+ List swapList = getMoveList0(swap, onlyLegal);
+ List ret = getMoveList0(pos, onlyLegal);
+ assertEquals(swapList.size(), ret.size());
+ // FIXME! Test that swapList contains swapped moves compared to ret
+ return ret;
+ }
+
+ private List getMoveList0(Position pos, boolean onlyLegal) {
+ MoveGen moveGen = new MoveGen();
+ MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
+ if (onlyLegal)
+ MoveGen.removeIllegal(pos, moves);
+ ArrayList strMoves = new ArrayList();
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ String mStr = TextIO.moveToUCIString(m);
+ strMoves.add(mStr);
+ }
+
+ List capList1 = getCaptureList(pos, false, onlyLegal);
+ assertTrue(strMoves.containsAll(capList1));
+
+ List capList2 = getCaptureList(pos, true, onlyLegal);
+ assertTrue(strMoves.containsAll(capList2));
+
+ List evList = getCheckEvasions(pos, onlyLegal);
+ if (evList != null)
+ assertTrue(strMoves.containsAll(evList));
+ UndoInfo ui = new UndoInfo();
+ for (String sm : strMoves) {
+ Move m = TextIO.uciStringToMove(sm);
+ if (m != null) {
+ pos.makeMove(m, ui);
+ boolean invalid = MoveGen.canTakeKing(pos);
+ pos.unMakeMove(m, ui);
+ if (invalid) m = null;
+ }
+ if (m == null) // Move was illegal (but pseudo-legal)
+ continue;
+ boolean qProm = false; // Promotion types considered in qsearch
+ switch (m.promoteTo) {
+ case Piece.WQUEEN: case Piece.BQUEEN:
+ case Piece.WKNIGHT: case Piece.BKNIGHT:
+ case Piece.EMPTY:
+ qProm = true;
+ break;
+ default:
+ break;
+ }
+ if (!MoveGen.canTakeKing(pos) && MoveGen.givesCheck(pos, m)) {
+ if (qProm)
+ assertTrue(capList2.contains(sm));
+ } else {
+ switch (m.promoteTo) {
+ case Piece.WQUEEN: case Piece.BQUEEN:
+ case Piece.WKNIGHT: case Piece.BKNIGHT:
+ assertTrue(capList1.contains(sm)); // All queen/knight promotions
+ assertTrue(capList2.contains(sm)); // All queen/knight promotions
+ break;
+ case Piece.EMPTY:
+ break;
+ default:
+ assertTrue(!capList1.contains(sm)); // No rook/bishop promotions
+ assertTrue(!capList2.contains(sm)); // No rook/bishop promotions
+ break;
+ }
+ }
+ if (pos.getPiece(m.to) != Piece.EMPTY) {
+ if (qProm) {
+ assertTrue(capList1.contains(sm));
+ assertTrue(capList2.contains(sm));
+ }
+ }
+ if (evList != null) {
+ assertTrue(evList.contains(sm));
+ }
+ }
+
+ return strMoves;
+ }
+
+ private List getCaptureList(Position pos, boolean includeChecks, boolean onlyLegal) {
+ MoveGen.MoveList moves;
+ if (includeChecks) {
+ moves = new MoveGen().pseudoLegalCapturesAndChecks(pos);
+ } else {
+ moves = new MoveGen().pseudoLegalCaptures(pos);
+ }
+ if (onlyLegal)
+ MoveGen.removeIllegal(pos, moves);
+ ArrayList strMoves = new ArrayList();
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ String mStr = TextIO.moveToUCIString(m);
+ strMoves.add(mStr);
+ }
+ return strMoves;
+ }
+
+ private List getCheckEvasions(Position pos, boolean onlyLegal) {
+ if (!MoveGen.inCheck(pos))
+ return null;
+ MoveGen.MoveList moves = new MoveGen().checkEvasions(pos);
+ if (onlyLegal)
+ MoveGen.removeIllegal(pos, moves);
+ ArrayList strMoves = new ArrayList();
+ for (int mi = 0; mi < moves.size; mi++) {
+ Move m = moves.m[mi];
+ String mStr = TextIO.moveToUCIString(m);
+ strMoves.add(mStr);
+ }
+ return strMoves;
+ }
+}
diff --git a/CuckooChessEngine/test/chess/MoveTest.java b/CuckooChessEngine/test/chess/MoveTest.java
new file mode 100644
index 0000000..827ed29
--- /dev/null
+++ b/CuckooChessEngine/test/chess/MoveTest.java
@@ -0,0 +1,84 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class MoveTest {
+
+ public MoveTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() {
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ /**
+ * Test of move constructor, of class Move.
+ */
+ @Test
+ public void testMoveConstructor() {
+ System.out.println("MoveTest");
+ int f = Position.getSquare(4, 1);
+ int t = Position.getSquare(4, 3);
+ int p = Piece.WROOK;
+ Move move = new Move(f, t, p);
+ assertEquals(move.from, f);
+ assertEquals(move.to,t);
+ assertEquals(move.promoteTo, p);
+ }
+
+ /**
+ * Test of equals, of class Move.
+ */
+ @Test
+ public void testEquals() {
+ System.out.println("equals");
+ Move m1 = new Move(Position.getSquare(0, 6), Position.getSquare(1, 7), Piece.WROOK);
+ Move m2 = new Move(Position.getSquare(0, 6), Position.getSquare(0, 7), Piece.WROOK);
+ Move m3 = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WROOK);
+ Move m4 = new Move(Position.getSquare(0, 6), Position.getSquare(1, 7), Piece.WKNIGHT);
+ Move m5 = new Move(Position.getSquare(0, 6), Position.getSquare(1, 7), Piece.WROOK);
+ assertTrue(!m1.equals(m2));
+ assertTrue(!m1.equals(m3));
+ assertTrue(!m1.equals(m4));
+ assertTrue(m1.equals(m5));
+ }
+}
diff --git a/CuckooChessEngine/test/chess/PieceTest.java b/CuckooChessEngine/test/chess/PieceTest.java
new file mode 100644
index 0000000..b6cdd44
--- /dev/null
+++ b/CuckooChessEngine/test/chess/PieceTest.java
@@ -0,0 +1,54 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class PieceTest {
+
+ public PieceTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /**
+ * Test of isWhite method, of class Piece.
+ */
+ @Test
+ public void testIsWhite() {
+ System.out.println("isWhite");
+ assertEquals(false, Piece.isWhite(Piece.BBISHOP));
+ assertEquals(true , Piece.isWhite(Piece.WBISHOP));
+ assertEquals(true , Piece.isWhite(Piece.WKING));
+ assertEquals(false, Piece.isWhite(Piece.BKING));
+ }
+}
diff --git a/CuckooChessEngine/test/chess/PositionTest.java b/CuckooChessEngine/test/chess/PositionTest.java
new file mode 100644
index 0000000..fb1a08f
--- /dev/null
+++ b/CuckooChessEngine/test/chess/PositionTest.java
@@ -0,0 +1,444 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class PositionTest {
+
+ public PositionTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() {
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ /**
+ * Test of getPiece method, of class Position.
+ */
+ @Test
+ public void testGetPiece() throws ChessParseError {
+ System.out.println("getPiece");
+ Position pos = new Position();
+ int result = pos.getPiece(0);
+ assertEquals(result, Piece.EMPTY);
+
+ pos = TextIO.readFEN(TextIO.startPosFEN);
+ result = pos.getPiece(0);
+ assertEquals(result, Piece.WROOK);
+ for (int x = 0; x < 8; x++) {
+ for (int y = 0; y < 2; y++) {
+ int p1 = pos.getPiece(Position.getSquare(x, y));
+ int p2 = pos.getPiece(Position.getSquare(x, 7-y));
+ int bwDiff = Piece.BPAWN - Piece.WPAWN;
+ assertEquals(p2, p1 + bwDiff);
+ }
+ }
+ }
+
+ /**
+ * Test of getIndex method, of class Position.
+ */
+ @Test
+ public void testGetIndex() {
+ System.out.println("getIndex");
+ for (int x = 0; x < 8; x++) {
+ for (int y = 0; y < 8; y++) {
+ int sq = Position.getSquare(x, y);
+ int x2 = Position.getX(sq);
+ int y2 = Position.getY(sq);
+ assertEquals(x, x2);
+ assertEquals(y, y2);
+ }
+ }
+ }
+
+ /**
+ * Test of setPiece method, of class Position.
+ */
+ @Test
+ public void testSetPiece() {
+ System.out.println("setPiece");
+ Position instance = new Position();
+ assertEquals(Piece.EMPTY, instance.getPiece(Position.getSquare(0, 0)));
+ instance.setPiece(Position.getSquare(3, 4), Piece.WKING);
+ assertEquals(Piece.WKING, instance.getPiece(Position.getSquare(3, 4)));
+ }
+
+ /**
+ * Test of makeMove method, of class Position.
+ */
+ @Test
+ public void testMakeMove() throws ChessParseError {
+ System.out.println("makeMove");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ Position origPos = new Position(pos);
+ assertTrue(pos.equals(origPos));
+ Move move = new Move(Position.getSquare(4,1), Position.getSquare(4,3), Piece.EMPTY);
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(move, ui);
+ assertEquals(pos.whiteMove, false);
+ assertEquals(-1, pos.getEpSquare());
+ assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(4,1)));
+ assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(4,3)));
+ assertTrue(!pos.equals(origPos));
+ int castleMask = (1 << Position.A1_CASTLE) |
+ (1 << Position.H1_CASTLE) |
+ (1 << Position.A8_CASTLE) |
+ (1 << Position.H8_CASTLE);
+ assertEquals(castleMask,pos.getCastleMask());
+ pos.unMakeMove(move, ui);
+ assertEquals(pos.whiteMove, true);
+ assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(4,1)));
+ assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(4,3)));
+ assertTrue(pos.equals(origPos));
+
+ String fen = "r1bqk2r/2ppbppp/p1n2n2/1pP1p3/B3P3/5N2/PP1P1PPP/RNBQK2R w KQkq b6 0 2";
+ pos = TextIO.readFEN(fen);
+ assertEquals(fen, TextIO.toFEN(pos));
+ origPos = new Position(pos);
+ assertEquals(Position.getSquare(1,5), pos.getEpSquare());
+
+ // Test capture
+ move = new Move(Position.getSquare(0, 3), Position.getSquare(1,4), Piece.EMPTY);
+ pos.makeMove(move, ui);
+ assertEquals(-1, pos.getEpSquare());
+ assertEquals(Piece.WBISHOP, pos.getPiece(Position.getSquare(1,4)));
+ assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(0,3)));
+ pos.unMakeMove(move, ui);
+ assertTrue(pos.equals(origPos));
+
+ // Test castling
+ move = new Move(Position.getSquare(4, 0), Position.getSquare(6,0), Piece.EMPTY);
+ pos.makeMove(move, ui);
+ assertEquals(Piece.WROOK, pos.getPiece(Position.getSquare(5,0)));
+ assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(7,0)));
+ castleMask = (1 << Position.A8_CASTLE) |
+ (1 << Position.H8_CASTLE);
+ assertEquals(castleMask,pos.getCastleMask());
+ assertEquals(-1, pos.getEpSquare());
+ pos.unMakeMove(move, ui);
+ assertTrue(pos.equals(origPos));
+
+ // Test castling rights (king move)
+ move = new Move(Position.getSquare(4, 0), Position.getSquare(4,1), Piece.EMPTY);
+ pos.makeMove(move, ui);
+ castleMask = (1 << Position.A8_CASTLE) |
+ (1 << Position.H8_CASTLE);
+ assertEquals(castleMask,pos.getCastleMask());
+ assertEquals(-1, pos.getEpSquare());
+ pos.unMakeMove(move, ui);
+ assertTrue(pos.equals(origPos));
+
+ // Test castling rights (rook move)
+ move = new Move(Position.getSquare(7, 0), Position.getSquare(6,0), Piece.EMPTY);
+ pos.makeMove(move, ui);
+ castleMask = (1 << Position.A1_CASTLE) |
+ (1 << Position.A8_CASTLE) |
+ (1 << Position.H8_CASTLE);
+ assertEquals(castleMask,pos.getCastleMask());
+ assertEquals(-1, pos.getEpSquare());
+ pos.unMakeMove(move, ui);
+ assertTrue(pos.equals(origPos));
+
+ // Test en passant
+ move = new Move(Position.getSquare(2, 4), Position.getSquare(1,5), Piece.EMPTY);
+ pos.makeMove(move, ui);
+ assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(1,5)));
+ assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(2,4)));
+ assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,4)));
+ pos.unMakeMove(move, ui);
+ assertTrue(pos.equals(origPos));
+
+ // Test castling rights loss when rook captured
+ pos.setPiece(Position.getSquare(6,2), Piece.BKNIGHT);
+ pos.setWhiteMove(false);
+ Position origPos2 = new Position(pos);
+ move = new Move(Position.getSquare(6,2), Position.getSquare(7,0), Piece.EMPTY);
+ pos.makeMove(move, ui);
+ castleMask = (1 << Position.A1_CASTLE) |
+ (1 << Position.A8_CASTLE) |
+ (1 << Position.H8_CASTLE);
+ assertEquals(castleMask,pos.getCastleMask());
+ assertEquals(-1, pos.getEpSquare());
+ pos.unMakeMove(move, ui);
+ assertTrue(pos.equals(origPos2));
+ }
+
+ @Test
+ public void testCastleMask() throws ChessParseError {
+ System.out.println("castleMask");
+ Position pos = TextIO.readFEN("rnbqk1nr/pppp1ppp/8/4p3/4P3/2N2N2/PPPP1bPP/R1BQKB1R w KQkq - 0 1");
+ UndoInfo ui = new UndoInfo();
+ Move m = TextIO.stringToMove(pos, "Kxf2");
+ pos.makeMove(m, ui);
+ int castleMask = (1 << Position.A8_CASTLE) |
+ (1 << Position.H8_CASTLE);
+ assertEquals(castleMask, pos.getCastleMask());
+ }
+
+ /**
+ * Test of makeMove method, of class Position.
+ */
+ @Test
+ public void testPromotion() throws ChessParseError {
+ System.out.println("promotion");
+ String fen = "r1bqk2r/1Pppbppp/p1n2n2/2P1p3/B3P3/5N2/Pp1P1PPP/R1BQK2R w KQkq - 0 1";
+ Position pos = TextIO.readFEN(fen);
+ assertEquals(fen, TextIO.toFEN(pos));
+ Position origPos = new Position(pos);
+ assertEquals(origPos, pos);
+
+ Move move = new Move(Position.getSquare(1, 6), Position.getSquare(0,7), Piece.WQUEEN);
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(move, ui);
+ assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,6)));
+ assertEquals(Piece.WQUEEN, pos.getPiece(Position.getSquare(0,7)));
+ pos.unMakeMove(move, ui);
+ assertEquals(origPos, pos);
+
+ move = new Move(Position.getSquare(1, 6), Position.getSquare(1,7), Piece.WKNIGHT);
+ ui = new UndoInfo();
+ pos.makeMove(move, ui);
+ assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,6)));
+ assertEquals(Piece.WKNIGHT, pos.getPiece(Position.getSquare(1,7)));
+ pos.unMakeMove(move, ui);
+ assertEquals(origPos, pos);
+
+ pos.setWhiteMove(false);
+ origPos = new Position(pos);
+
+ move = new Move(Position.getSquare(1, 1), Position.getSquare(2, 0), Piece.BROOK);
+ ui = new UndoInfo();
+ pos.makeMove(move, ui);
+ assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,1)));
+ assertEquals(Piece.BROOK, pos.getPiece(Position.getSquare(2,0)));
+ pos.unMakeMove(move, ui);
+ assertEquals(origPos, pos);
+ }
+
+ /**
+ * Test move counters, of class Position.
+ */
+ @Test
+ public void testMoveCounters() throws ChessParseError {
+ System.out.println("moveCounters");
+ String fen = "r1bqk2r/2ppbppp/p1n2n2/1pP1p3/B3P3/5N2/PP1P1PPP/RNBQK2R w KQkq b6 0 7";
+ Position pos = TextIO.readFEN(fen);
+
+ Move move = TextIO.stringToMove(pos, "Nc3");
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(move, ui);
+ assertEquals(1, pos.halfMoveClock);
+ assertEquals(7, pos.fullMoveCounter);
+ pos.unMakeMove(move, ui);
+
+ move = TextIO.stringToMove(pos, "O-O");
+ pos.makeMove(move, ui);
+ assertEquals(1, pos.halfMoveClock); // Castling does not reset 50 move counter
+ assertEquals(7, pos.fullMoveCounter);
+ pos.unMakeMove(move, ui);
+
+ move = TextIO.stringToMove(pos, "a3");
+ pos.makeMove(move, ui);
+ assertEquals(0, pos.halfMoveClock); // Pawn move resets 50 move counter
+ assertEquals(7, pos.fullMoveCounter);
+ pos.unMakeMove(move, ui);
+
+ move = TextIO.stringToMove(pos, "Nxe5");
+ pos.makeMove(move, ui);
+ assertEquals(0, pos.halfMoveClock); // Capture move resets 50 move counter
+ assertEquals(7, pos.fullMoveCounter);
+ pos.unMakeMove(move, ui);
+
+ move = TextIO.stringToMove(pos, "cxb6");
+ pos.makeMove(move, ui);
+ assertEquals(0, pos.halfMoveClock); // EP capture move resets 50 move counter
+ assertEquals(7, pos.fullMoveCounter);
+ pos.unMakeMove(move, ui);
+
+ move = TextIO.stringToMove(pos, "Kf1");
+ pos.makeMove(move, ui);
+ assertEquals(1, pos.halfMoveClock); // Loss of castling rights does not reset 50 move counter
+ assertEquals(7, pos.fullMoveCounter);
+ pos.unMakeMove(move, ui);
+
+ Move firstMove = TextIO.stringToMove(pos, "Nc3");
+ UndoInfo firstUi = new UndoInfo();
+ pos.makeMove(move, firstUi);
+ move = TextIO.stringToMove(pos, "O-O");
+ pos.makeMove(move, ui);
+ assertEquals(2, pos.halfMoveClock);
+ assertEquals(8, pos.fullMoveCounter); // Black move increases fullMoveCounter
+ pos.unMakeMove(move, ui);
+ pos.unMakeMove(firstMove, firstUi);
+
+ fen = "8/8/8/4k3/8/8/2p5/5K2 b - - 47 68";
+ pos = TextIO.readFEN(fen);
+ move = TextIO.stringToMove(pos, "c1Q");
+ pos.makeMove(move, ui);
+ assertEquals(0, pos.halfMoveClock); // Pawn promotion resets 50 move counter
+ assertEquals(69, pos.fullMoveCounter);
+ }
+
+ /**
+ * Test of drawRuleEquals, of class Position.
+ */
+ @Test
+ public void testDrawRuleEquals() throws ChessParseError {
+ System.out.println("drawRuleEquals");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ Position origPos = new Position(pos);
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(TextIO.stringToMove(pos, "Nf3"), ui);
+ assertEquals(false, pos.drawRuleEquals(origPos));
+ pos.makeMove(TextIO.stringToMove(pos, "Nf6"), ui);
+ assertEquals(false, pos.drawRuleEquals(origPos));
+ pos.makeMove(TextIO.stringToMove(pos, "Ng1"), ui);
+ assertEquals(false, pos.drawRuleEquals(origPos));
+ pos.makeMove(TextIO.stringToMove(pos, "Ng8"), ui);
+ assertEquals(true, pos.drawRuleEquals(origPos));
+ assertEquals(false, pos.equals(origPos)); // Move counters have changed
+
+ String fen = "r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1";
+ pos = TextIO.readFEN(fen);
+ origPos = new Position(pos);
+ pos.makeMove(TextIO.stringToMove(pos, "Ke2"), ui);
+ assertEquals(false, pos.drawRuleEquals(origPos));
+ pos.makeMove(TextIO.stringToMove(pos, "Be7"), ui);
+ assertEquals(false, pos.drawRuleEquals(origPos));
+ pos.makeMove(TextIO.stringToMove(pos, "Ke1"), ui);
+ assertEquals(false, pos.drawRuleEquals(origPos));
+ pos.makeMove(TextIO.stringToMove(pos, "Bf8"), ui);
+ assertEquals(false, pos.drawRuleEquals(origPos)); // Not equal, castling rights lost
+
+ pos = TextIO.readFEN(TextIO.startPosFEN);
+ pos.makeMove(TextIO.stringToMove(pos, "c4"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "a6"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "c5"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "b5"), ui);
+ assertEquals(Position.getSquare(1, 5), pos.getEpSquare());
+ origPos = new Position(pos);
+ pos.makeMove(TextIO.stringToMove(pos, "Nc3"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Nc6"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Nb1"), ui);
+ pos.makeMove(TextIO.stringToMove(pos, "Nb8"), ui);
+ assertEquals(false, pos.drawRuleEquals(origPos)); // Not equal, en passant rights lost
+ }
+
+ /**
+ * Test of hashCode method, of class Position.
+ */
+ @Test
+ public void testHashCode() throws ChessParseError {
+ System.out.println("hashCode");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ long h1 = pos.zobristHash();
+ assertEquals(h1, pos.computeZobristHash());
+ UndoInfo ui = new UndoInfo();
+ Move move = TextIO.stringToMove(pos, "e4");
+ pos.makeMove(move, ui);
+ assertTrue(h1 != pos.zobristHash());
+ pos.unMakeMove(move, ui);
+ assertTrue(h1 == pos.zobristHash());
+
+ pos.setWhiteMove(!pos.whiteMove);
+ long h4 = pos.zobristHash();
+ assertEquals(h4, pos.computeZobristHash());
+ assertTrue(h1 != pos.zobristHash());
+ pos.setWhiteMove(!pos.whiteMove);
+ assertTrue(h1 == pos.zobristHash());
+
+ pos.setCastleMask(0);
+ assertTrue(h1 != pos.zobristHash());
+
+ pos = TextIO.readFEN("rnbqkbnr/pppp1ppp/8/2P1p3/8/8/PP1PPPPP/RNBQKBNR b KQkq - 0 1");
+ h1 = pos.zobristHash();
+ assertEquals(h1, pos.computeZobristHash());
+
+ String[] moves = {
+ "b5", "Nc3", "Nf6", "Nb1", "Ng8", "Nc3", "Nf6", "Nb1", "Ng8", "Nc3", "d5",
+ "cxd6", "Qxd6", "h4", "Be6", "h5", "Nc6", "h6", "o-o-o", "hxg7", "Nf6", "gxh8Q", "Be7"
+ };
+ List uiList = new ArrayList();
+ List hashList = new ArrayList();
+ List moveList = new ArrayList();
+ for (int i = 0; i < moves.length; i++) {
+ uiList.add(new UndoInfo());
+ Move m = TextIO.stringToMove(pos, moves[i]);
+ moveList.add(m);
+ pos.makeMove(m, uiList.get(i));
+ long h = pos.zobristHash();
+ assertEquals(h, pos.computeZobristHash());
+ hashList.add(h);
+ }
+ assertTrue(!hashList.get(0).equals(hashList.get(4)));
+ assertTrue(hashList.get(4).equals(hashList.get(8)));
+ for (int i = moves.length - 1; i >= 0; i--) {
+ pos.unMakeMove(moveList.get(i), uiList.get(i));
+ long h = pos.zobristHash();
+ assertEquals(h, pos.computeZobristHash());
+ assertEquals(h, i > 0 ? hashList.get(i - 1) : h1);
+ }
+ }
+
+ /**
+ * Test of getKingSq method, of class Position.
+ */
+ @Test
+ public void testGetKingSq() throws ChessParseError {
+ System.out.println("getKingSq");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ assertEquals(TextIO.getSquare("e1"), pos.getKingSq(true));
+ assertEquals(TextIO.getSquare("e8"), pos.getKingSq(false));
+ pos = TextIO.readFEN("r1bq1bnr/ppppkppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQ - 0 4");
+ assertEquals(TextIO.getSquare("e1"), pos.getKingSq(true));
+ assertEquals(TextIO.getSquare("e7"), pos.getKingSq(false));
+ UndoInfo ui = new UndoInfo();
+ pos.makeMove(TextIO.stringToMove(pos, "o-o"), ui);
+ assertEquals(TextIO.getSquare("g1"), pos.getKingSq(true));
+ assertEquals(TextIO.getSquare("e7"), pos.getKingSq(false));
+ pos.makeMove(TextIO.stringToMove(pos, "Kd6"), ui);
+ assertEquals(TextIO.getSquare("g1"), pos.getKingSq(true));
+ assertEquals(TextIO.getSquare("d6"), pos.getKingSq(false));
+ }
+}
diff --git a/CuckooChessEngine/test/chess/SearchTest.java b/CuckooChessEngine/test/chess/SearchTest.java
new file mode 100644
index 0000000..2c4bfc3
--- /dev/null
+++ b/CuckooChessEngine/test/chess/SearchTest.java
@@ -0,0 +1,461 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import chess.Search.StopSearch;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class SearchTest {
+ static final long[] nullHist = new long[200];
+ static TranspositionTable tt = new TranspositionTable(19);
+
+ public SearchTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /**
+ * Test of negaScout method, of class Search.
+ */
+ @Test
+ public void testNegaScout() throws ChessParseError, StopSearch {
+ System.out.println("negaScout");
+ final int mate0 = Search.MATE0;
+
+ Position pos = TextIO.readFEN("3k4/8/3K2R1/8/8/8/8/8 w - - 0 1");
+ Search sc = new Search(pos, nullHist, 0, tt);
+ final int plyScale = Search.plyScale;
+ int score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos));
+ assertEquals(mate0 - 2, score); // depth 2 is enough to find mate in 1
+ int score2 = idSearch(sc, 2).score;
+ assertEquals(score, score2);
+
+ pos = TextIO.readFEN("8/1P6/k7/2K5/8/8/8/8 w - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ score = sc.negaScout(-mate0, mate0, 0, 4*plyScale, -1, MoveGen.inCheck(pos));
+ assertEquals(mate0 - 4, score); // depth 4 is enough to find mate in 2
+ score2 = idSearch(sc, 4).score;
+ assertEquals(score, score2);
+
+ pos = TextIO.readFEN("8/5P1k/5K2/8/8/8/8/8 w - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ score = sc.negaScout(-mate0, mate0, 0, 5*plyScale, -1, MoveGen.inCheck(pos));
+ assertEquals(mate0 - 4, score); // must avoid stale-mate after f8Q
+ score2 = idSearch(sc, 5).score;
+ assertEquals(score, score2);
+
+ pos = TextIO.readFEN("4k3/8/3K1Q2/8/8/8/8/8 b - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos));
+ assertEquals(0, score); // Position is stale-mate
+
+ pos = TextIO.readFEN("3kB3/8/1N1K4/8/8/8/8/8 w - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ score = sc.negaScout(-mate0, mate0, 0, 3*plyScale, -1, MoveGen.inCheck(pos));
+ assertTrue(Math.abs(score) < 50); // Stale-mate trap
+ score2 = idSearch(sc, 5).score;
+ assertEquals(score, score2);
+
+ pos = TextIO.readFEN("8/8/2K5/3QP3/P6P/1q6/8/k7 w - - 31 51");
+ sc = new Search(pos, nullHist, 0, tt);
+ Move bestM = idSearch(sc, 2);
+ assertTrue(!TextIO.moveToString(pos, bestM, false).equals("Qxb3"));
+ }
+
+ /**
+ * Test of draw by 50 move rule, of class Search.
+ */
+ @Test
+ public void testDraw50() throws ChessParseError, StopSearch {
+ System.out.println("draw50");
+ final int mate0 = Search.MATE0;
+ final int mateInOne = mate0 - 2;
+ final int matedInOne = -mate0 + 3;
+ final int mateInTwo = mate0 - 4;
+ final int mateInThree = mate0 - 6;
+
+ Position pos = TextIO.readFEN("8/1R2k3/R7/8/8/8/8/1K6 b - - 0 1");
+ Search sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ final int plyScale = Search.plyScale;
+ int score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos));
+ assertEquals(matedInOne, score);
+
+ pos = TextIO.readFEN("8/1R2k3/R7/8/8/8/8/1K6 b - - 99 80");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos));
+ assertEquals(0, score); // Draw by 50-move rule
+
+ pos = TextIO.readFEN("8/1R2k3/R7/8/8/8/8/1K6 b - - 98 80");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos));
+ assertEquals(matedInOne, score); // No draw
+
+ pos = TextIO.readFEN("8/1R2k3/R7/8/8/8/8/1K6 b - - 99 80");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = idSearch(sc, 3).score;
+ assertEquals(0, score);
+
+ pos = TextIO.readFEN("3k4/1R6/R7/8/8/8/8/1K6 w - - 100 80");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = idSearch(sc, 2).score;
+ assertEquals(mateInOne, score); // Black forgot to claim draw. Now it's too late.
+
+ pos = TextIO.readFEN("8/7k/1R6/R7/8/7P/8/1K6 w - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = idSearch(sc, 3).score;
+ assertEquals(mateInTwo, score);
+
+ pos = TextIO.readFEN("8/7k/1R6/R7/8/7P/8/1K6 w - - 98 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = idSearch(sc, 6).score;
+ assertEquals(mateInThree, score); // Need an extra pawn move to avoid 50-move rule
+
+ pos = TextIO.readFEN("8/7k/1R6/R7/8/7P/8/1K6 w - - 125 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = idSearch(sc, 6).score;
+ assertEquals(mateInThree, score); // Need an extra pawn move to avoid 50-move rule
+ }
+
+ /**
+ * Test of draw by repetition rule, of class Search.
+ */
+ @Test
+ public void testDrawRep() throws ChessParseError, StopSearch {
+ System.out.println("drawRep");
+ final int mate0 = Search.MATE0;
+ Position pos = TextIO.readFEN("7k/5RR1/8/8/8/8/q3q3/2K5 w - - 0 1");
+ Search sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ final int plyScale = Search.plyScale;
+ int score = sc.negaScout(-mate0, mate0, 0, 3*plyScale, -1, MoveGen.inCheck(pos));
+ assertEquals(0, score);
+
+ pos = TextIO.readFEN("7k/5RR1/8/8/8/8/q3q3/2K5 w - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = idSearch(sc, 3).score;
+ assertEquals(0, score);
+
+ pos = TextIO.readFEN("7k/5RR1/8/8/8/8/1q3q2/3K4 w - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = idSearch(sc, 3).score;
+ assertTrue(score < 0);
+
+ pos = TextIO.readFEN("7k/5RR1/8/8/8/8/1q3q2/3K4 w - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = sc.negaScout(-mate0, mate0, 0, 3*plyScale, -1, MoveGen.inCheck(pos));
+ assertTrue(score < 0);
+
+ pos = TextIO.readFEN("qn6/qn4k1/pp3R2/5R2/8/8/8/K7 w - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ sc.maxTimeMillis = -1;
+ score = idSearch(sc, 7).score;
+ assertEquals(0, score); // Draw, black can not escape from perpetual checks
+ }
+
+ /**
+ * Test of hash table, of class Search.
+ */
+ @Test
+ public void testHashing() throws ChessParseError {
+ System.out.println("hashing");
+ Position pos = TextIO.readFEN("/k/3p/p2P1p/P2P1P///K/ w - -"); // Fine #70
+ Search sc = new Search(pos, nullHist, 0, tt);
+ Move bestM = idSearch(sc, 28);
+ assertEquals(TextIO.stringToMove(pos, "Kb1"), new Move(bestM));
+ }
+
+ @Test
+ public void testCheckEvasion() throws ChessParseError {
+ System.out.println("check evasion");
+ Position pos = TextIO.readFEN("6r1/R5PK/2p5/1k6/8/8/p7/8 b - - 0 62");
+ Search sc = new Search(pos, nullHist, 0, tt);
+ Move bestM = idSearch(sc, 3);
+ assertTrue(bestM.score < 0);
+
+ pos = TextIO.readFEN("r1bq2rk/pp3pbp/2p1p1pQ/7P/3P4/2PB1N2/PP3PPR/2KR4 w - -"); // WAC 004
+ sc = new Search(pos, nullHist, 0, tt);
+ bestM = idSearch(sc, 1);
+ assertEquals(Search.MATE0 - 4, bestM.score);
+ assertEquals(TextIO.stringToMove(pos, "Qxh7+"), new Move(bestM));
+ }
+
+ @Test
+ public void testStalemateTrap() throws ChessParseError {
+ System.out.println("stalemate trap");
+ Position pos = TextIO.readFEN("7k/1P3R1P/6r1/5K2/8/8/6R1/8 b - - 98 194");
+ Search sc = new Search(pos, nullHist, 0, tt);
+ Move bestM = idSearch(sc, 3);
+ assertEquals(0, bestM.score);
+ }
+
+ @Test
+ public void testKQKRNullMove() throws ChessParseError {
+ System.out.println("kqkrNullMove");
+ Position pos = TextIO.readFEN("7K/6R1/5k2/3q4/8/8/8/8 b - - 0 1");
+ Search sc = new Search(pos, nullHist, 0, tt);
+ Move bestM = idSearch(sc, 9);
+ assertEquals(Search.MATE0-18, bestM.score);
+ }
+
+ private Move idSearch(Search sc, int maxDepth) {
+ MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(sc.pos);
+ MoveGen.removeIllegal(sc.pos, moves);
+ sc.scoreMoveList(moves, 0);
+ sc.timeLimit(-1, -1);
+ Move bestM = sc.iterativeDeepening(moves, maxDepth, -1, false);
+ return bestM;
+ }
+
+ /** Compute SEE(m) and assure that signSEE and negSEE give matching results. */
+ private int getSEE(Search sc, Move m) {
+ int see = sc.SEE(m);
+ boolean neg = sc.negSEE(m);
+ assertEquals(neg, see < 0);
+ int sign = sc.signSEE(m);
+ if (sign > 0)
+ assertTrue(see > 0);
+ else if (sign == 0)
+ assertEquals(0, see);
+ else
+ assertTrue(see < 0);
+ return see;
+ }
+
+ /**
+ * Test of SEE method, of class Search.
+ */
+ @Test
+ public void testSEE() throws ChessParseError {
+ System.out.println("SEE");
+ final int pV = Evaluate.pV;
+ final int nV = Evaluate.nV;
+ final int bV = Evaluate.bV;
+ final int rV = Evaluate.rV;
+
+ // Basic tests
+ Position pos = TextIO.readFEN("r2qk2r/ppp2ppp/1bnp1nb1/1N2p3/3PP3/1PP2N2/1P3PPP/R1BQRBK1 w kq - 0 1");
+ Search sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "dxe5")));
+ assertEquals(pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxe5")));
+ assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa7")));
+ assertEquals(pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxa7")));
+ assertEquals(pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxd6")));
+ assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "d5")));
+ assertEquals(-bV, getSEE(sc, TextIO.stringToMove(pos, "Bf4")));
+ assertEquals(-bV, getSEE(sc, TextIO.stringToMove(pos, "Bh6")));
+ assertEquals(-rV, getSEE(sc, TextIO.stringToMove(pos, "Ra5")));
+ assertEquals(-rV, getSEE(sc, TextIO.stringToMove(pos, "Ra6")));
+
+ pos.setWhiteMove(false);
+ sc = new Search(pos, nullHist, 0, tt);
+ assertTrue(nV <= bV); // Assumed by following test
+ assertEquals(pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxd4")));
+ assertEquals(pV - bV, getSEE(sc, TextIO.stringToMove(pos, "Bxd4")));
+ assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "exd4")));
+ assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Nxe4")));
+ assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Bxe4")));
+ assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "d5")));
+ assertEquals(-nV, getSEE(sc, TextIO.stringToMove(pos, "Nd5")));
+ assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "a6")));
+
+ // Test X-ray attacks
+ pos = TextIO.readFEN("3r2k1/pp1q1ppp/1bnr1nb1/1Np1p3/1P1PP3/2P1BN2/1Q1R1PPP/3R1BK1 b - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ // 1 1 1 1 3 3 3 3 3 5 5 9 5 5
+ // 0 1 0 1 0 3 0 3 0 5 0 9 0 5
+ assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "exd4")));
+ // 1 3 1 1 3 1 3 3 5 5 5 9 9
+ //-1 2 1 0 3 0 3 0 5 0 5 0 9
+ assertEquals(2 * pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxd4")));
+
+ pos.setPiece(TextIO.getSquare("b2"), Piece.EMPTY); // Remove white queen
+ sc = new Search(pos, nullHist, 0, tt);
+ // 1 1 1 1 3 3 3 3 3 5 5 9 5
+ // 0 1 0 1 0 3 0 3 0 4 1 4 5
+ assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "exd4")));
+ assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "cxb4")));
+
+ pos.setPiece(TextIO.getSquare("b5"), Piece.EMPTY); // Remove white knight
+ sc = new Search(pos, nullHist, 0, tt);
+ // 1 1 1 1 3 3 3 3 5 5 5
+ // 1 0 1 0 3 0 3 0 5 0 5
+ assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "exd4")));
+
+ pos.setPiece(TextIO.getSquare("b2"), Piece.WQUEEN); // Restore white queen
+ sc = new Search(pos, nullHist, 0, tt);
+ // 1 1 1 1 3 3 3 3 5 5 5 9 9
+ // 1 0 1 0 3 0 3 0 5 0 5 0 9
+ assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "exd4")));
+
+ pos.setPiece(TextIO.getSquare("b6"), Piece.EMPTY); // Remove black bishop
+ pos.setPiece(TextIO.getSquare("c6"), Piece.EMPTY); // Remove black knight
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(-pV, getSEE(sc, TextIO.stringToMove(pos, "a5")));
+
+ // Test EP capture
+ pos = TextIO.readFEN("2b3k1/1p3ppp/8/pP6/8/2PB4/5PPP/6K1 w - a6 0 2");
+ sc = new Search(pos, nullHist, 0, tt);
+ // 1 1 1 3
+ // 0 1 0 3
+ assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "bxa6")));
+
+ pos.setPiece(TextIO.getSquare("b7"), Piece.EMPTY); // Remove black pawn
+ sc = new Search(pos, nullHist, 0, tt);
+ // 1 1 3
+ // 1 0 3
+ assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "bxa6")));
+
+
+ // Test king capture
+ pos = TextIO.readFEN("8/8/8/4k3/r3P3/4K3/8/4R3 b - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ // 1 5 99
+ // 1 0 99
+ assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Rxe4+")));
+
+ pos = TextIO.readFEN("8/8/8/4k3/r3P1R1/4K3/8/8 b - - 0 1");
+ final int kV = Evaluate.kV;
+ sc = new Search(pos, nullHist, 0, tt);
+ // 1 5 5 99
+ //-4 5 0 99
+ assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxe4+")));
+ // 1 99
+ //-98 99
+ assertEquals(pV - kV, getSEE(sc, new Move(TextIO.getSquare("e5"), TextIO.getSquare("e4"), Piece.EMPTY)));
+
+ pos = TextIO.readFEN("8/8/4k3/8/r3P3/4K3/8/8 b - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxe4+")));
+
+ // Test king too far away
+ pos = TextIO.readFEN("8/8/4k3/8/r3P3/8/4K3/8 b - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Rxe4+")));
+
+ pos = TextIO.readFEN("8/3k4/8/8/r1K5/8/8/2R5 w - - 0 1");
+ pos.setWhiteMove(false);
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(kV, getSEE(sc, new Move(TextIO.getSquare("a4"), TextIO.getSquare("c4"), Piece.EMPTY)));
+
+
+ // Test blocking pieces
+ pos = TextIO.readFEN("r7/p2k4/8/r7/P7/8/4K3/R7 b - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); // Ra8 doesn't help
+
+ pos.setPiece(TextIO.getSquare("a7"), Piece.BBISHOP);
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); // Ra8 doesn't help
+
+ pos.setPiece(TextIO.getSquare("a7"), Piece.BPAWN);
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); // Ra8 doesn't help
+
+ pos.setPiece(TextIO.getSquare("a7"), Piece.BQUEEN);
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); // Ra8 does help
+
+ pos = TextIO.readFEN("8/3k4/R7/r7/P7/8/4K3/8 b - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4")));
+
+ pos = TextIO.readFEN("Q7/q6k/R7/r7/P7/8/4K3/8 b - - 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4")));
+
+ pos = TextIO.readFEN("8/3k4/5R2/8/4pP2/8/8/3K4 b - f3 0 1");
+ sc = new Search(pos, nullHist, 0, tt);
+ int score1 = EvaluateTest.evalWhite(sc.pos);
+ long h1 = sc.pos.zobristHash();
+ assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "exf3")));
+ int score2 = EvaluateTest.evalWhite(sc.pos);
+ long h2 = sc.pos.zobristHash();
+ assertEquals(score1, score2);
+ assertEquals(h1, h2);
+ }
+
+ /**
+ * Test of scoreMoveList method, of class Search.
+ */
+ @Test
+ public void testScoreMoveList() throws ChessParseError {
+ System.out.println("SEEorder");
+ Position pos = TextIO.readFEN("r2qk2r/ppp2ppp/1bnp1nb1/1N2p3/3PP3/1PP2N2/1P3PPP/R1BQRBK1 w kq - 0 1");
+ Search sc = new Search(pos, nullHist, 0, tt);
+ MoveGen moveGen = new MoveGen();
+ MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
+ sc.scoreMoveList(moves, 0);
+ for (int i = 0; i < moves.size; i++) {
+ Search.selectBest(moves, i);
+ if (i > 0) {
+ int sc1 = moves.m[i - 1].score;
+ int sc2 = moves.m[i].score;
+ assertTrue(sc2 <= sc1);
+ }
+ }
+
+ moves = moveGen.pseudoLegalMoves(pos);
+ moves.m[0].score = 17;
+ moves.m[1].score = 666;
+ moves.m[2].score = 4711;
+ sc.scoreMoveList(moves, 0, 2);
+ assertEquals(17, moves.m[0].score);
+ assertEquals(666, moves.m[1].score);
+ for (int i = 1; i < moves.size; i++) {
+ Search.selectBest(moves, i);
+ if (i > 1) {
+ int sc1 = moves.m[i - 1].score;
+ int sc2 = moves.m[i].score;
+ assertTrue(sc2 <= sc1);
+ }
+ }
+
+ // The hashMove should be first in the list
+ Move m = TextIO.stringToMove(pos, "Ra6");
+ moves = moveGen.pseudoLegalMoves(pos);
+ boolean res = Search.selectHashMove(moves, m);
+ assertEquals(true, res);
+ assertEquals(m, moves.m[0]);
+ }
+}
diff --git a/CuckooChessEngine/test/chess/TextIOTest.java b/CuckooChessEngine/test/chess/TextIOTest.java
new file mode 100644
index 0000000..539bbc0
--- /dev/null
+++ b/CuckooChessEngine/test/chess/TextIOTest.java
@@ -0,0 +1,398 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class TextIOTest {
+
+ public TextIOTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() {
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ /**
+ * Test of readFEN method, of class TextIO.
+ */
+ @Test
+ public void testReadFEN() throws ChessParseError {
+ System.out.println("readFEN");
+ String fen = "rnbqk2r/1p3ppp/p7/1NpPp3/QPP1P1n1/P4N2/4KbPP/R1B2B1R b kq - 0 1";
+ Position pos = TextIO.readFEN(fen);
+ assertEquals(fen, TextIO.toFEN(pos));
+ assertEquals(pos.getPiece(Position.getSquare(0, 3)), Piece.WQUEEN);
+ assertEquals(pos.getPiece(Position.getSquare(4, 7)), Piece.BKING);
+ assertEquals(pos.getPiece(Position.getSquare(4, 1)), Piece.WKING);
+ assertEquals(pos.whiteMove, false);
+ assertEquals(pos.a1Castle(), false);
+ assertEquals(pos.h1Castle(), false);
+ assertEquals(pos.a8Castle(), true);
+ assertEquals(pos.h8Castle(), true);
+
+ fen = "8/3k4/8/5pP1/1P6/1NB5/2QP4/R3K2R w KQ f6 1 2";
+ pos = TextIO.readFEN(fen);
+ assertEquals(fen, TextIO.toFEN(pos));
+ assertEquals(1, pos.halfMoveClock);
+ assertEquals(2, pos.fullMoveCounter);
+
+ // Must have exactly one king
+ boolean wasError = testFENParseError("8/8/8/8/8/8/8/kk1K4 w - - 0 1");
+ assertEquals(true, wasError);
+
+ // Must not be possible to capture the king
+ wasError = testFENParseError("8/8/8/8/8/8/8/k1RK4 w - - 0 1");
+ assertEquals(true, wasError);
+
+ // Make sure bogus en passant square information is removed
+ fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1";
+ pos = TextIO.readFEN(fen);
+ assertEquals(-1, pos.getEpSquare());
+
+ // Test for too many rows (slashes)
+ wasError = testFENParseError("8/8/8/8/4k3/8/8/8/KBN5 w - - 0 1");
+ assertEquals(true, wasError);
+
+ // Test for too many columns
+ wasError = testFENParseError("8K/8/8/8/4k3/8/8/8 w - - 0 1");
+ assertEquals(true, wasError);
+
+ // Pawns must not be on first/last rank
+ wasError = testFENParseError("kp6/8/8/8/8/8/8/K7 w - - 0 1");
+ assertEquals(true, wasError);
+
+ wasError = testFENParseError("kr/pppp/8/8/8/8/8/KBR w");
+ assertEquals(false, wasError); // OK not to specify castling flags and ep square
+
+ wasError = testFENParseError("k/8/8/8/8/8/8/K");
+ assertEquals(true, wasError); // Error side to move not specified
+
+ wasError = testFENParseError("");
+ assertEquals(true, wasError);
+
+ wasError = testFENParseError(" |");
+ assertEquals(true, wasError);
+
+ wasError = testFENParseError("1B1B4/6k1/7r/7P/6q1/r7/q7/7K b - - acn 6; acs 0;");
+ assertEquals(false, wasError); // Extra stuff after FEN string is allowed
+ }
+
+ /** Tests if trying to parse a FEN string causes an error. */
+ private boolean testFENParseError(String fen) {
+ boolean wasError;
+ wasError = false;
+ try {
+ TextIO.readFEN(fen);
+ } catch (ChessParseError err) {
+ wasError = true;
+ }
+ return wasError;
+ }
+
+ /**
+ * Test of moveToString method, of class TextIO.
+ */
+ @Test
+ public void testMoveToString() throws ChessParseError {
+ System.out.println("moveToString");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ assertEquals(TextIO.startPosFEN, TextIO.toFEN(pos));
+ Move move = new Move(Position.getSquare(4, 1), Position.getSquare(4, 3),
+ Piece.EMPTY);
+ boolean longForm = true;
+ String result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("e2-e4", result);
+
+ move = new Move(Position.getSquare(6, 0), Position.getSquare(5, 2), Piece.EMPTY);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("Ng1-f3", result);
+
+ move = new Move(Position.getSquare(4, 7), Position.getSquare(2, 7),
+ Piece.EMPTY);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("O-O-O", result);
+
+ String fen = "1r3k2/2P5/8/8/8/4K3/8/8 w - - 0 1";
+ pos = TextIO.readFEN(fen);
+ assertEquals(fen, TextIO.toFEN(pos));
+ move = new Move(Position.getSquare(2,6), Position.getSquare(1,7), Piece.WROOK);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("c7xb8R+", result);
+
+ move = new Move(Position.getSquare(2,6), Position.getSquare(2,7), Piece.WKNIGHT);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("c7-c8N", result);
+
+ move = new Move(Position.getSquare(2,6), Position.getSquare(2,7), Piece.WQUEEN);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("c7-c8Q+", result);
+ }
+
+ /**
+ * Test of moveToString method, of class TextIO, mate/stalemate tests.
+ */
+ @Test
+ public void testMoveToStringMate() throws ChessParseError {
+ System.out.println("moveToStringMate");
+ Position pos = TextIO.readFEN("3k4/1PR5/3N4/8/4K3/8/8/8 w - - 0 1");
+ boolean longForm = true;
+
+ Move move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WROOK);
+ String result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("b7-b8R+", result); // check
+
+ move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WQUEEN);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("b7-b8Q#", result); // check mate
+
+ move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WKNIGHT);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("b7-b8N", result);
+
+ move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WBISHOP);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("b7-b8B", result); // stalemate
+ }
+
+ /**
+ * Test of moveToString method, of class TextIO, short form.
+ */
+ @Test
+ public void testMoveToStringShortForm() throws ChessParseError {
+ System.out.println("moveToStringShortForm");
+ String fen = "r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1";
+ Position pos = TextIO.readFEN(fen);
+ assertEquals(fen, TextIO.toFEN(pos));
+ boolean longForm = false;
+
+ Move move = new Move(Position.getSquare(4,5), Position.getSquare(4,3), Piece.EMPTY);
+ String result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("Qee4", result); // File disambiguation needed
+
+ move = new Move(Position.getSquare(2,5), Position.getSquare(4,3), Piece.EMPTY);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("Qc6e4", result); // Full disambiguation needed
+
+ move = new Move(Position.getSquare(2,3), Position.getSquare(4,3), Piece.EMPTY);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("Q4e4", result); // Row disambiguation needed
+
+ move = new Move(Position.getSquare(2,3), Position.getSquare(2,0), Piece.EMPTY);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("Qc1+", result); // No disambiguation needed
+
+ move = new Move(Position.getSquare(0,1), Position.getSquare(0,0), Piece.BQUEEN);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("a1Q", result); // Normal promotion
+
+ move = new Move(Position.getSquare(0,1), Position.getSquare(1,0), Piece.BQUEEN);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("axb1Q#", result); // Capture promotion and check mate
+
+ move = new Move(Position.getSquare(0,1), Position.getSquare(1,0), Piece.BKNIGHT);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("axb1N", result); // Capture promotion
+
+ move = new Move(Position.getSquare(3,6), Position.getSquare(4,4), Piece.EMPTY);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("Ne5", result); // Other knight pinned, no disambiguation needed
+
+ move = new Move(Position.getSquare(7,6), Position.getSquare(7,4), Piece.EMPTY);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("h5", result); // Regular pawn move
+
+ move = new Move(Position.getSquare(5,7), Position.getSquare(3,7), Piece.EMPTY);
+ result = TextIO.moveToString(pos, move, longForm);
+ assertEquals("Rfd8", result); // File disambiguation needed
+ }
+
+ /**
+ * Test of stringToMove method, of class TextIO.
+ */
+ @Test
+ public void testStringToMove() throws ChessParseError {
+ System.out.println("stringToMove");
+ Position pos = TextIO.readFEN("r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1");
+
+ Move mNe5 = new Move(Position.getSquare(3, 6), Position.getSquare(4, 4), Piece.EMPTY);
+ Move m = TextIO.stringToMove(pos, "Ne5");
+ assertEquals(mNe5, m);
+ m = TextIO.stringToMove(pos, "ne");
+ assertEquals(mNe5, m);
+ m = TextIO.stringToMove(pos, "N");
+ assertEquals(null, m);
+
+ Move mQc6e4 = new Move(Position.getSquare(2, 5), Position.getSquare(4, 3), Piece.EMPTY);
+ m = TextIO.stringToMove(pos, "Qc6-e4");
+ assertEquals(mQc6e4, m);
+ m = TextIO.stringToMove(pos, "Qc6e4");
+ assertEquals(mQc6e4, m);
+ m = TextIO.stringToMove(pos, "Qce4");
+ assertEquals(null, m);
+ m = TextIO.stringToMove(pos, "Q6e4");
+ assertEquals(null, m);
+
+ Move maxb1Q = new Move(Position.getSquare(0, 1), Position.getSquare(1, 0), Piece.BQUEEN);
+ m = TextIO.stringToMove(pos, "axb1Q");
+ assertEquals(maxb1Q, m);
+ m = TextIO.stringToMove(pos, "axb1Q#");
+ assertEquals(maxb1Q, m);
+ m = TextIO.stringToMove(pos, "axb1Q+");
+ assertEquals(null, m);
+
+ Move mh5= new Move(Position.getSquare(7, 6), Position.getSquare(7, 4), Piece.EMPTY);
+ m = TextIO.stringToMove(pos, "h5");
+ assertEquals(mh5, m);
+ m = TextIO.stringToMove(pos, "h7-h5");
+ assertEquals(mh5, m);
+ m = TextIO.stringToMove(pos, "h");
+ assertEquals(null, m);
+
+ pos = TextIO.readFEN("r1b1k2r/1pqpppbp/p5pn/3BP3/8/2pP4/PPPBQPPP/R3K2R w KQkq - 0 12");
+ m = TextIO.stringToMove(pos, "bxc3");
+ assertEquals(TextIO.getSquare("b2"), m.from);
+ m = TextIO.stringToMove(pos, "Bxc3");
+ assertEquals(TextIO.getSquare("d2"), m.from);
+ m = TextIO.stringToMove(pos, "bxc");
+ assertEquals(TextIO.getSquare("b2"), m.from);
+ m = TextIO.stringToMove(pos, "Bxc");
+ assertEquals(TextIO.getSquare("d2"), m.from);
+
+ // Test castling. o-o is a substring of o-o-o, which could cause problems.
+ pos = TextIO.readFEN("5k2/p1pQn3/1p2Bp1r/8/4P1pN/2N5/PPP2PPP/R3K2R w KQ - 0 16");
+ Move kCastle = new Move(Position.getSquare(4,0), Position.getSquare(6,0), Piece.EMPTY);
+ Move qCastle = new Move(Position.getSquare(4,0), Position.getSquare(2,0), Piece.EMPTY);
+ m = TextIO.stringToMove(pos, "o");
+ assertEquals(null, m);
+ m = TextIO.stringToMove(pos, "o-o");
+ assertEquals(kCastle, m);
+ m = TextIO.stringToMove(pos, "O-O");
+ assertEquals(kCastle, m);
+ m = TextIO.stringToMove(pos, "o-o-o");
+ assertEquals(qCastle, m);
+
+ // Test 'o-o+'
+ pos.setPiece(Position.getSquare(5,1), Piece.EMPTY);
+ pos.setPiece(Position.getSquare(5,5), Piece.EMPTY);
+ m = TextIO.stringToMove(pos, "o");
+ assertEquals(null, m);
+ m = TextIO.stringToMove(pos, "o-o");
+ assertEquals(kCastle, m);
+ m = TextIO.stringToMove(pos, "o-o-o");
+ assertEquals(qCastle, m);
+ m = TextIO.stringToMove(pos, "o-o+");
+ assertEquals(kCastle, m);
+
+ // Test d8=Q+ syntax
+ pos = TextIO.readFEN("1r3r2/2kP2Rp/p1bN1p2/2p5/5P2/2P5/P5PP/3R2K1 w - -");
+ m = TextIO.stringToMove(pos, "d8=Q+");
+ Move m2 = TextIO.stringToMove(pos, "d8Q");
+ assertEquals(m2, m);
+ }
+
+ /**
+ * Test of getSquare method, of class TextIO.
+ */
+ @Test
+ public void testGetSquare() throws ChessParseError {
+ System.out.println("getSquare");
+ assertEquals(Position.getSquare(0, 0), TextIO.getSquare("a1"));
+ assertEquals(Position.getSquare(1, 7), TextIO.getSquare("b8"));
+ assertEquals(Position.getSquare(3, 3), TextIO.getSquare("d4"));
+ assertEquals(Position.getSquare(4, 3), TextIO.getSquare("e4"));
+ assertEquals(Position.getSquare(3, 1), TextIO.getSquare("d2"));
+ assertEquals(Position.getSquare(7, 7), TextIO.getSquare("h8"));
+ }
+
+ /**
+ * Test of squareToString method, of class TextIO.
+ */
+ @Test
+ public void testSquareToString() {
+ System.out.println("squareToString");
+ assertEquals("a1", TextIO.squareToString(Position.getSquare(0, 0)));
+ assertEquals("h6", TextIO.squareToString(Position.getSquare(7, 5)));
+ assertEquals("e4", TextIO.squareToString(Position.getSquare(4, 3)));
+ }
+
+ /**
+ * Test of asciiBoard method, of class TextIO.
+ */
+ @Test
+ public void testAsciiBoard() throws ChessParseError {
+ System.out.println("asciiBoard");
+ Position pos = TextIO.readFEN("r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1");
+ String aBrd = TextIO.asciiBoard(pos);
+// System.out.print(aBrd);
+ assertEquals(12, aBrd.length() - aBrd.replaceAll("\\*", "").length()); // 12 black pieces
+ assertEquals(3, aBrd.length() - aBrd.replaceAll("\\*Q", " ").length()); // 3 black queens
+ assertEquals(3, aBrd.length() - aBrd.replaceAll(" P", " ").length()); // 3 white pawns
+ }
+
+ /**
+ * Test of uciStringToMove method, of class TextIO.
+ */
+ @Test
+ public void testUciStringToMove() throws ChessParseError {
+ System.out.println("stringToMove");
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ Move m = TextIO.uciStringToMove("e2e4");
+ assertEquals(TextIO.stringToMove(pos, "e4"), m);
+ m = TextIO.uciStringToMove("e2e5");
+ assertEquals(new Move(12, 12+8*3, Piece.EMPTY), m);
+
+ m = TextIO.uciStringToMove("e2e5q");
+ assertEquals(null, m);
+
+ m = TextIO.uciStringToMove("e7e8q");
+ assertEquals(Piece.WQUEEN, m.promoteTo);
+ m = TextIO.uciStringToMove("e7e8r");
+ assertEquals(Piece.WROOK, m.promoteTo);
+ m = TextIO.uciStringToMove("e7e8b");
+ assertEquals(Piece.WBISHOP, m.promoteTo);
+ m = TextIO.uciStringToMove("e2e1n");
+ assertEquals(Piece.BKNIGHT, m.promoteTo);
+ m = TextIO.uciStringToMove("e7e8x");
+ assertEquals(null, m); // Invalid promotion piece
+ m = TextIO.uciStringToMove("i1i3");
+ assertEquals(null, m); // Outside board
+ }
+}
diff --git a/CuckooChessEngine/test/chess/TranspositionTableTest.java b/CuckooChessEngine/test/chess/TranspositionTableTest.java
new file mode 100644
index 0000000..aef1fda
--- /dev/null
+++ b/CuckooChessEngine/test/chess/TranspositionTableTest.java
@@ -0,0 +1,162 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package chess;
+
+import chess.TranspositionTable.TTEntry;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author petero
+ */
+public class TranspositionTableTest {
+
+ public TranspositionTableTest() {
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ }
+
+ /**
+ * Test of TTEntry nested class, of class TranspositionTable.
+ */
+ @Test
+ public void testTTEntry() throws ChessParseError {
+ System.out.println("TTEntry");
+ final int mate0 = Search.MATE0;
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ Move move = TextIO.stringToMove(pos, "e4");
+
+ // Test "normal" (non-mate) score
+ int score = 17;
+ int ply = 3;
+ TTEntry ent1 = new TTEntry();
+ ent1.key = 1;
+ ent1.setMove(move);
+ ent1.setScore(score, ply);
+ ent1.setDepth(3);
+ ent1.generation = 0;
+ ent1.type = TranspositionTable.TTEntry.T_EXACT;
+ ent1.setHashSlot(0);
+ Move tmpMove = new Move(0,0,0);
+ ent1.getMove(tmpMove);
+ assertEquals(move, tmpMove);
+ assertEquals(score, ent1.getScore(ply));
+ assertEquals(score, ent1.getScore(ply + 3)); // Non-mate score, should be ply-independent
+
+ // Test positive mate score
+ TTEntry ent2 = new TTEntry();
+ score = mate0 - 6;
+ ply = 3;
+ ent2.key = 3;
+ move = new Move(8, 0, Piece.BQUEEN);
+ ent2.setMove(move);
+ ent2.setScore(score, ply);
+ ent2.setDepth(99);
+ ent2.generation = 0;
+ ent2.type = TranspositionTable.TTEntry.T_EXACT;
+ ent2.setHashSlot(0);
+ ent2.getMove(tmpMove);
+ assertEquals(move, tmpMove);
+ assertEquals(score, ent2.getScore(ply));
+ assertEquals(score + 2, ent2.getScore(ply - 2));
+
+ // Compare ent1 and ent2
+ assertTrue(!ent1.betterThan(ent2, 0)); // More depth is good
+ assertTrue(ent2.betterThan(ent1, 0));
+
+ ent2.generation = 1;
+ assertTrue(!ent2.betterThan(ent1, 0)); // ent2 has wrong generation
+ assertTrue(ent2.betterThan(ent1, 1)); // ent1 has wrong generation
+
+ ent2.generation = 0;
+ ent1.setDepth(7); ent2.setDepth(7);
+ ent1.type = TranspositionTable.TTEntry.T_GE;
+ assertTrue(ent2.betterThan(ent1, 0));
+ ent2.type = TranspositionTable.TTEntry.T_LE;
+ assertTrue(!ent2.betterThan(ent1, 0)); // T_GE is equally good as T_LE
+ assertTrue(!ent1.betterThan(ent2, 0));
+
+ // Test negative mate score
+ TTEntry ent3 = new TTEntry();
+ score = -mate0 + 5;
+ ply = 3;
+ ent3.key = 3;
+ move = new Move(8, 0, Piece.BQUEEN);
+ ent3.setMove(move);
+ ent3.setScore(score, ply);
+ ent3.setDepth(99);
+ ent3.generation = 0;
+ ent3.type = TranspositionTable.TTEntry.T_EXACT;
+ ent3.setHashSlot(0);
+ ent3.getMove(tmpMove);
+ assertEquals(move, tmpMove);
+ assertEquals(score, ent3.getScore(ply));
+ assertEquals(score - 2, ent3.getScore(ply - 2));
+ }
+
+ /**
+ * Test of insert method, of class TranspositionTable.
+ */
+ @Test
+ public void testInsert() throws ChessParseError {
+ System.out.println("insert");
+ TranspositionTable tt = new TranspositionTable(16);
+ Position pos = TextIO.readFEN(TextIO.startPosFEN);
+ String[] moves = {
+ "e4", "e5", "Nf3", "Nc6", "Bb5", "a6", "Ba4", "b5", "Bb3", "Nf6", "O-O", "Be7", "Re1"
+ };
+ UndoInfo ui = new UndoInfo();
+ for (int i = 0; i < moves.length; i++) {
+ Move m = TextIO.stringToMove(pos, moves[i]);
+ pos.makeMove(m, ui);
+ int score = i * 17 + 3;
+ m.score = score;
+ int type = TranspositionTable.TTEntry.T_EXACT;
+ int ply = i + 1;
+ int depth = i * 2 + 5;
+ tt.insert(pos.historyHash(), m, type, ply, depth, score * 2 + 3);
+ }
+
+ pos = TextIO.readFEN(TextIO.startPosFEN);
+ for (int i = 0; i < moves.length; i++) {
+ Move m = TextIO.stringToMove(pos, moves[i]);
+ pos.makeMove(m, ui);
+ TranspositionTable.TTEntry ent = tt.probe(pos.historyHash());
+ assertEquals(TranspositionTable.TTEntry.T_EXACT, ent.type);
+ int score = i * 17 + 3;
+ int ply = i + 1;
+ int depth = i * 2 + 5;
+ assertEquals(score, ent.getScore(ply));
+ assertEquals(depth, ent.getDepth());
+ assertEquals(score * 2 + 3, ent.evalScore);
+ Move tmpMove = new Move(0,0,0);
+ ent.getMove(tmpMove);
+ assertEquals(m, tmpMove);
+ }
+ }
+}
diff --git a/CuckooChessEngine/test/guibase/ChessControllerTest.java b/CuckooChessEngine/test/guibase/ChessControllerTest.java
new file mode 100644
index 0000000..63dccbd
--- /dev/null
+++ b/CuckooChessEngine/test/guibase/ChessControllerTest.java
@@ -0,0 +1,47 @@
+/*
+ CuckooChess - A java chess program.
+ Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
+
+ 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 .
+*/
+
+package guibase;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import chess.ChessParseError;
+import chess.Piece;
+import chess.TextIO;
+
+public class ChessControllerTest {
+
+ @Test
+ public final void testSetPGN() throws ChessParseError {
+ ChessController ctrl = new ChessController(null);
+ ctrl.newGame(true, 8, false);
+ ctrl.setPGN("[FEN \"k/8/8/8/8/8/KP/8 w\"]\n");
+ assertEquals(TextIO.getSquare("a2"), ctrl.game.pos.getKingSq(true));
+ assertEquals(TextIO.getSquare("a8"), ctrl.game.pos.getKingSq(false));
+
+ ctrl.setPGN("1.e4 e5 2. Nf3!!! $6 (Nc3 (a3)) Nc6?? Bb5!!? a6?! * Ba4");
+ assertEquals(Piece.BPAWN, ctrl.game.pos.getPiece(TextIO.getSquare("a6")));
+ assertEquals(Piece.WBISHOP, ctrl.game.pos.getPiece(TextIO.getSquare("b5")));
+ assertEquals(Piece.EMPTY, ctrl.game.pos.getPiece(TextIO.getSquare("a4")));
+
+ ctrl.setPGN("[FEN \"r1bq1rk1/pp3ppp/2n1pn2/6B1/1bBP4/2N2N2/PPQ2PPP/R3K2R w KQ - 1 10\"]\n");
+ assertEquals(10, ctrl.game.pos.fullMoveCounter);
+ }
+}