Moved CuckooChessEngine project to trunk/

This commit is contained in:
Peter Osterlund 2011-11-12 19:43:53 +00:00
parent 374c481a14
commit f2946fe6c7
46 changed files with 12411 additions and 0 deletions

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet editPageId=&quot;org.eclipse.ui.resourceWorkingSetPage&quot; factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; id=&quot;1278056238759_232&quot; label=&quot;working set&quot; name=&quot;working set&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/CuckooChessEngine/src/book.bin&quot; type=&quot;1&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet editPageId=&quot;org.eclipse.ui.resourceWorkingSetPage&quot; factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; id=&quot;1278056261536_239&quot; label=&quot;workingSet&quot; name=&quot;workingSet&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/CuckooChessEngine/src/chess/Book.java&quot; type=&quot;1&quot;/&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/CuckooChessEngine/src/book.txt&quot; type=&quot;1&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="/usr/bin/java"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="chess/Book"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/CuckooChessEngine/bin}"/>
</launchConfiguration>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>CuckooChessEngine</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/BinBook_Builder.launch</value>
</dictionary>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -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

Binary file not shown.

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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)];
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Long, List<BookEntry>> 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<Long, List<BookEntry>>();
rndGen = new SecureRandom();
rndGen.setSeed(System.currentTimeMillis());
numBookMoves = 0;
try {
InputStream inStream = getClass().getResourceAsStream("/book.bin");
List<Byte> buf = new ArrayList<Byte>(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<BookEntry> ent = bookMap.get(pos.zobristHash());
if (ent == null) {
ent = new ArrayList<BookEntry>();
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<BookEntry> 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<BookEntry> 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<Byte> 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<Byte> createBinBook() {
List<Byte> binBook = new ArrayList<Byte>(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<Byte> 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;
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Position> 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<Move, String> 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<Move, String>(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
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
package chess;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
*
* @author petero
*/
public class Game {
protected List<Move> moveList = null;
protected List<UndoInfo> uiInfoList = null;
List<Boolean> 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<Move>();
uiInfoList = new ArrayList<UndoInfo>();
drawOfferList = new ArrayList<Boolean>();
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<String> getPosHistory() {
List<String> ret = new ArrayList<String>();
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<Position> getHistory() {
ArrayList<Position> posList = new ArrayList<Position>();
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<Position> oldPositions = new ArrayList<Position>();
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Position> 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() {
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Move> {
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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<String> parNames = new ArrayList<String>();
for (Map.Entry<String, ParamBase> 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<String, ParamBase> params = new TreeMap<String, ParamBase>();
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;
}
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Position> 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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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";
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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 "";
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Move> extractPVMoves(Position rootPos, Move m) {
Position pos = new Position(rootPos);
m = new Move(m);
ArrayList<Move> ret = new ArrayList<Move>();
UndoInfo ui = new UndoInfo();
List<Long> hashHistory = new ArrayList<Long>();
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<Long> hashHistory = new ArrayList<Long>();
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<Integer> depHist = new ArrayList<Integer>();
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));
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Move> 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<Integer> 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<Integer> children = findChildren(currIndex);
String m = cmdStr;
StartEntry se = new StartEntry();
EndEntry ee = new EndEntry();
ArrayList<Integer> found = new ArrayList<Integer>();
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<Integer> children = findChildren(currIndex);
String m = getArgStr(cmdStr, "");
for (Integer c : children)
printNodeInfo(rootPos, c, m);
doPrint = false;
} else if (cmdStr.startsWith("n")) {
ArrayList<Integer> nodes = getNodeSequence(currIndex);
for (int node : nodes)
printNodeInfo(rootPos, node);
doPrint = false;
} else if (cmdStr.startsWith("d")) {
ArrayList<Integer> nVec = getArgs(cmdStr, 0);
for (int n : nVec) {
ArrayList<Integer> children = findChildren(currIndex);
if ((n >= 0) && (n < children.size())) {
currIndex = children.get(n);
} else
break;
}
} else if (cmdStr.startsWith("p")) {
ArrayList<Move> 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<Integer> 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<Integer> getNodeForHashKey(long hashKey) {
hashKey &= 0x0000ffffffffffffL;
ArrayList<Integer> ret = new ArrayList<Integer>();
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<Integer> getArgs(String s, int defVal) {
ArrayList<Integer> ret = new ArrayList<Integer>();
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<Integer> findChildren(int index) {
ArrayList<Integer> ret = new ArrayList<Integer>();
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<Integer> 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<Integer> getNodeSequence(int index) {
ArrayList<Integer> nodes = new ArrayList<Integer>();
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<Move> getMoveSequence(int index) {
ArrayList<Move> moves = new ArrayList<Move>();
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<Move> 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");
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
package chess;
/**
* A small helper class that makes it possible to return two values from a function.
* @author petero
*/
public final class TwoReturnValues<T1, T2> {
public final T1 first;
public final T2 second;
TwoReturnValues(T1 first, T2 second) {
this.first = first;
this.second = second;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Move> 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<String> 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<String> 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<String> 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());
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}

Binary file not shown.

Binary file not shown.

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<<TextIO.getSquare("e2")) |
(1L<<TextIO.getSquare("f3")) |
(1L<<TextIO.getSquare("h3")),
BitBoard.knightAttacks[TextIO.getSquare("g1")]);
}
/** Test of squaresBetween[][], of class BitBoard. */
@Test
public void testSquaresBetween() throws ChessParseError {
System.out.println("squaresBetween");
// Tests that the set of nonzero elements is correct
for (int sq1 = 0; sq1 < 64; sq1++) {
for (int sq2 = 0; sq2 < 64; sq2++) {
int d = BitBoard.getDirection(sq1, sq2);
if (d == 0) {
assertEquals(0, BitBoard.squaresBetween[sq1][sq2]);
} else {
int dx = Position.getX(sq1) - Position.getX(sq2);
int dy = Position.getY(sq1) - Position.getY(sq2);
if (Math.abs(dx * dy) == 2) { // Knight direction
assertEquals(0, BitBoard.squaresBetween[sq1][sq2]);
} else {
if ((Math.abs(dx) > 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));
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Position> nullHist = new ArrayList<Position>();
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
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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));
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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));
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> 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<String> 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<String> 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<String> 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<String> 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<String> getMoveList(Position pos, boolean onlyLegal) {
Position swap = EvaluateTest.swapColors(pos);
List<String> swapList = getMoveList0(swap, onlyLegal);
List<String> ret = getMoveList0(pos, onlyLegal);
assertEquals(swapList.size(), ret.size());
// FIXME! Test that swapList contains swapped moves compared to ret
return ret;
}
private List<String> getMoveList0(Position pos, boolean onlyLegal) {
MoveGen moveGen = new MoveGen();
MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
if (onlyLegal)
MoveGen.removeIllegal(pos, moves);
ArrayList<String> strMoves = new ArrayList<String>();
for (int mi = 0; mi < moves.size; mi++) {
Move m = moves.m[mi];
String mStr = TextIO.moveToUCIString(m);
strMoves.add(mStr);
}
List<String> capList1 = getCaptureList(pos, false, onlyLegal);
assertTrue(strMoves.containsAll(capList1));
List<String> capList2 = getCaptureList(pos, true, onlyLegal);
assertTrue(strMoves.containsAll(capList2));
List<String> 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<String> 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<String> strMoves = new ArrayList<String>();
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<String> 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<String> strMoves = new ArrayList<String>();
for (int mi = 0; mi < moves.size; mi++) {
Move m = moves.m[mi];
String mStr = TextIO.moveToUCIString(m);
strMoves.add(mStr);
}
return strMoves;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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));
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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));
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<UndoInfo> uiList = new ArrayList<UndoInfo>();
List<Long> hashList = new ArrayList<Long>();
List<Move> moveList = new ArrayList<Move>();
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));
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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]);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}