From f2946fe6c77d677d54358d715333766d109be2ee Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Sat, 12 Nov 2011 19:43:53 +0000 Subject: [PATCH] Moved CuckooChessEngine project to trunk/ --- CuckooChessEngine/.classpath | 8 + .../BinBook_Builder.launch | 11 + CuckooChessEngine/.project | 27 + .../.settings/org.eclipse.jdt.core.prefs | 12 + CuckooChessEngine/src/book.bin | Bin 0 -> 5762 bytes CuckooChessEngine/src/book.txt | 228 +++ CuckooChessEngine/src/chess/BitBoard.java | 359 +++++ CuckooChessEngine/src/chess/Book.java | 280 ++++ .../src/chess/ChessParseError.java | 32 + .../src/chess/ComputerPlayer.java | 244 ++++ CuckooChessEngine/src/chess/Evaluate.java | 1181 ++++++++++++++++ CuckooChessEngine/src/chess/Game.java | 572 ++++++++ CuckooChessEngine/src/chess/History.java | 80 ++ CuckooChessEngine/src/chess/HumanPlayer.java | 73 + CuckooChessEngine/src/chess/KillerTable.java | 83 ++ CuckooChessEngine/src/chess/Move.java | 97 ++ CuckooChessEngine/src/chess/MoveGen.java | 1064 ++++++++++++++ CuckooChessEngine/src/chess/Parameters.java | 166 +++ CuckooChessEngine/src/chess/Piece.java | 57 + CuckooChessEngine/src/chess/Player.java | 59 + CuckooChessEngine/src/chess/Position.java | 674 +++++++++ CuckooChessEngine/src/chess/Search.java | 1220 +++++++++++++++++ CuckooChessEngine/src/chess/TextIO.java | 612 +++++++++ .../src/chess/TranspositionTable.java | 343 +++++ CuckooChessEngine/src/chess/TreeLogger.java | 617 +++++++++ .../src/chess/TwoReturnValues.java | 33 + CuckooChessEngine/src/chess/UndoInfo.java | 31 + .../src/guibase/ChessController.java | 549 ++++++++ .../src/guibase/GUIInterface.java | 58 + CuckooChessEngine/src/kpk.bitbase | Bin 0 -> 24576 bytes CuckooChessEngine/src/krkp.winmasks | Bin 0 -> 24576 bytes .../test/chess/BitBoardTest.java | 136 ++ CuckooChessEngine/test/chess/BookTest.java | 85 ++ .../test/chess/ComputerPlayerTest.java | 114 ++ .../test/chess/EvaluateTest.java | 492 +++++++ CuckooChessEngine/test/chess/GameTest.java | 441 ++++++ CuckooChessEngine/test/chess/HistoryTest.java | 85 ++ .../test/chess/KillerTableTest.java | 95 ++ CuckooChessEngine/test/chess/MoveGenTest.java | 543 ++++++++ CuckooChessEngine/test/chess/MoveTest.java | 84 ++ CuckooChessEngine/test/chess/PieceTest.java | 54 + .../test/chess/PositionTest.java | 444 ++++++ CuckooChessEngine/test/chess/SearchTest.java | 461 +++++++ CuckooChessEngine/test/chess/TextIOTest.java | 398 ++++++ .../test/chess/TranspositionTableTest.java | 162 +++ .../test/guibase/ChessControllerTest.java | 47 + 46 files changed, 12411 insertions(+) create mode 100644 CuckooChessEngine/.classpath create mode 100644 CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch create mode 100644 CuckooChessEngine/.project create mode 100644 CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs create mode 100644 CuckooChessEngine/src/book.bin create mode 100644 CuckooChessEngine/src/book.txt create mode 100644 CuckooChessEngine/src/chess/BitBoard.java create mode 100644 CuckooChessEngine/src/chess/Book.java create mode 100644 CuckooChessEngine/src/chess/ChessParseError.java create mode 100644 CuckooChessEngine/src/chess/ComputerPlayer.java create mode 100644 CuckooChessEngine/src/chess/Evaluate.java create mode 100644 CuckooChessEngine/src/chess/Game.java create mode 100644 CuckooChessEngine/src/chess/History.java create mode 100644 CuckooChessEngine/src/chess/HumanPlayer.java create mode 100644 CuckooChessEngine/src/chess/KillerTable.java create mode 100644 CuckooChessEngine/src/chess/Move.java create mode 100644 CuckooChessEngine/src/chess/MoveGen.java create mode 100644 CuckooChessEngine/src/chess/Parameters.java create mode 100644 CuckooChessEngine/src/chess/Piece.java create mode 100644 CuckooChessEngine/src/chess/Player.java create mode 100644 CuckooChessEngine/src/chess/Position.java create mode 100644 CuckooChessEngine/src/chess/Search.java create mode 100644 CuckooChessEngine/src/chess/TextIO.java create mode 100644 CuckooChessEngine/src/chess/TranspositionTable.java create mode 100644 CuckooChessEngine/src/chess/TreeLogger.java create mode 100644 CuckooChessEngine/src/chess/TwoReturnValues.java create mode 100644 CuckooChessEngine/src/chess/UndoInfo.java create mode 100644 CuckooChessEngine/src/guibase/ChessController.java create mode 100644 CuckooChessEngine/src/guibase/GUIInterface.java create mode 100644 CuckooChessEngine/src/kpk.bitbase create mode 100644 CuckooChessEngine/src/krkp.winmasks create mode 100644 CuckooChessEngine/test/chess/BitBoardTest.java create mode 100644 CuckooChessEngine/test/chess/BookTest.java create mode 100644 CuckooChessEngine/test/chess/ComputerPlayerTest.java create mode 100644 CuckooChessEngine/test/chess/EvaluateTest.java create mode 100644 CuckooChessEngine/test/chess/GameTest.java create mode 100644 CuckooChessEngine/test/chess/HistoryTest.java create mode 100644 CuckooChessEngine/test/chess/KillerTableTest.java create mode 100644 CuckooChessEngine/test/chess/MoveGenTest.java create mode 100644 CuckooChessEngine/test/chess/MoveTest.java create mode 100644 CuckooChessEngine/test/chess/PieceTest.java create mode 100644 CuckooChessEngine/test/chess/PositionTest.java create mode 100644 CuckooChessEngine/test/chess/SearchTest.java create mode 100644 CuckooChessEngine/test/chess/TextIOTest.java create mode 100644 CuckooChessEngine/test/chess/TranspositionTableTest.java create mode 100644 CuckooChessEngine/test/guibase/ChessControllerTest.java diff --git a/CuckooChessEngine/.classpath b/CuckooChessEngine/.classpath new file mode 100644 index 0000000..b0e3db7 --- /dev/null +++ b/CuckooChessEngine/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch b/CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch new file mode 100644 index 0000000..c2dcb75 --- /dev/null +++ b/CuckooChessEngine/.externalToolBuilders/BinBook_Builder.launch @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/CuckooChessEngine/.project b/CuckooChessEngine/.project new file mode 100644 index 0000000..36bf309 --- /dev/null +++ b/CuckooChessEngine/.project @@ -0,0 +1,27 @@ + + + CuckooChessEngine + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.ui.externaltools.ExternalToolBuilder + auto,full,incremental, + + + LaunchConfigHandle + <project>/.externalToolBuilders/BinBook_Builder.launch + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs b/CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..12a57ed --- /dev/null +++ b/CuckooChessEngine/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Sun May 09 18:17:45 CEST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/CuckooChessEngine/src/book.bin b/CuckooChessEngine/src/book.bin new file mode 100644 index 0000000000000000000000000000000000000000..b53cf3795fa9b458976048b5ddedd9245cc1f640 GIT binary patch literal 5762 zcma)AOK4oz8J@ZKyzXN(;G#{f1{{|{l?nu%O(dj+j0M6~7lDGYLMXP$-i-x8wg*d| zaqX)hNZTxmO>nD}zR1E;BdW(xE3 z;i5TST**q~9aRl$!AH@5@|EyCC!uzch#RWvz2%LVmf4KTg)@bVT@7`qgQz5sJTTAI zMz|6AE2T`zkQEj;MtI(f*&2wKy<(EnHQoho)lKoOOaNl8iq%L`P z;Z3DD1Rq*XgS}@rI_gq*BfKrvXz6~rsY@@qAdhk0ymzIPZNHp2_H}n!0F)E$Au-Gtu@wNfMQ9HaFC1@2>Y3 zZ`DS1OB^fo?nf2|RWm$|JhkL{RL;xdipcNBATGEoUjcTEK7{>Q1f!<=^dZv{n|sfL zkL$Og&$5Uj#j-6`fV{c^M7B-sfUE$i|sjuOcqcoB7_JIvtr);28CM zMfc?;Jl~`?b5tLS1~RC&M&4^q={|KYeir^Byp8A%$#HX2|3JU~Dri<(aYc>?H@!wQ zng4V2t^HroK2%7em&7soPT^GXqWpf)t2Wfby-38S-E({UO&rVMs)ZrcNh>%UywTO@ z`m!To1)AgPi=ewOD9@S+^Oi38zTe{uvz4Nva%CEwO3&|g&Vp{#+*#d+UKkY3=em1JnqQe3th{CJzdBZ~$IGvdR}a-j zw#Q%WhqDAMbPG{f$)6|=fvvWsES}Hhs$hFgrp}k>swU%SP_NMd>O0fB4n;OY{*q_` zHBisX!Z5qZTaC6^;o@X?PMQ54GoJA?;D^P@XdBo&Y8E~F^@EuOL)plOr^!rK>sj)PYDQ%_8cd;POWxhE z=KJbW&>io@kK!$1y~tuw1*71ps$P=o!Qb>;^lN*1o; zK#Xl2T(ez795I+wmNEC9`Vw)ip#C5QUoV>b0cH<-NhNBYHpmzq44Y|(m`-I^=VeTP z^it=fYEPn)s+t-)64S@&iArw%#XB-aCPtBo%V7=D1X$c8N6~rc(O?El8FNZPg6_wi zU;=i(lf*mT6@%}t?vu(G@m`~(YVn8-G^hKKj7`nlFn7EwWDjSNi>|#P2Ai#^dG%9N z;%3;NucVly2G06|c40~_%DCfCQsXHsg?%ZS_u-yR^OReHoQujnxR{X*X3#tLtXHT2 zl?0{Wu>KUD)u9vnPw*P~-{uuLxBN>y^tpaB*;7OX*`45A4g19rb8h!LZhQO=h_GLe zV^^Grm&Mx49A>eQQhF^jmTIdDkK?Q#zK9D-6`k&j^8XY4w5DeCW3s-pq~^1p!m+uaYFA?lsaw>XPYQJ&keO$|EwQ46~{&E+bnu1s~5b!>O5lGi|8f+O{AjAKva+R(>ic>+a zURR%}57iR#H^`1aRvq)98<3T?@GI8|ddXWwza?SKyW*!0ti*)D4gwAb1JM>=QFk;MosHh3{#na$_G_S!C!0qb=X<8^HBc3nD?3Qc z1^Q*g&&Bn^utuZ`9~b{r__uoKE6mVwXH}}#q4kyYu4^^y5l_tnSC8NkFf6=rw`O>W z_z+tI-UHJb+ix%>Z$`h;oY$lAPDkpkLK<~`BUfK^_}Nv8(-ZVS2fERh=69?mk)#>v zx`UpZ%3XG7@TaxOzGuI3T#0P~%&X(%fJL;1Rt0}A{8OxnZPAEIL^A4_fS9wKUD*8o zgsZXA((0VsS)F^Om{@kgcI?5n3r1^O$p0qJ z$+>unbI;wui18GhJ0*VlrM9!3YL3@Z~aJSbHP~v~hPpU)!kfiMYM1y>~#$ zeaBh|>u9CZ%U)0k?MFXu+J6BSbd1^6zLgTYH&`hu8vh~`>gG6V&0rH64d2An`wh_+ z+1_fLF6I?`Nxw3My2k$FZihE+7qqqD^us!<-)%P6c&F7Q=Q>tlKGl6pv0M)5Ryw^{ zMFM*?cX{Lr*~hJAN>$?UcyIIF(L#paM3!M|)grjHnHs8b5t9c!nV#YV_+TnMB`&o_ z%@eP%6?=El*C*5&{QL91-RWxWiY>{x!Vv95F8bYez$ud2>rggw(CfND$IvimT0UNi zh$!+DOyLz<;u}JY6^6VKdR?`=h{|bVGOY%fUYtdallV3Lh6Z#UHdRzT`4kv*V0A7i z`Bi>t^}}DjRf{X`y~}?T;O`_Xtc>lOzYH)ihoB;#NwED)?K>;!oGxE{Yg znZ}(v1+{}CtkiAm*bQ1bR-gNaz^ETY17rZarfPUYVeWEoC4x8b?G9ah>(scvx>q_Q z;B;^=b}tUra0R<2c^3b$EsCb=$LL*7CHym{Tu-7xxC`5z*L66uh|IY-2@?&dF?!g> zA(o5`G5p5jQ0r<8??2p#>1bmO+d6!WO3M4(+=|@69AW{LfQsc_$hQ`{bXt9`59M+{ zPUYw`_uryDX}?u4(RYGwcs3T4JiFDy9z+Rw8D#ZX9&hty*uv%p&-k{WrDRkxa5hss zNqFMA&TcQNnIIeOY+_%+uH;zN_CCC}{|5TfRx#fC@v. +*/ + +package chess; + +public class BitBoard { + + /** Squares attacked by a king on a given square. */ + public static final long[] kingAttacks; + public static final long[] knightAttacks; + public static final long[] wPawnAttacks, bPawnAttacks; + + // Squares preventing a pawn from being a passed pawn, if occupied by enemy pawn + static final long[] wPawnBlockerMask, bPawnBlockerMask; + + public static final long maskAToGFiles = 0x7F7F7F7F7F7F7F7FL; + public static final long maskBToHFiles = 0xFEFEFEFEFEFEFEFEL; + public static final long maskAToFFiles = 0x3F3F3F3F3F3F3F3FL; + public static final long maskCToHFiles = 0xFCFCFCFCFCFCFCFCL; + + public static final long[] maskFile = { + 0x0101010101010101L, + 0x0202020202020202L, + 0x0404040404040404L, + 0x0808080808080808L, + 0x1010101010101010L, + 0x2020202020202020L, + 0x4040404040404040L, + 0x8080808080808080L + }; + + public static final long maskRow1 = 0x00000000000000FFL; + public static final long maskRow2 = 0x000000000000FF00L; + public static final long maskRow3 = 0x0000000000FF0000L; + public static final long maskRow4 = 0x00000000FF000000L; + public static final long maskRow5 = 0x000000FF00000000L; + public static final long maskRow6 = 0x0000FF0000000000L; + public static final long maskRow7 = 0x00FF000000000000L; + public static final long maskRow8 = 0xFF00000000000000L; + public static final long maskRow1Row8 = 0xFF000000000000FFL; + + public static final long maskDarkSq = 0xAA55AA55AA55AA55L; + public static final long maskLightSq = 0x55AA55AA55AA55AAL; + + public static final long maskCorners = 0x8100000000000081L; + + static { + // Compute king attacks + kingAttacks = new long[64]; + + for (int sq = 0; sq < 64; sq++) { + long m = 1L << sq; + long mask = (((m >>> 1) | (m << 7) | (m >>> 9)) & maskAToGFiles) | + (((m << 1) | (m << 9) | (m >>> 7)) & maskBToHFiles) | + (m << 8) | (m >>> 8); + kingAttacks[sq] = mask; + } + + // Compute knight attacks + knightAttacks = new long[64]; + for (int sq = 0; sq < 64; sq++) { + long m = 1L << sq; + long mask = (((m << 6) | (m >>> 10)) & maskAToFFiles) | + (((m << 15) | (m >>> 17)) & maskAToGFiles) | + (((m << 17) | (m >>> 15)) & maskBToHFiles) | + (((m << 10) | (m >>> 6)) & maskCToHFiles); + knightAttacks[sq] = mask; + } + + // Compute pawn attacks + wPawnAttacks = new long[64]; + bPawnAttacks = new long[64]; + wPawnBlockerMask = new long[64]; + bPawnBlockerMask = new long[64]; + for (int sq = 0; sq < 64; sq++) { + long m = 1L << sq; + long mask = ((m << 7) & maskAToGFiles) | ((m << 9) & maskBToHFiles); + wPawnAttacks[sq] = mask; + mask = ((m >>> 9) & maskAToGFiles) | ((m >>> 7) & maskBToHFiles); + bPawnAttacks[sq] = mask; + + int x = Position.getX(sq); + int y = Position.getY(sq); + m = 0; + for (int y2 = y+1; y2 < 8; y2++) { + if (x > 0) m |= 1L << Position.getSquare(x-1, y2); + m |= 1L << Position.getSquare(x , y2); + if (x < 7) m |= 1L << Position.getSquare(x+1, y2); + } + wPawnBlockerMask[sq] = m; + m = 0; + for (int y2 = y-1; y2 >= 0; y2--) { + if (x > 0) m |= 1L << Position.getSquare(x-1, y2); + m |= 1L << Position.getSquare(x , y2); + if (x < 7) m |= 1L << Position.getSquare(x+1, y2); + } + bPawnBlockerMask[sq] = m; + } + } + + private final static long[][] rTables; + private final static long[] rMasks; + private final static int[] rBits = { 12, 11, 11, 11, 11, 11, 11, 12, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 10, 9, 9, 9, 9, 9, 10, 10, + 11, 10, 10, 10, 10, 11, 11, 11 }; + private final static long[] rMagics = { + 0x0080011084624000L, 0x1440031000200141L, 0x2080082004801000L, 0x0100040900100020L, + 0x0200020010200408L, 0x0300010008040002L, 0x040024081000a102L, 0x0080003100054680L, + 0x1100800040008024L, 0x8440401000200040L, 0x0432001022008044L, 0x0402002200100840L, + 0x4024808008000400L, 0x100a000410820008L, 0x8042001144020028L, 0x2451000041002082L, + 0x1080004000200056L, 0xd41010c020004000L, 0x0004410020001104L, 0x0000818050000800L, + 0x0000050008010010L, 0x0230808002000400L, 0x2000440090022108L, 0x0488020000811044L, + 0x8000410100208006L, 0x2000a00240100140L, 0x2088802200401600L, 0x0a10100180080082L, + 0x0000080100110004L, 0x0021002300080400L, 0x8400880400010230L, 0x2001008200004401L, + 0x0000400022800480L, 0x00200040e2401000L, 0x4004100084802000L, 0x0218800800801002L, + 0x0420800800800400L, 0x002a000402001008L, 0x0e0b000401008200L, 0x0815908072000401L, + 0x1840008002498021L, 0x1070122002424000L, 0x1040200100410010L, 0x0600080010008080L, + 0x0215001008010004L, 0x0000020004008080L, 0x1300021051040018L, 0x0004040040820001L, + 0x48fffe99fecfaa00L, 0x48fffe99fecfaa00L, 0x497fffadff9c2e00L, 0x613fffddffce9200L, + 0xffffffe9ffe7ce00L, 0xfffffff5fff3e600L, 0x2000080281100400L, 0x510ffff5f63c96a0L, + 0xebffffb9ff9fc526L, 0x61fffeddfeedaeaeL, 0x53bfffedffdeb1a2L, 0x127fffb9ffdfb5f6L, + 0x411fffddffdbf4d6L, 0x0005000208040001L, 0x264038060100d004L, 0x7645fffecbfea79eL, + }; + private final static long[][] bTables; + private final static long[] bMasks; + private final static int[] bBits = { 5, 4, 5, 5, 5, 5, 4, 5, + 4, 4, 5, 5, 5, 5, 4, 4, + 4, 4, 7, 7, 7, 7, 4, 4, + 5, 5, 7, 9, 9, 7, 5, 5, + 5, 5, 7, 9, 9, 7, 5, 5, + 4, 4, 7, 7, 7, 7, 4, 4, + 4, 4, 5, 5, 5, 5, 4, 4, + 5, 4, 5, 5, 5, 5, 4, 5 }; + private final static long[] bMagics = { + 0xffedf9fd7cfcffffL, 0xfc0962854a77f576L, 0x9010210041047000L, 0x52242420800c0000L, + 0x884404220480004aL, 0x0002080248000802L, 0xfc0a66c64a7ef576L, 0x7ffdfdfcbd79ffffL, + 0xfc0846a64a34fff6L, 0xfc087a874a3cf7f6L, 0x02000888010a2211L, 0x0040044040801808L, + 0x0880040420000000L, 0x0000084110109000L, 0xfc0864ae59b4ff76L, 0x3c0860af4b35ff76L, + 0x73c01af56cf4cffbL, 0x41a01cfad64aaffcL, 0x1010000200841104L, 0x802802142a006000L, + 0x0a02000412020020L, 0x0000800040504030L, 0x7c0c028f5b34ff76L, 0xfc0a028e5ab4df76L, + 0x0020082044905488L, 0xa572211102080220L, 0x0014020001280300L, 0x0220208058008042L, + 0x0001010000104016L, 0x0005114028080800L, 0x0202640000848800L, 0x040040900a008421L, + 0x400e094000600208L, 0x800a100400120890L, 0x0041229001480020L, 0x0000020080880082L, + 0x0040002020060080L, 0x1819100100c02400L, 0x04112a4082c40400L, 0x0001240130210500L, + 0xdcefd9b54bfcc09fL, 0xf95ffa765afd602bL, 0x008200222800a410L, 0x0100020102406400L, + 0x80a8040094000200L, 0x002002006200a041L, 0x43ff9a5cf4ca0c01L, 0x4bffcd8e7c587601L, + 0xfc0ff2865334f576L, 0xfc0bf6ce5924f576L, 0x0900420442088104L, 0x0062042084040010L, + 0x01380810220a0240L, 0x0000101002082800L, 0xc3ffb7dc36ca8c89L, 0xc3ff8a54f4ca2c89L, + 0xfffffcfcfd79edffL, 0xfc0863fccb147576L, 0x0050009040441000L, 0x00139a0000840400L, + 0x9080000412220a00L, 0x0000002020010a42L, 0xfc087e8e4bb2f736L, 0x43ff9e4ef4ca2c89L, + }; + + private static final long createPattern(int i, long mask) { + long ret = 0L; + for (int j = 0; ; j++) { + long nextMask = mask & (mask - 1); + long bit = mask ^ nextMask; + if ((i & (1L << j)) != 0) + ret |= bit; + mask = nextMask; + if (mask == 0) + break; + } + return ret; + } + + private static final long addRookRays(int x, int y, long occupied, boolean inner) { + long mask = 0; + mask = addRay(mask, x, y, 1, 0, occupied, inner); + mask = addRay(mask, x, y, -1, 0, occupied, inner); + mask = addRay(mask, x, y, 0, 1, occupied, inner); + mask = addRay(mask, x, y, 0, -1, occupied, inner); + return mask; + } + private static final long addBishopRays(int x, int y, long occupied, boolean inner) { + long mask = 0; + mask = addRay(mask, x, y, 1, 1, occupied, inner); + mask = addRay(mask, x, y, -1, -1, occupied, inner); + mask = addRay(mask, x, y, 1, -1, occupied, inner); + mask = addRay(mask, x, y, -1, 1, occupied, inner); + return mask; + } + + private static final long addRay(long mask, int x, int y, int dx, int dy, + long occupied, boolean inner) { + int lo = inner ? 1 : 0; + int hi = inner ? 6 : 7; + while (true) { + if (dx != 0) { + x += dx; if ((x < lo) || (x > hi)) break; + } + if (dy != 0) { + y += dy; if ((y < lo) || (y > hi)) break; + } + int sq = Position.getSquare(x, y); + mask |= 1L << sq; + if ((occupied & (1L << sq)) != 0) + break; + } + return mask; + } + + static { // Rook magics + rTables = new long[64][]; + rMasks = new long[64]; + for (int sq = 0; sq < 64; sq++) { + int x = Position.getX(sq); + int y = Position.getY(sq); + rMasks[sq] = addRookRays(x, y, 0L, true); + int tableSize = 1 << rBits[sq]; + long[] table = new long[tableSize]; + for (int i = 0; i < tableSize; i++) table[i] = -1; + int nPatterns = 1 << Long.bitCount(rMasks[sq]); + for (int i = 0; i < nPatterns; i++) { + long p = createPattern(i, rMasks[sq]); + int entry = (int)((p * rMagics[sq]) >>> (64 - rBits[sq])); + long atks = addRookRays(x, y, p, false); + if (table[entry] == -1) { + table[entry] = atks; + } else if (table[entry] != atks) { + throw new RuntimeException(); + } + } + rTables[sq] = table; + } + } + + static { // Bishop magics + bTables = new long[64][]; + bMasks = new long[64]; + for (int sq = 0; sq < 64; sq++) { + int x = Position.getX(sq); + int y = Position.getY(sq); + bMasks[sq] = addBishopRays(x, y, 0L, true); + int tableSize = 1 << bBits[sq]; + long[] table = new long[tableSize]; + for (int i = 0; i < tableSize; i++) table[i] = -1; + int nPatterns = 1 << Long.bitCount(bMasks[sq]); + for (int i = 0; i < nPatterns; i++) { + long p = createPattern(i, bMasks[sq]); + int entry = (int)((p * bMagics[sq]) >>> (64 - bBits[sq])); + long atks = addBishopRays(x, y, p, false); + if (table[entry] == -1) { + table[entry] = atks; + } else if (table[entry] != atks) { + throw new RuntimeException(); + } + } + bTables[sq] = table; + } + } + + public static final long bishopAttacks(int sq, long occupied) { + return bTables[sq][(int)(((occupied & bMasks[sq]) * bMagics[sq]) >>> (64 - bBits[sq]))]; + } + + public static final long rookAttacks(int sq, long occupied) { + return rTables[sq][(int)(((occupied & rMasks[sq]) * rMagics[sq]) >>> (64 - rBits[sq]))]; + } + + static public final long[][] squaresBetween; + static { + squaresBetween = new long[64][]; + for (int sq1 = 0; sq1 < 64; sq1++) { + squaresBetween[sq1] = new long[64]; + for (int j = 0; j < 64; j++) + squaresBetween[sq1][j] = 0; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if ((dx == 0) && (dy == 0)) + continue; + long m = 0; + int x = Position.getX(sq1); + int y = Position.getY(sq1); + while (true) { + x += dx; y += dy; + if ((x < 0) || (x > 7) || (y < 0) || (y > 7)) + break; + int sq2 = Position.getSquare(x, y); + squaresBetween[sq1][sq2] = m; + m |= 1L << sq2; + } + } + } + } + } + + private static final byte dirTable[] = { + -9, 0, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, 0, -7, + 0, 0, -9, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, -7, 0, + 0, 0, 0, -9, 0, 0, 0, 0, -8, 0, 0, 0, 0, -7, 0, 0, + 0, 0, 0, 0, -9, 0, 0, 0, -8, 0, 0, 0, -7, 0, 0, 0, + 0, 0, 0, 0, 0, -9, 0, 0, -8, 0, 0, -7, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -9, -17, -8, -15, -7, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -10, -9, -8, -7, -6, 0, 0, 0, 0, 0, + 0, -1, -1, -1, -1, -1, -1, -1, 0, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 7, 15, 8, 17, 9, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 7, 0, 0, 8, 0, 0, 9, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, + 0, 0, 0, 7, 0, 0, 0, 0, 8, 0, 0, 0, 0, 9, 0, 0, + 0, 0, 7, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 9, 0, + 0, 7, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 9 + }; + + static public final int getDirection(int from, int to) { + int offs = to + (to|7) - from - (from|7) + 0x77; + return dirTable[offs]; + } + + public static final long southFill(long mask) { + mask |= (mask >>> 8); + mask |= (mask >>> 16); + mask |= (mask >>> 32); + return mask; + } + + public static final long northFill(long mask) { + mask |= (mask << 8); + mask |= (mask << 16); + mask |= (mask << 32); + return mask; + } + + private static final int trailingZ[] = { + 63, 0, 58, 1, 59, 47, 53, 2, + 60, 39, 48, 27, 54, 33, 42, 3, + 61, 51, 37, 40, 49, 18, 28, 20, + 55, 30, 34, 11, 43, 14, 22, 4, + 62, 57, 46, 52, 38, 26, 32, 41, + 50, 36, 17, 19, 29, 10, 13, 21, + 56, 45, 25, 31, 35, 16, 9, 12, + 44, 24, 15, 8, 23, 7, 6, 5 + }; + + static public final int numberOfTrailingZeros(long mask) { + return trailingZ[(int)(((mask & -mask) * 0x07EDD5E59A4E28C2L) >>> 58)]; + } +} diff --git a/CuckooChessEngine/src/chess/Book.java b/CuckooChessEngine/src/chess/Book.java new file mode 100644 index 0000000..94d4059 --- /dev/null +++ b/CuckooChessEngine/src/chess/Book.java @@ -0,0 +1,280 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.io.BufferedReader; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * Implements an opening book. + * @author petero + */ +public class Book { + public static class BookEntry { + Move move; + int count; + BookEntry(Move move) { + this.move = move; + count = 1; + } + } + private static Map> bookMap; + private static Random rndGen; + private static int numBookMoves = -1; + private boolean verbose; + + public Book(boolean verbose) { + this.verbose = verbose; + } + + private final void initBook() { + if (numBookMoves >= 0) + return; + long t0 = System.currentTimeMillis(); + bookMap = new HashMap>(); + rndGen = new SecureRandom(); + rndGen.setSeed(System.currentTimeMillis()); + numBookMoves = 0; + try { + InputStream inStream = getClass().getResourceAsStream("/book.bin"); + List buf = new ArrayList(8192); + byte[] tmpBuf = new byte[1024]; + while (true) { + int len = inStream.read(tmpBuf); + if (len <= 0) break; + for (int i = 0; i < len; i++) + buf.add(tmpBuf[i]); + } + inStream.close(); + Position startPos = TextIO.readFEN(TextIO.startPosFEN); + Position pos = new Position(startPos); + UndoInfo ui = new UndoInfo(); + int len = buf.size(); + for (int i = 0; i < len; i += 2) { + int b0 = buf.get(i); if (b0 < 0) b0 += 256; + int b1 = buf.get(i+1); if (b1 < 0) b1 += 256; + int move = (b0 << 8) + b1; + if (move == 0) { + pos = new Position(startPos); + } else { + boolean bad = ((move >> 15) & 1) != 0; + int prom = (move >> 12) & 7; + Move m = new Move(move & 63, (move >> 6) & 63, + promToPiece(prom, pos.whiteMove)); + if (!bad) + addToBook(pos, m); + pos.makeMove(m, ui); + } + } + } catch (ChessParseError ex) { + throw new RuntimeException(); + } catch (IOException ex) { + System.out.println("Can't read opening book resource"); + throw new RuntimeException(); + } + if (verbose) { + long t1 = System.currentTimeMillis(); + System.out.printf("Book moves:%d (parse time:%.3f)%n", numBookMoves, + (t1 - t0) / 1000.0); + } + } + + /** Add a move to a position in the opening book. */ + private final void addToBook(Position pos, Move moveToAdd) { + List ent = bookMap.get(pos.zobristHash()); + if (ent == null) { + ent = new ArrayList(); + bookMap.put(pos.zobristHash(), ent); + } + for (int i = 0; i < ent.size(); i++) { + BookEntry be = ent.get(i); + if (be.move.equals(moveToAdd)) { + be.count++; + return; + } + } + BookEntry be = new BookEntry(moveToAdd); + ent.add(be); + numBookMoves++; + } + + /** Return a random book move for a position, or null if out of book. */ + public final Move getBookMove(Position pos) { + initBook(); + List bookMoves = bookMap.get(pos.zobristHash()); + if (bookMoves == null) { + return null; + } + + MoveGen.MoveList legalMoves = new MoveGen().pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, legalMoves); + int sum = 0; + for (int i = 0; i < bookMoves.size(); i++) { + BookEntry be = bookMoves.get(i); + boolean contains = false; + for (int mi = 0; mi < legalMoves.size; mi++) + if (legalMoves.m[mi].equals(be.move)) { + contains = true; + break; + } + if (!contains) { + // If an illegal move was found, it means there was a hash collision. + return null; + } + sum += getWeight(bookMoves.get(i).count); + } + if (sum <= 0) { + return null; + } + int rnd = rndGen.nextInt(sum); + sum = 0; + for (int i = 0; i < bookMoves.size(); i++) { + sum += getWeight(bookMoves.get(i).count); + if (rnd < sum) { + return bookMoves.get(i).move; + } + } + // Should never get here + throw new RuntimeException(); + } + + final private int getWeight(int count) { + double tmp = Math.sqrt(count); + return (int)(tmp * Math.sqrt(tmp) * 100 + 1); + } + + /** Return a string describing all book moves. */ + public final String getAllBookMoves(Position pos) { + initBook(); + StringBuilder ret = new StringBuilder(); + List bookMoves = bookMap.get(pos.zobristHash()); + if (bookMoves != null) { + for (BookEntry be : bookMoves) { + String moveStr = TextIO.moveToString(pos, be.move, false); + ret.append(moveStr); + ret.append("("); + ret.append(be.count); + ret.append(") "); + } + } + return ret.toString(); + } + + /** Creates the book.bin file. */ + public static void main(String[] args) throws IOException { + List binBook = createBinBook(); + FileOutputStream out = new FileOutputStream("../src/book.bin"); + int bookLen = binBook.size(); + byte[] binBookA = new byte[bookLen]; + for (int i = 0; i < bookLen; i++) + binBookA[i] = binBook.get(i); + out.write(binBookA); + out.close(); + } + + public static List createBinBook() { + List binBook = new ArrayList(0); + try { + InputStream inStream = new Object().getClass().getResourceAsStream("/book.txt"); + InputStreamReader inFile = new InputStreamReader(inStream); + BufferedReader inBuf = new BufferedReader(inFile); + LineNumberReader lnr = new LineNumberReader(inBuf); + String line; + while ((line = lnr.readLine()) != null) { + if (line.startsWith("#") || (line.length() == 0)) { + continue; + } + if (!addBookLine(line, binBook)) { + System.out.printf("Book parse error, line:%d\n", lnr.getLineNumber()); + throw new RuntimeException(); + } +// System.out.printf("no:%d line:%s%n", lnr.getLineNumber(), line); + } + lnr.close(); + } catch (ChessParseError ex) { + throw new RuntimeException(); + } catch (IOException ex) { + System.out.println("Can't read opening book resource"); + throw new RuntimeException(); + } + return binBook; + } + + /** Add a sequence of moves, starting from the initial position, to the binary opening book. */ + private static boolean addBookLine(String line, List binBook) throws ChessParseError { + Position pos = TextIO.readFEN(TextIO.startPosFEN); + UndoInfo ui = new UndoInfo(); + String[] strMoves = line.split(" "); + for (String strMove : strMoves) { +// System.out.printf("Adding move:%s\n", strMove); + int bad = 0; + if (strMove.endsWith("?")) { + strMove = strMove.substring(0, strMove.length() - 1); + bad = 1; + } + Move m = TextIO.stringToMove(pos, strMove); + if (m == null) { + return false; + } + int prom = pieceToProm(m.promoteTo); + int val = m.from + (m.to << 6) + (prom << 12) + (bad << 15); + binBook.add((byte)(val >> 8)); + binBook.add((byte)(val & 255)); + pos.makeMove(m, ui); + } + binBook.add((byte)0); + binBook.add((byte)0); + return true; + } + + private static int pieceToProm(int p) { + switch (p) { + case Piece.WQUEEN: case Piece.BQUEEN: + return 1; + case Piece.WROOK: case Piece.BROOK: + return 2; + case Piece.WBISHOP: case Piece.BBISHOP: + return 3; + case Piece.WKNIGHT: case Piece.BKNIGHT: + return 4; + default: + return 0; + } + } + + private static int promToPiece(int prom, boolean whiteMove) { + switch (prom) { + case 1: return whiteMove ? Piece.WQUEEN : Piece.BQUEEN; + case 2: return whiteMove ? Piece.WROOK : Piece.BROOK; + case 3: return whiteMove ? Piece.WBISHOP : Piece.BBISHOP; + case 4: return whiteMove ? Piece.WKNIGHT : Piece.BKNIGHT; + default: return Piece.EMPTY; + } + } +} diff --git a/CuckooChessEngine/src/chess/ChessParseError.java b/CuckooChessEngine/src/chess/ChessParseError.java new file mode 100644 index 0000000..220444a --- /dev/null +++ b/CuckooChessEngine/src/chess/ChessParseError.java @@ -0,0 +1,32 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +/** + * Exception class to represent parse errors in FEN or algebraic notation. + * @author petero + */ +public class ChessParseError extends Exception { + private static final long serialVersionUID = -6051856171275301175L; + public ChessParseError() { + } + public ChessParseError(String msg) { + super(msg); + } +} diff --git a/CuckooChessEngine/src/chess/ComputerPlayer.java b/CuckooChessEngine/src/chess/ComputerPlayer.java new file mode 100644 index 0000000..5aea724 --- /dev/null +++ b/CuckooChessEngine/src/chess/ComputerPlayer.java @@ -0,0 +1,244 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.security.SecureRandom; +import java.util.List; +import java.util.Random; + +/** + * A computer algorithm player. + * @author petero + */ +public class ComputerPlayer implements Player { + public static final String engineName; + + static { + String name = "CuckooChess 1.13a2"; + String m = System.getProperty("sun.arch.data.model"); + if ("32".equals(m)) + name += " 32-bit"; + else if ("64".equals(m)) + name += " 64-bit"; + engineName = name; + } + + int minTimeMillis; + int maxTimeMillis; + int maxDepth; + int maxNodes; + public boolean verbose; + TranspositionTable tt; + Book book; + boolean bookEnabled; + boolean randomMode; + Search currentSearch; + + public ComputerPlayer() { + minTimeMillis = 10000; + maxTimeMillis = 10000; + maxDepth = 100; + maxNodes = -1; + verbose = true; + setTTLogSize(15); + book = new Book(verbose); + bookEnabled = true; + randomMode = false; + } + + public void setTTLogSize(int logSize) { + tt = new TranspositionTable(logSize); + } + + Search.Listener listener; + public void setListener(Search.Listener listener) { + this.listener = listener; + } + + @Override + public String getCommand(Position pos, boolean drawOffer, List history) { + // Create a search object + long[] posHashList = new long[200 + history.size()]; + int posHashListSize = 0; + for (Position p : history) { + posHashList[posHashListSize++] = p.zobristHash(); + } + tt.nextGeneration(); + Search sc = new Search(pos, posHashList, posHashListSize, tt); + + // Determine all legal moves + MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + sc.scoreMoveList(moves, 0); + + // Test for "game over" + if (moves.size == 0) { + // Switch sides so that the human can decide what to do next. + return "swap"; + } + + if (bookEnabled) { + Move bookMove = book.getBookMove(pos); + if (bookMove != null) { + System.out.printf("Book moves: %s\n", book.getAllBookMoves(pos)); + return TextIO.moveToString(pos, bookMove, false); + } + } + + // Find best move using iterative deepening + currentSearch = sc; + sc.setListener(listener); + Move bestM; + if ((moves.size == 1) && (canClaimDraw(pos, posHashList, posHashListSize, moves.m[0]) == "")) { + bestM = moves.m[0]; + bestM.score = 0; + } else if (randomMode) { + bestM = findSemiRandomMove(sc, moves); + } else { + sc.timeLimit(minTimeMillis, maxTimeMillis); + bestM = sc.iterativeDeepening(moves, maxDepth, maxNodes, verbose); + } + currentSearch = null; +// tt.printStats(); + String strMove = TextIO.moveToString(pos, bestM, false); + + // Claim draw if appropriate + if (bestM.score <= 0) { + String drawClaim = canClaimDraw(pos, posHashList, posHashListSize, bestM); + if (drawClaim != "") + strMove = drawClaim; + } + return strMove; + } + + /** Check if a draw claim is allowed, possibly after playing "move". + * @param move The move that may have to be made before claiming draw. + * @return The draw string that claims the draw, or empty string if draw claim not valid. + */ + private String canClaimDraw(Position pos, long[] posHashList, int posHashListSize, Move move) { + String drawStr = ""; + if (Search.canClaimDraw50(pos)) { + drawStr = "draw 50"; + } else if (Search.canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) { + drawStr = "draw rep"; + } else { + String strMove = TextIO.moveToString(pos, move, false); + posHashList[posHashListSize++] = pos.zobristHash(); + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + if (Search.canClaimDraw50(pos)) { + drawStr = "draw 50 " + strMove; + } else if (Search.canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) { + drawStr = "draw rep " + strMove; + } + pos.unMakeMove(move, ui); + } + return drawStr; + } + + @Override + public boolean isHumanPlayer() { + return false; + } + + @Override + public void useBook(boolean bookOn) { + bookEnabled = bookOn; + } + + @Override + public void timeLimit(int minTimeLimit, int maxTimeLimit, boolean randomMode) { + if (randomMode) { + minTimeLimit = 0; + maxTimeLimit = 0; + } + minTimeMillis = minTimeLimit; + maxTimeMillis = maxTimeLimit; + this.randomMode = randomMode; + if (currentSearch != null) { + currentSearch.timeLimit(minTimeLimit, maxTimeLimit); + } + } + + @Override + public void clearTT() { + tt.clear(); + } + + /** Search a position and return the best move and score. Used for test suite processing. */ + public TwoReturnValues searchPosition(Position pos, int maxTimeMillis) { + // Create a search object + long[] posHashList = new long[200]; + tt.nextGeneration(); + Search sc = new Search(pos, posHashList, 0, tt); + + // Determine all legal moves + MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + sc.scoreMoveList(moves, 0); + + // Find best move using iterative deepening + sc.timeLimit(maxTimeMillis, maxTimeMillis); + Move bestM = sc.iterativeDeepening(moves, -1, -1, false); + + // Extract PV + String PV = TextIO.moveToString(pos, bestM, false) + " "; + UndoInfo ui = new UndoInfo(); + pos.makeMove(bestM, ui); + PV += tt.extractPV(pos); + pos.unMakeMove(bestM, ui); + +// tt.printStats(); + + // Return best move and PV + return new TwoReturnValues(bestM, PV); + } + + private Move findSemiRandomMove(Search sc, MoveGen.MoveList moves) { + sc.timeLimit(minTimeMillis, maxTimeMillis); + Move bestM = sc.iterativeDeepening(moves, 1, maxNodes, verbose); + int bestScore = bestM.score; + + Random rndGen = new SecureRandom(); + rndGen.setSeed(System.currentTimeMillis()); + + int sum = 0; + for (int mi = 0; mi < moves.size; mi++) { + sum += moveProbWeight(moves.m[mi].score, bestScore); + } + int rnd = rndGen.nextInt(sum); + for (int mi = 0; mi < moves.size; mi++) { + int weight = moveProbWeight(moves.m[mi].score, bestScore); + if (rnd < weight) { + return moves.m[mi]; + } + rnd -= weight; + } + assert(false); + return null; + } + + private final static int moveProbWeight(int moveScore, int bestScore) { + double d = (bestScore - moveScore) / 100.0; + double w = 100*Math.exp(-d*d/2); + return (int)Math.ceil(w); + } + + // FIXME!!! Test Botvinnik-Markoff extension +} diff --git a/CuckooChessEngine/src/chess/Evaluate.java b/CuckooChessEngine/src/chess/Evaluate.java new file mode 100644 index 0000000..ca595e8 --- /dev/null +++ b/CuckooChessEngine/src/chess/Evaluate.java @@ -0,0 +1,1181 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Position evaluation routines. + * + * @author petero + */ +public class Evaluate { + static final int pV = 92 + Parameters.instance().getIntPar("pV"); + static final int nV = 385 + Parameters.instance().getIntPar("nV"); + static final int bV = 385 + Parameters.instance().getIntPar("bV"); + static final int rV = 593 + Parameters.instance().getIntPar("rV"); + static final int qV = 1244 + Parameters.instance().getIntPar("qV"); + static final int kV = 9900; // Used by SEE algorithm, but not included in board material sums + + static final int[] pieceValue; + static { + // Initialize material table + pieceValue = new int[Piece.nPieceTypes]; + pieceValue[Piece.WKING ] = kV; + pieceValue[Piece.WQUEEN ] = qV; + pieceValue[Piece.WROOK ] = rV; + pieceValue[Piece.WBISHOP] = bV; + pieceValue[Piece.WKNIGHT] = nV; + pieceValue[Piece.WPAWN ] = pV; + pieceValue[Piece.BKING ] = kV; + pieceValue[Piece.BQUEEN ] = qV; + pieceValue[Piece.BROOK ] = rV; + pieceValue[Piece.BBISHOP] = bV; + pieceValue[Piece.BKNIGHT] = nV; + pieceValue[Piece.BPAWN ] = pV; + pieceValue[Piece.EMPTY ] = 0; + } + + /** Piece/square table for king during middle game. */ + static final int[] kt1b = { -22,-35,-40,-40,-40,-40,-35,-22, + -22,-35,-40,-40,-40,-40,-35,-22, + -25,-35,-40,-45,-45,-40,-35,-25, + -15,-30,-35,-40,-40,-35,-30,-15, + -10,-15,-20,-25,-25,-20,-15,-10, + 4, -2, -5,-15,-15, -5, -2, 4, + 16, 14, 7, -3, -3, 7, 14, 16, + 24, 24, 9, 0, 0, 9, 24, 24 }; + + /** Piece/square table for king during end game. */ + static final int[] kt2b = { 0, 8, 16, 24, 24, 16, 8, 0, + 8, 16, 24, 32, 32, 24, 16, 8, + 16, 24, 32, 40, 40, 32, 24, 16, + 24, 32, 40, 48, 48, 40, 32, 24, + 24, 32, 40, 48, 48, 40, 32, 24, + 16, 24, 32, 40, 40, 32, 24, 16, + 8, 16, 24, 32, 32, 24, 16, 8, + 0, 8, 16, 24, 24, 16, 8, 0 }; + + /** Piece/square table for pawns during middle game. */ + static final int[] pt1b = { 0, 0, 0, 0, 0, 0, 0, 0, + 8, 16, 24, 32, 32, 24, 16, 8, + 3, 12, 20, 28, 28, 20, 12, 3, + -5, 4, 10, 20, 20, 10, 4, -5, + -6, 4, 5, 16, 16, 5, 4, -6, + -6, 4, 2, 5, 5, 2, 4, -6, + -6, 4, 4,-15,-15, 4, 4, -6, + 0, 0, 0, 0, 0, 0, 0, 0 }; + + /** Piece/square table for pawns during end game. */ + static final int[] pt2b = { 0, 0, 0, 0, 0, 0, 0, 0, + 25, 40, 45, 45, 45, 45, 40, 25, + 17, 32, 35, 35, 35, 35, 32, 17, + 5, 24, 24, 24, 24, 24, 24, 5, + -9, 11, 11, 11, 11, 11, 11, -9, + -17, 3, 3, 3, 3, 3, 3,-17, + -20, 0, 0, 0, 0, 0, 0,-20, + 0, 0, 0, 0, 0, 0, 0, 0 }; + + /** Piece/square table for knights during middle game. */ + static final int[] nt1b = { -53,-42,-32,-21,-21,-32,-42,-53, + -42,-32,-10, 0, 0,-10,-32,-42, + -21, 5, 10, 16, 16, 10, 5,-21, + -18, 0, 10, 21, 21, 10, 0,-18, + -18, 0, 3, 21, 21, 3, 0,-18, + -21,-10, 0, 0, 0, 0,-10,-21, + -42,-32,-10, 0, 0,-10,-32,-42, + -53,-42,-32,-21,-21,-32,-42,-53 }; + + /** Piece/square table for knights during end game. */ + static final int[] nt2b = { -56,-44,-34,-22,-22,-34,-44,-56, + -44,-34,-10, 0, 0,-10,-34,-44, + -22, 5, 10, 17, 17, 10, 5,-22, + -19, 0, 10, 22, 22, 10, 0,-19, + -19, 0, 3, 22, 22, 3, 0,-19, + -22,-10, 0, 0, 0, 0,-10,-22, + -44,-34,-10, 0, 0,-10,-34,-44, + -56,-44,-34,-22,-22,-34,-44,-56 }; + + /** Piece/square table for bishops during middle game. */ + static final int[] bt1b = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 4, 2, 2, 2, 2, 4, 0, + 0, 2, 4, 4, 4, 4, 2, 0, + 0, 2, 4, 4, 4, 4, 2, 0, + 0, 2, 4, 4, 4, 4, 2, 0, + 0, 3, 4, 4, 4, 4, 3, 0, + 0, 4, 2, 2, 2, 2, 4, 0, + -5, -5, -7, -5, -5, -7, -5, -5 }; + + /** Piece/square table for bishops during middle game. */ + static final int[] bt2b = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 2, 2, 2, 2, 0, + 0, 2, 4, 4, 4, 4, 2, 0, + 0, 2, 4, 4, 4, 4, 2, 0, + 0, 2, 4, 4, 4, 4, 2, 0, + 0, 2, 4, 4, 4, 4, 2, 0, + 0, 2, 2, 2, 2, 2, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + + /** Piece/square table for queens during middle game. */ + static final int[] qt1b = { -10, -5, 0, 0, 0, 0, -5,-10, + -5, 0, 5, 5, 5, 5, 0, -5, + 0, 5, 5, 6, 6, 5, 5, 0, + 0, 5, 6, 6, 6, 6, 5, 0, + 0, 5, 6, 6, 6, 6, 5, 0, + 0, 5, 5, 6, 6, 5, 5, 0, + -5, 0, 5, 5, 5, 5, 0, -5, + -10, -5, 0, 0, 0, 0, -5,-10 }; + + /** Piece/square table for rooks during middle game. */ + static final int[] rt1b = { 8, 11, 13, 13, 13, 13, 11, 8, + 22, 27, 27, 27, 27, 27, 27, 22, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + -2, 0, 0, 0, 0, 0, 0, -2, + -2, 0, 0, 2, 2, 0, 0, -2, + -3, 2, 5, 5, 5, 5, 2, -3, + 0, 3, 5, 5, 5, 5, 3, 0 }; + + static final int[] kt1w, qt1w, rt1w, bt1w, nt1w, pt1w, kt2w, bt2w, nt2w, pt2w; + static { + kt1w = new int[64]; + qt1w = new int[64]; + rt1w = new int[64]; + bt1w = new int[64]; + nt1w = new int[64]; + pt1w = new int[64]; + kt2w = new int[64]; + bt2w = new int[64]; + nt2w = new int[64]; + pt2w = new int[64]; + for (int i = 0; i < 64; i++) { + kt1w[i] = kt1b[63-i]; + qt1w[i] = qt1b[63-i]; + rt1w[i] = rt1b[63-i]; + bt1w[i] = bt1b[63-i]; + nt1w[i] = nt1b[63-i]; + pt1w[i] = pt1b[63-i]; + kt2w[i] = kt2b[63-i]; + bt2w[i] = bt2b[63-i]; + nt2w[i] = nt2b[63-i]; + pt2w[i] = pt2b[63-i]; + } + } + + private static final int[] empty = { 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}; + static final int[][] psTab1 = { empty, kt1w, qt1w, rt1w, bt1w, nt1w, pt1w, + kt1b, qt1b, rt1b, bt1b, nt1b, pt1b }; + static final int[][] psTab2 = { empty, kt2w, qt1w, rt1w, bt2w, nt2w, pt2w, + kt2b, qt1b, rt1b, bt2b, nt2b, pt2b }; + + static final int[][] distToH1A8 = { { 0, 1, 2, 3, 4, 5, 6, 7 }, + { 1, 2, 3, 4, 5, 6, 7, 6 }, + { 2, 3, 4, 5, 6, 7, 6, 5 }, + { 3, 4, 5, 6, 7, 6, 5, 4 }, + { 4, 5, 6, 7, 6, 5, 4, 3 }, + { 5, 6, 7, 6, 5, 4, 3, 2 }, + { 6, 7, 6, 5, 4, 3, 2, 1 }, + { 7, 6, 5, 4, 3, 2, 1, 0 } }; + + static final int[] rookMobScore = {-10,-7,-4,-1,2,5,7,9,11,12,13,14,14,14,14}; + static final int[] bishMobScore = {-15,-10,-6,-2,2,6,10,13,16,18,20,22,23,24}; + static final int[] queenMobScore = {-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,9,10,10,10,10,10,10,10,10,10,10,10,10}; + + private static final class PawnHashData { + long key; + int score; // Positive score means good for white + short passedBonusW; + short passedBonusB; + long passedPawnsW; // The most advanced passed pawns for each file + long passedPawnsB; + } + static final PawnHashData[] pawnHash; + static { + final int numEntries = 1<<16; + pawnHash = new PawnHashData[numEntries]; + for (int i = 0; i < numEntries; i++) { + PawnHashData phd = new PawnHashData(); + phd.key = -1; // Non-zero to avoid collision for positions with no pawns + phd.score = 0; + pawnHash[i] = phd; + } + } + + static byte[] kpkTable = null; + static byte[] krkpTable = null; + + // King safety variables + private long wKingZone, bKingZone; // Squares close to king that are worth attacking + private int wKingAttacks, bKingAttacks; // Number of attacks close to white/black king + private long wAttacksBB, bAttacksBB; + private long wPawnAttacks, bPawnAttacks; // Squares attacked by white/black pawns + + /** Constructor. */ + public Evaluate() { + if (kpkTable == null) + kpkTable = readTable("/kpk.bitbase", 2*32*64*48/8); + if (krkpTable == null) + krkpTable = readTable("/krkp.winmasks", 2*32*48*8); + } + + private byte[] readTable(String resource, int length) { + byte[] table = new byte[2*32*64*48/8]; + InputStream inStream = getClass().getResourceAsStream(resource); + try { + int off = 0; + while (off < table.length) { + int len = inStream.read(table, off, table.length - off); + if (len < 0) + throw new RuntimeException(); + off += len; + } + inStream.close(); + return table; + } catch (IOException e) { + throw new RuntimeException(); + } + } + + /** + * Static evaluation of a position. + * @param pos The position to evaluate. + * @return The evaluation score, measured in centipawns. + * Positive values are good for the side to make the next move. + */ + final public int evalPos(Position pos) { + int score = pos.wMtrl - pos.bMtrl; + + wKingAttacks = bKingAttacks = 0; + wKingZone = BitBoard.kingAttacks[pos.getKingSq(true)]; wKingZone |= wKingZone << 8; + bKingZone = BitBoard.kingAttacks[pos.getKingSq(false)]; bKingZone |= bKingZone >>> 8; + wAttacksBB = bAttacksBB = 0L; + + long pawns = pos.pieceTypeBB[Piece.WPAWN]; + wPawnAttacks = ((pawns & BitBoard.maskBToHFiles) << 7) | + ((pawns & BitBoard.maskAToGFiles) << 9); + pawns = pos.pieceTypeBB[Piece.BPAWN]; + bPawnAttacks = ((pawns & BitBoard.maskBToHFiles) >>> 9) | + ((pawns & BitBoard.maskAToGFiles) >>> 7); + + score += pieceSquareEval(pos); + score += pawnBonus(pos); + score += tradeBonus(pos); + score += castleBonus(pos); + + score += rookBonus(pos); + score += bishopEval(pos, score); + score += threatBonus(pos); + score += kingSafety(pos); + score = endGameEval(pos, score); + + if (!pos.whiteMove) + score = -score; + return score; + + // FIXME! Test penalty if side to move has >1 hanging piece + + // FIXME! Test "tempo value" + } + + /** Compute white_material - black_material. */ + static final int material(Position pos) { + return pos.wMtrl - pos.bMtrl; + } + + /** Compute score based on piece square tables. Positive values are good for white. */ + private final int pieceSquareEval(Position pos) { + int score = 0; + final int wMtrl = pos.wMtrl; + final int bMtrl = pos.bMtrl; + final int wMtrlPawns = pos.wMtrlPawns; + final int bMtrlPawns = pos.bMtrlPawns; + + // Kings + { + final int t1 = qV + 2 * rV + 2 * bV; + final int t2 = rV; + { + final int k1 = pos.psScore1[Piece.WKING]; + final int k2 = pos.psScore2[Piece.WKING]; + final int t = bMtrl - bMtrlPawns; + score += interpolate(t, t2, k2, t1, k1); + } + { + final int k1 = pos.psScore1[Piece.BKING]; + final int k2 = pos.psScore2[Piece.BKING]; + final int t = wMtrl - wMtrlPawns; + score -= interpolate(t, t2, k2, t1, k1); + } + } + + // Pawns + { + final int t1 = qV + 2 * rV + 2 * bV; + final int t2 = rV; + int wp1 = pos.psScore1[Piece.WPAWN]; + int wp2 = pos.psScore2[Piece.WPAWN]; + if ((wp1 != 0) || (wp2 != 0)) { + final int tw = bMtrl - bMtrlPawns; + score += interpolate(tw, t2, wp2, t1, wp1); + } + int bp1 = pos.psScore1[Piece.BPAWN]; + int bp2 = pos.psScore2[Piece.BPAWN]; + if ((bp1 != 0) || (bp2 != 0)) { + final int tb = wMtrl - wMtrlPawns; + score -= interpolate(tb, t2, bp2, t1, bp1); + } + } + + // Knights + { + final int t1 = qV + 2 * rV + 1 * bV + 1 * nV + 6 * pV; + final int t2 = nV + 8 * pV; + int n1 = pos.psScore1[Piece.WKNIGHT]; + int n2 = pos.psScore2[Piece.WKNIGHT]; + if ((n1 != 0) || (n2 != 0)) { + score += interpolate(bMtrl, t2, n2, t1, n1); + } + n1 = pos.psScore1[Piece.BKNIGHT]; + n2 = pos.psScore2[Piece.BKNIGHT]; + if ((n1 != 0) || (n2 != 0)) { + score -= interpolate(wMtrl, t2, n2, t1, n1); + } + } + + // Bishops + { + score += pos.psScore1[Piece.WBISHOP]; + score -= pos.psScore1[Piece.BBISHOP]; + } + + // Queens + { + final long occupied = pos.whiteBB | pos.blackBB; + score += pos.psScore1[Piece.WQUEEN]; + long m = pos.pieceTypeBB[Piece.WQUEEN]; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + long atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied); + wAttacksBB |= atk; + score += queenMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))]; + bKingAttacks += Long.bitCount(atk & bKingZone) * 2; + m &= m-1; + } + score -= pos.psScore1[Piece.BQUEEN]; + m = pos.pieceTypeBB[Piece.BQUEEN]; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + long atk = BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied); + bAttacksBB |= atk; + score -= queenMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))]; + wKingAttacks += Long.bitCount(atk & wKingZone) * 2; + m &= m-1; + } + } + + // Rooks + { + int r1 = pos.psScore1[Piece.WROOK]; + if (r1 != 0) { + final int nP = bMtrlPawns / pV; + final int s = r1 * Math.min(nP, 6) / 6; + score += s; + } + r1 = pos.psScore1[Piece.BROOK]; + if (r1 != 0) { + final int nP = wMtrlPawns / pV; + final int s = r1 * Math.min(nP, 6) / 6; + score -= s; + } + } + + return score; + } + + /** Implement the "when ahead trade pieces, when behind trade pawns" rule. */ + private final int tradeBonus(Position pos) { + final int wM = pos.wMtrl; + final int bM = pos.bMtrl; + final int wPawn = pos.wMtrlPawns; + final int bPawn = pos.bMtrlPawns; + final int deltaScore = wM - bM; + + int pBonus = 0; + pBonus += interpolate((deltaScore > 0) ? wPawn : bPawn, 0, -30 * deltaScore / 100, 6 * pV, 0); + pBonus += interpolate((deltaScore > 0) ? bM : wM, 0, 30 * deltaScore / 100, qV + 2 * rV + 2 * bV + 2 * nV, 0); + + return pBonus; + } + + private static final int[] castleFactor; + static { + castleFactor = new int[256]; + for (int i = 0; i < 256; i++) { + int h1Dist = 100; + boolean h1Castle = (i & (1<<7)) != 0; + if (h1Castle) + h1Dist = 2 + Long.bitCount(i & 0x0000000000000060L); // f1,g1 + int a1Dist = 100; + boolean a1Castle = (i & 1) != 0; + if (a1Castle) + a1Dist = 2 + Long.bitCount(i & 0x000000000000000EL); // b1,c1,d1 + castleFactor[i] = 1024 / Math.min(a1Dist, h1Dist); + } + } + + /** Score castling ability. */ + private final int castleBonus(Position pos) { + if (pos.getCastleMask() == 0) return 0; + + final int k1 = kt1b[7*8+6] - kt1b[7*8+4]; + final int k2 = kt2b[7*8+6] - kt2b[7*8+4]; + final int t1 = qV + 2 * rV + 2 * bV; + final int t2 = rV; + final int t = pos.bMtrl - pos.bMtrlPawns; + final int ks = interpolate(t, t2, k2, t1, k1); + + final int castleValue = ks + rt1b[7*8+5] - rt1b[7*8+7]; + if (castleValue <= 0) + return 0; + + long occupied = pos.whiteBB | pos.blackBB; + int tmp = (int) (occupied & 0x6E); + if (pos.a1Castle()) tmp |= 1; + if (pos.h1Castle()) tmp |= (1 << 7); + final int wBonus = (castleValue * castleFactor[tmp]) >> 10; + + tmp = (int) ((occupied >>> 56) & 0x6E); + if (pos.a8Castle()) tmp |= 1; + if (pos.h8Castle()) tmp |= (1 << 7); + final int bBonus = (castleValue * castleFactor[tmp]) >> 10; + + return wBonus - bBonus; + } + + private final int pawnBonus(Position pos) { + long key = pos.pawnZobristHash(); + PawnHashData phd = pawnHash[(int)key & (pawnHash.length - 1)]; + if (phd.key != key) + computePawnHashData(pos, phd); + int score = phd.score; + + final int hiMtrl = qV + rV; + score += interpolate(pos.bMtrl - pos.bMtrlPawns, 0, 2 * phd.passedBonusW, hiMtrl, phd.passedBonusW); + score -= interpolate(pos.wMtrl - pos.wMtrlPawns, 0, 2 * phd.passedBonusB, hiMtrl, phd.passedBonusB); + + // Passed pawns are more dangerous if enemy king is far away + int mtrlNoPawns; + final int highMtrl = qV + rV; + long m = phd.passedPawnsW; + if (m != 0) { + mtrlNoPawns = pos.bMtrl - pos.bMtrlPawns; + if (mtrlNoPawns < highMtrl) { + int kingPos = pos.getKingSq(false); + int kingX = Position.getX(kingPos); + int kingY = Position.getY(kingPos); + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + int x = Position.getX(sq); + int y = Position.getY(sq); + int pawnDist = Math.min(5, 7 - y); + int kingDistX = Math.abs(kingX - x); + int kingDistY = Math.abs(kingY - 7); + int kingDist = Math.max(kingDistX, kingDistY); + int kScore = kingDist * 4; + if (kingDist > pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist); + score += interpolate(mtrlNoPawns, 0, kScore, highMtrl, 0); + if (!pos.whiteMove) + kingDist--; + if ((pawnDist < kingDist) && (mtrlNoPawns == 0)) + score += 500; // King can't stop pawn + m &= m-1; + } + } + } + m = phd.passedPawnsB; + if (m != 0) { + mtrlNoPawns = pos.wMtrl - pos.wMtrlPawns; + if (mtrlNoPawns < highMtrl) { + int kingPos = pos.getKingSq(true); + int kingX = Position.getX(kingPos); + int kingY = Position.getY(kingPos); + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + int x = Position.getX(sq); + int y = Position.getY(sq); + int pawnDist = Math.min(5, y); + int kingDistX = Math.abs(kingX - x); + int kingDistY = Math.abs(kingY - 0); + int kingDist = Math.max(kingDistX, kingDistY); + int kScore = kingDist * 4; + if (kingDist > pawnDist) kScore += (kingDist - pawnDist) * (kingDist - pawnDist); + score -= interpolate(mtrlNoPawns, 0, kScore, highMtrl, 0); + if (pos.whiteMove) + kingDist--; + if ((pawnDist < kingDist) && (mtrlNoPawns == 0)) + score -= 500; // King can't stop pawn + m &= m-1; + } + } + } + + return score; + } + + /** Compute pawn hash data for pos. */ + private final void computePawnHashData(Position pos, PawnHashData ph) { + int score = 0; + + // Evaluate double pawns and pawn islands + long wPawns = pos.pieceTypeBB[Piece.WPAWN]; + long wPawnFiles = BitBoard.southFill(wPawns) & 0xff; + int wDouble = Long.bitCount(wPawns) - Long.bitCount(wPawnFiles); + int wIslands = Long.bitCount(((~wPawnFiles) >>> 1) & wPawnFiles); + int wIsolated = Long.bitCount(~(wPawnFiles<<1) & wPawnFiles & ~(wPawnFiles>>>1)); + + + long bPawns = pos.pieceTypeBB[Piece.BPAWN]; + long bPawnFiles = BitBoard.southFill(bPawns) & 0xff; + int bDouble = Long.bitCount(bPawns) - Long.bitCount(bPawnFiles); + int bIslands = Long.bitCount(((~bPawnFiles) >>> 1) & bPawnFiles); + int bIsolated = Long.bitCount(~(bPawnFiles<<1) & bPawnFiles & ~(bPawnFiles>>>1)); + + score -= (wDouble - bDouble) * 25; + score -= (wIslands - bIslands) * 15; + score -= (wIsolated - bIsolated) * 15; + + // Evaluate backward pawns, defined as a pawn that guards a friendly pawn, + // can't be guarded by friendly pawns, can advance, but can't advance without + // being captured by an enemy pawn. + long wPawnAttacks = (((wPawns & BitBoard.maskBToHFiles) << 7) | + ((wPawns & BitBoard.maskAToGFiles) << 9)); + long bPawnAttacks = (((bPawns & BitBoard.maskBToHFiles) >>> 9) | + ((bPawns & BitBoard.maskAToGFiles) >>> 7)); + long wBackward = wPawns & ~((wPawns | bPawns) >>> 8) & (bPawnAttacks >>> 8) & + ~BitBoard.northFill(wPawnAttacks); + wBackward &= (((wPawns & BitBoard.maskBToHFiles) >>> 9) | + ((wPawns & BitBoard.maskAToGFiles) >>> 7)); + wBackward &= ~BitBoard.northFill(bPawnFiles); + long bBackward = bPawns & ~((wPawns | bPawns) << 8) & (wPawnAttacks << 8) & + ~BitBoard.southFill(bPawnAttacks); + bBackward &= (((bPawns & BitBoard.maskBToHFiles) << 7) | + ((bPawns & BitBoard.maskAToGFiles) << 9)); + bBackward &= ~BitBoard.northFill(wPawnFiles); + score -= (Long.bitCount(wBackward) - Long.bitCount(bBackward)) * 15; + + // Evaluate passed pawn bonus, white + long passedPawnsW = wPawns & ~BitBoard.southFill(bPawns | bPawnAttacks | (wPawns >>> 8)); + final int[] ppBonus = {-1,24,26,30,36,47,64,-1}; + int passedBonusW = 0; + if (passedPawnsW != 0) { + long guardedPassedW = passedPawnsW & (((wPawns & BitBoard.maskBToHFiles) << 7) | + ((wPawns & BitBoard.maskAToGFiles) << 9)); + passedBonusW += 15 * Long.bitCount(guardedPassedW); + long m = passedPawnsW; + while (m != 0) { + int sq = Long .numberOfTrailingZeros(m); + int y = Position.getY(sq); + passedBonusW += ppBonus[y]; + m &= m-1; + } + } + + // Evaluate passed pawn bonus, black + long passedPawnsB = bPawns & ~BitBoard.northFill(wPawns | wPawnAttacks | (bPawns << 8)); + int passedBonusB = 0; + if (passedPawnsB != 0) { + long guardedPassedB = passedPawnsB & (((bPawns & BitBoard.maskBToHFiles) >>> 9) | + ((bPawns & BitBoard.maskAToGFiles) >>> 7)); + passedBonusB += 15 * Long.bitCount(guardedPassedB); + long m = passedPawnsB; + while (m != 0) { + int sq = Long .numberOfTrailingZeros(m); + int y = Position.getY(sq); + passedBonusB += ppBonus[7-y]; + m &= m-1; + } + } + + // Connected passed pawn bonus. Seems logical but doesn't help in tests +// if (passedPawnsW != 0) +// passedBonusW += 15 * Long.bitCount(passedPawnsW & ((passedPawnsW & BitBoard.maskBToHFiles) >>> 1)); +// if (passedPawnsB != 0) +// passedBonusB += 15 * Long.bitCount(passedPawnsB & ((passedPawnsB & BitBoard.maskBToHFiles) >>> 1)); + + ph.key = pos.pawnZobristHash(); + ph.score = score; + ph.passedBonusW = (short)passedBonusW; + ph.passedBonusB = (short)passedBonusB; + ph.passedPawnsW = passedPawnsW; + ph.passedPawnsB = passedPawnsB; + } + + /** Compute rook bonus. Rook on open/half-open file. */ + private final int rookBonus(Position pos) { + int score = 0; + final long wPawns = pos.pieceTypeBB[Piece.WPAWN]; + final long bPawns = pos.pieceTypeBB[Piece.BPAWN]; + final long occupied = pos.whiteBB | pos.blackBB; + long m = pos.pieceTypeBB[Piece.WROOK]; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + final int x = Position.getX(sq); + if ((wPawns & BitBoard.maskFile[x]) == 0) { // At least half-open file + score += (bPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12; + } + long atk = BitBoard.rookAttacks(sq, occupied); + wAttacksBB |= atk; + score += rookMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))]; + if ((atk & bKingZone) != 0) + bKingAttacks += Long.bitCount(atk & bKingZone); + m &= m-1; + } + long r7 = (pos.pieceTypeBB[Piece.WROOK] >>> 48) & 0x00ffL; + if (((r7 & (r7 - 1)) != 0) && + ((pos.pieceTypeBB[Piece.BKING] & 0xff00000000000000L) != 0)) + score += 30; // Two rooks on 7:th row + m = pos.pieceTypeBB[Piece.BROOK]; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + final int x = Position.getX(sq); + if ((bPawns & BitBoard.maskFile[x]) == 0) { + score -= (wPawns & BitBoard.maskFile[x]) == 0 ? 25 : 12; + } + long atk = BitBoard.rookAttacks(sq, occupied); + bAttacksBB |= atk; + score -= rookMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))]; + if ((atk & wKingZone) != 0) + wKingAttacks += Long.bitCount(atk & wKingZone); + m &= m-1; + } + r7 = pos.pieceTypeBB[Piece.BROOK] & 0xff00L; + if (((r7 & (r7 - 1)) != 0) && + ((pos.pieceTypeBB[Piece.WKING] & 0xffL) != 0)) + score -= 30; // Two rooks on 2:nd row + return score; + } + + /** Compute bishop evaluation. */ + private final int bishopEval(Position pos, int oldScore) { + int score = 0; + final long occupied = pos.whiteBB | pos.blackBB; + long wBishops = pos.pieceTypeBB[Piece.WBISHOP]; + long bBishops = pos.pieceTypeBB[Piece.BBISHOP]; + if ((wBishops | bBishops) == 0) + return 0; + long m = wBishops; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + long atk = BitBoard.bishopAttacks(sq, occupied); + wAttacksBB |= atk; + score += bishMobScore[Long.bitCount(atk & ~(pos.whiteBB | bPawnAttacks))]; + if ((atk & bKingZone) != 0) + bKingAttacks += Long.bitCount(atk & bKingZone); + m &= m-1; + } + m = bBishops; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + long atk = BitBoard.bishopAttacks(sq, occupied); + bAttacksBB |= atk; + score -= bishMobScore[Long.bitCount(atk & ~(pos.blackBB | wPawnAttacks))]; + if ((atk & wKingZone) != 0) + wKingAttacks += Long.bitCount(atk & wKingZone); + m &= m-1; + } + + boolean whiteDark = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq ) != 0; + boolean whiteLight = (pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) != 0; + boolean blackDark = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq ) != 0; + boolean blackLight = (pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) != 0; + int numWhite = (whiteDark ? 1 : 0) + (whiteLight ? 1 : 0); + int numBlack = (blackDark ? 1 : 0) + (blackLight ? 1 : 0); + + // Bishop pair bonus + if (numWhite == 2) { + final int numPawns = pos.wMtrlPawns / pV; + score += 28 + (8 - numPawns) * 3; + } + if (numBlack == 2) { + final int numPawns = pos.bMtrlPawns / pV; + score -= 28 + (8 - numPawns) * 3; + } + + // FIXME!!! Bad bishop + + if ((numWhite == 1) && (numBlack == 1) && (whiteDark != blackDark) && + (pos.wMtrl - pos.wMtrlPawns == pos.bMtrl - pos.bMtrlPawns)) { + final int penalty = (oldScore + score) / 2; + final int loMtrl = 2 * bV; + final int hiMtrl = 2 * (qV + rV + bV); + int mtrl = pos.wMtrl + pos.bMtrl - pos.wMtrlPawns - pos.bMtrlPawns; + score -= interpolate(mtrl, loMtrl, penalty, hiMtrl, 0); + } + + // Penalty for bishop trapped behind pawn at a2/h2/a7/h7 + if (((wBishops | bBishops) & 0x0081000000008100L) != 0) { + if ((pos.squares[48] == Piece.WBISHOP) && // a7 + (pos.squares[41] == Piece.BPAWN) && + (pos.squares[50] == Piece.BPAWN)) + score -= pV * 3 / 2; + if ((pos.squares[55] == Piece.WBISHOP) && // h7 + (pos.squares[46] == Piece.BPAWN) && + (pos.squares[53] == Piece.BPAWN)) + score -= (pos.pieceTypeBB[Piece.WQUEEN] != 0) ? pV : pV * 3 / 2; + if ((pos.squares[8] == Piece.BBISHOP) && // a2 + (pos.squares[17] == Piece.WPAWN) && + (pos.squares[10] == Piece.WPAWN)) + score += pV * 3 / 2; + if ((pos.squares[15] == Piece.BBISHOP) && // h2 + (pos.squares[22] == Piece.WPAWN) && + (pos.squares[13] == Piece.WPAWN)) + score += (pos.pieceTypeBB[Piece.BQUEEN] != 0) ? pV : pV * 3 / 2; + } + + return score; + } + + private int threatBonus(Position pos) { + int score = 0; + + // Sum values for all black pieces under attack + long m = pos.pieceTypeBB[Piece.WKNIGHT]; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + wAttacksBB |= BitBoard.knightAttacks[sq]; + m &= m-1; + } + wAttacksBB &= (pos.pieceTypeBB[Piece.BKNIGHT] | + pos.pieceTypeBB[Piece.BBISHOP] | + pos.pieceTypeBB[Piece.BROOK] | + pos.pieceTypeBB[Piece.BQUEEN]); + wAttacksBB |= wPawnAttacks; + m = wAttacksBB & pos.blackBB & ~pos.pieceTypeBB[Piece.BKING]; + int tmp = 0; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + tmp += pieceValue[pos.squares[sq]]; + m &= m-1; + } + score += tmp + tmp * tmp / qV; + + // Sum values for all white pieces under attack + m = pos.pieceTypeBB[Piece.BKNIGHT]; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + bAttacksBB |= BitBoard.knightAttacks[sq]; + m &= m-1; + } + bAttacksBB &= (pos.pieceTypeBB[Piece.WKNIGHT] | + pos.pieceTypeBB[Piece.WBISHOP] | + pos.pieceTypeBB[Piece.WROOK] | + pos.pieceTypeBB[Piece.WQUEEN]); + bAttacksBB |= bPawnAttacks; + m = bAttacksBB & pos.whiteBB & ~pos.pieceTypeBB[Piece.WKING]; + tmp = 0; + while (m != 0) { + int sq = BitBoard.numberOfTrailingZeros(m); + tmp += pieceValue[pos.squares[sq]]; + m &= m-1; + } + score -= tmp + tmp * tmp / qV; + return score / 64; + } + + /** Compute king safety for both kings. */ + private final int kingSafety(Position pos) { + final int minM = rV + bV; + final int m = (pos.wMtrl - pos.wMtrlPawns + pos.bMtrl - pos.bMtrlPawns) / 2; + if (m <= minM) + return 0; + final int maxM = qV + 2 * rV + 2 * bV + 2 * nV; + int score = kingSafetyKPPart(pos); + if (Position.getY(pos.wKingSq) == 0) { + if (((pos.pieceTypeBB[Piece.WKING] & 0x60L) != 0) && // King on f1 or g1 + ((pos.pieceTypeBB[Piece.WROOK] & 0xC0L) != 0) && // Rook on g1 or h1 + ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[6]) != 0) && + ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[7]) != 0)) { + score -= 6 * 15; + } else + if (((pos.pieceTypeBB[Piece.WKING] & 0x6L) != 0) && // King on b1 or c1 + ((pos.pieceTypeBB[Piece.WROOK] & 0x3L) != 0) && // Rook on a1 or b1 + ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[0]) != 0) && + ((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskFile[1]) != 0)) { + score -= 6 * 15; + } + } + if (Position.getY(pos.bKingSq) == 7) { + if (((pos.pieceTypeBB[Piece.BKING] & 0x6000000000000000L) != 0) && // King on f8 or g8 + ((pos.pieceTypeBB[Piece.BROOK] & 0xC000000000000000L) != 0) && // Rook on g8 or h8 + ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[6]) != 0) && + ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[7]) != 0)) { + score += 6 * 15; + } else + if (((pos.pieceTypeBB[Piece.BKING] & 0x600000000000000L) != 0) && // King on b8 or c8 + ((pos.pieceTypeBB[Piece.BROOK] & 0x300000000000000L) != 0) && // Rook on a8 or b8 + ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[0]) != 0) && + ((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskFile[1]) != 0)) { + score += 6 * 15; + } + } + score += (bKingAttacks - wKingAttacks) * 4; + final int kSafety = interpolate(m, minM, 0, maxM, score); + return kSafety; + } + + private static final class KingSafetyHashData { + long key; + int score; + } + private static final KingSafetyHashData[] kingSafetyHash; + static { + final int numEntries = 1 << 15; + kingSafetyHash = new KingSafetyHashData[numEntries]; + for (int i = 0; i < numEntries; i++) { + KingSafetyHashData ksh = new KingSafetyHashData(); + ksh.key = -1; + ksh.score = 0; + kingSafetyHash[i] = ksh; + } + } + + private final int kingSafetyKPPart(Position pos) { + final long key = pos.pawnZobristHash() ^ pos.kingZobristHash(); + KingSafetyHashData ksh = kingSafetyHash[(int)key & (kingSafetyHash.length - 1)]; + if (ksh.key != key) { + int score = 0; + long wPawns = pos.pieceTypeBB[Piece.WPAWN]; + long bPawns = pos.pieceTypeBB[Piece.BPAWN]; + { + int safety = 0; + int halfOpenFiles = 0; + if (Position.getY(pos.wKingSq) < 2) { + long shelter = 1L << Position.getX(pos.wKingSq); + shelter |= ((shelter & BitBoard.maskBToHFiles) >>> 1) | + ((shelter & BitBoard.maskAToGFiles) << 1); + shelter <<= 8; + safety += 3 * Long.bitCount(wPawns & shelter); + safety -= 2 * Long.bitCount(bPawns & (shelter | (shelter << 8))); + shelter <<= 8; + safety += 2 * Long.bitCount(wPawns & shelter); + shelter <<= 8; + safety -= Long.bitCount(bPawns & shelter); + + long wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff; + if (wOpen != 0) { + halfOpenFiles += 25 * Long.bitCount(wOpen & 0xe7); + halfOpenFiles += 10 * Long.bitCount(wOpen & 0x18); + } + long bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff; + if (bOpen != 0) { + halfOpenFiles += 25 * Long.bitCount(bOpen & 0xe7); + halfOpenFiles += 10 * Long.bitCount(bOpen & 0x18); + } + safety = Math.min(safety, 8); + } + final int kSafety = (safety - 9) * 15 - halfOpenFiles; + score += kSafety; + } + { + int safety = 0; + int halfOpenFiles = 0; + if (Position.getY(pos.bKingSq) >= 6) { + long shelter = 1L << (56 + Position.getX(pos.bKingSq)); + shelter |= ((shelter & BitBoard.maskBToHFiles) >>> 1) | + ((shelter & BitBoard.maskAToGFiles) << 1); + shelter >>>= 8; + safety += 3 * Long.bitCount(bPawns & shelter); + safety -= 2 * Long.bitCount(wPawns & (shelter | (shelter >>> 8))); + shelter >>>= 8; + safety += 2 * Long.bitCount(bPawns & shelter); + shelter >>>= 8; + safety -= Long.bitCount(wPawns & shelter); + + long wOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(wPawns)) & 0xff; + if (wOpen != 0) { + halfOpenFiles += 25 * Long.bitCount(wOpen & 0xe7); + halfOpenFiles += 10 * Long.bitCount(wOpen & 0x18); + } + long bOpen = BitBoard.southFill(shelter) & (~BitBoard.southFill(bPawns)) & 0xff; + if (bOpen != 0) { + halfOpenFiles += 25 * Long.bitCount(bOpen & 0xe7); + halfOpenFiles += 10 * Long.bitCount(bOpen & 0x18); + } + safety = Math.min(safety, 8); + } + final int kSafety = (safety - 9) * 15 - halfOpenFiles; + score -= kSafety; + } + ksh.key = key; + ksh.score = score; + } + return ksh.score; + } + + /** Implements special knowledge for some endgame situations. */ + private final int endGameEval(Position pos, int oldScore) { + int score = oldScore; + if (pos.wMtrl + pos.bMtrl > 6 * rV) + return score; + final int wMtrlPawns = pos.wMtrlPawns; + final int bMtrlPawns = pos.bMtrlPawns; + final int wMtrlNoPawns = pos.wMtrl - wMtrlPawns; + final int bMtrlNoPawns = pos.bMtrl - bMtrlPawns; + + boolean handled = false; + if ((wMtrlPawns + bMtrlPawns == 0) && (wMtrlNoPawns < rV) && (bMtrlNoPawns < rV)) { + // King + minor piece vs king + minor piece is a draw + return 0; + } + if (!handled && (pos.wMtrl == qV) && (pos.bMtrl == pV) && + (Long.bitCount(pos.pieceTypeBB[Piece.WQUEEN]) == 1)) { + int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]); + int wq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WQUEEN]); + int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]); + int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]); + score = evalKQKP(wk, wq, bk, bp); + handled = true; + } + if (!handled && (pos.wMtrl == rV) && (pos.bMtrl == pV) && + (Long.bitCount(pos.pieceTypeBB[Piece.WROOK]) == 1)) { + int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]); + score = krkpEval(pos.getKingSq(true), pos.getKingSq(false), + bp, pos.whiteMove); + handled = true; + } + if (!handled && (pos.bMtrl == qV) && (pos.wMtrl == pV) && + (Long.bitCount(pos.pieceTypeBB[Piece.BQUEEN]) == 1)) { + int bk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BKING]); + int bq = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BQUEEN]); + int wk = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WKING]); + int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]); + score = -evalKQKP(63-bk, 63-bq, 63-wk, 63-wp); + handled = true; + } + if (!handled && (pos.bMtrl == rV) && (pos.wMtrl == pV) && + (Long.bitCount(pos.pieceTypeBB[Piece.BROOK]) == 1)) { + int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]); + score = -krkpEval(63-pos.getKingSq(false), 63-pos.getKingSq(true), + 63-wp, !pos.whiteMove); + handled = true; + } + if (!handled && (score > 0)) { + if ((wMtrlPawns == 0) && (wMtrlNoPawns <= bMtrlNoPawns + bV)) { + if (wMtrlNoPawns < rV) { + return -pos.bMtrl / 50; + } else { + score /= 8; // Too little excess material, probably draw + handled = true; + } + } else if ((pos.pieceTypeBB[Piece.WROOK] | pos.pieceTypeBB[Piece.WKNIGHT] | + pos.pieceTypeBB[Piece.WQUEEN]) == 0) { + // Check for rook pawn + wrong color bishop + if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskBToHFiles) == 0) && + ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskLightSq) == 0) && + ((pos.pieceTypeBB[Piece.BKING] & 0x0303000000000000L) != 0)) { + return 0; + } else + if (((pos.pieceTypeBB[Piece.WPAWN] & BitBoard.maskAToGFiles) == 0) && + ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) == 0) && + ((pos.pieceTypeBB[Piece.BKING] & 0xC0C0000000000000L) != 0)) { + return 0; + } + } + } + if (!handled) { + if (bMtrlPawns == 0) { + if (wMtrlNoPawns - bMtrlNoPawns > bV) { + int wKnights = Long.bitCount(pos.pieceTypeBB[Piece.WKNIGHT]); + int wBishops = Long.bitCount(pos.pieceTypeBB[Piece.WBISHOP]); + if ((wKnights == 2) && (wMtrlNoPawns == 2 * nV) && (bMtrlNoPawns == 0)) { + score /= 50; // KNNK is a draw + } else if ((wKnights == 1) && (wBishops == 1) && (wMtrlNoPawns == nV + bV) && (bMtrlNoPawns == 0)) { + score /= 10; + score += nV + bV + 300; + final int kSq = pos.getKingSq(false); + final int x = Position.getX(kSq); + final int y = Position.getY(kSq); + if ((pos.pieceTypeBB[Piece.WBISHOP] & BitBoard.maskDarkSq) != 0) { + score += (7 - distToH1A8[7-y][7-x]) * 10; + } else { + score += (7 - distToH1A8[7-y][x]) * 10; + } + } else { + score += 300; // Enough excess material, should win + } + handled = true; + } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (wMtrlPawns == pV)) { // KPK + int wp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.WPAWN]); + score = kpkEval(pos.getKingSq(true), pos.getKingSq(false), + wp, pos.whiteMove); + handled = true; + } + } + } + if (!handled && (score < 0)) { + if ((bMtrlPawns == 0) && (bMtrlNoPawns <= wMtrlNoPawns + bV)) { + if (bMtrlNoPawns < rV) { + return pos.wMtrl / 50; + } else { + score /= 8; // Too little excess material, probably draw + handled = true; + } + } else if ((pos.pieceTypeBB[Piece.BROOK] | pos.pieceTypeBB[Piece.BKNIGHT] | + pos.pieceTypeBB[Piece.BQUEEN]) == 0) { + // Check for rook pawn + wrong color bishop + if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskBToHFiles) == 0) && + ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) == 0) && + ((pos.pieceTypeBB[Piece.WKING] & 0x0303L) != 0)) { + return 0; + } else + if (((pos.pieceTypeBB[Piece.BPAWN] & BitBoard.maskAToGFiles) == 0) && + ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskLightSq) == 0) && + ((pos.pieceTypeBB[Piece.WKING] & 0xC0C0L) != 0)) { + return 0; + } + } + } + if (!handled) { + if (wMtrlPawns == 0) { + if (bMtrlNoPawns - wMtrlNoPawns > bV) { + int bKnights = Long.bitCount(pos.pieceTypeBB[Piece.BKNIGHT]); + int bBishops = Long.bitCount(pos.pieceTypeBB[Piece.BBISHOP]); + if ((bKnights == 2) && (bMtrlNoPawns == 2 * nV) && (wMtrlNoPawns == 0)) { + score /= 50; // KNNK is a draw + } else if ((bKnights == 1) && (bBishops == 1) && (bMtrlNoPawns == nV + bV) && (wMtrlNoPawns == 0)) { + score /= 10; + score -= nV + bV + 300; + final int kSq = pos.getKingSq(true); + final int x = Position.getX(kSq); + final int y = Position.getY(kSq); + if ((pos.pieceTypeBB[Piece.BBISHOP] & BitBoard.maskDarkSq) != 0) { + score -= (7 - distToH1A8[7-y][7-x]) * 10; + } else { + score -= (7 - distToH1A8[7-y][x]) * 10; + } + } else { + score -= 300; // Enough excess material, should win + } + handled = true; + } else if ((wMtrlNoPawns + bMtrlNoPawns == 0) && (bMtrlPawns == pV)) { // KPK + int bp = BitBoard.numberOfTrailingZeros(pos.pieceTypeBB[Piece.BPAWN]); + score = -kpkEval(63-pos.getKingSq(false), 63-pos.getKingSq(true), + 63-bp, !pos.whiteMove); + handled = true; + } + } + } + return score; + + // FIXME! Add evaluation of KRPKR : eg 8/8/8/5pk1/1r6/R7/8/4K3 w - - 0 74 + // FIXME! KRBKR is very hard to draw + } + + private static final int evalKQKP(int wKing, int wQueen, int bKing, int bPawn) { + boolean canWin = false; + if (((1L << bKing) & 0xFFFF) == 0) { + canWin = true; // King doesn't support pawn + } else if (Math.abs(Position.getX(bPawn) - Position.getX(bKing)) > 2) { + canWin = true; // King doesn't support pawn + } else { + switch (bPawn) { + case 8: // a2 + canWin = ((1L << wKing) & 0x0F1F1F1F1FL) != 0; + break; + case 10: // c2 + canWin = ((1L << wKing) & 0x071F1F1FL) != 0; + break; + case 13: // f2 + canWin = ((1L << wKing) & 0xE0F8F8F8L) != 0; + break; + case 15: // h2 + canWin = ((1L << wKing) & 0xF0F8F8F8F8L) != 0; + break; + default: + canWin = true; + break; + } + } + + final int dist = Math.max(Math.abs(Position.getX(wKing)-Position.getX(bPawn)), + Math.abs(Position.getY(wKing)-Position.getY(bPawn))); + int score = qV - pV - 20 * dist; + if (!canWin) + score /= 50; + return score; + } + + private static final int kpkEval(int wKing, int bKing, int wPawn, boolean whiteMove) { + if (Position.getX(wKing) >= 4) { // Mirror X + wKing ^= 7; + bKing ^= 7; + wPawn ^= 7; + } + int index = whiteMove ? 0 : 1; + index = index * 32 + Position.getY(wKing)*4+Position.getX(wKing); + index = index * 64 + bKing; + index = index * 48 + wPawn - 8; + + int bytePos = index / 8; + int bitPos = index % 8; + boolean draw = (((int)kpkTable[bytePos]) & (1 << bitPos)) == 0; + if (draw) + return 0; + return qV - pV / 4 * (7-Position.getY(wPawn)); + } + + private static final int krkpEval(int wKing, int bKing, int bPawn, boolean whiteMove) { + if (Position.getX(bKing) >= 4) { // Mirror X + wKing ^= 7; + bKing ^= 7; + bPawn ^= 7; + } + int index = whiteMove ? 0 : 1; + index = index * 32 + Position.getY(bKing)*4+Position.getX(bKing); + index = index * 48 + bPawn - 8; + index = index * 8 + Position.getY(wKing); + byte mask = krkpTable[index]; + boolean canWin = (mask & (1 << Position.getX(wKing))) != 0; + + int score = rV - pV + Position.getY(bPawn) * pV / 4; + if (canWin) + score += 150; + else + score /= 50; + return score; + } + + /** + * Interpolate between (x1,y1) and (x2,y2). + * If x < x1, return y1, if x > x2 return y2. Otherwise, use linear interpolation. + */ + static final int interpolate(int x, int x1, int y1, int x2, int y2) { + if (x > x2) { + return y2; + } else if (x < x1) { + return y1; + } else { + return (x - x1) * (y2 - y1) / (x2 - x1) + y1; + } + } +} diff --git a/CuckooChessEngine/src/chess/Game.java b/CuckooChessEngine/src/chess/Game.java new file mode 100644 index 0000000..b23a57f --- /dev/null +++ b/CuckooChessEngine/src/chess/Game.java @@ -0,0 +1,572 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * + * @author petero + */ +public class Game { + protected List moveList = null; + protected List uiInfoList = null; + List drawOfferList = null; + protected int currentMove; + boolean pendingDrawOffer; + GameState drawState; + String drawStateMoveStr; // Move required to claim DRAW_REP or DRAW_50 + GameState resignState; + public Position pos = null; + protected Player whitePlayer; + protected Player blackPlayer; + + public Game(Player whitePlayer, Player blackPlayer) { + this.whitePlayer = whitePlayer; + this.blackPlayer = blackPlayer; + handleCommand("new"); + } + + /** + * Update the game state according to move/command string from a player. + * @param str The move or command to process. + * @return True if str was understood, false otherwise. + */ + public boolean processString(String str) { + if (handleCommand(str)) { + return true; + } + if (getGameState() != GameState.ALIVE) { + return false; + } + + Move m = TextIO.stringToMove(pos, str); + if (m == null) { + return false; + } + + UndoInfo ui = new UndoInfo(); + pos.makeMove(m, ui); + TextIO.fixupEPSquare(pos); + while (currentMove < moveList.size()) { + moveList.remove(currentMove); + uiInfoList.remove(currentMove); + drawOfferList.remove(currentMove); + } + moveList.add(m); + uiInfoList.add(ui); + drawOfferList.add(pendingDrawOffer); + pendingDrawOffer = false; + currentMove++; + return true; + } + + public final String getGameStateString() { + switch (getGameState()) { + case ALIVE: + return ""; + case WHITE_MATE: + return "Game over, white mates!"; + case BLACK_MATE: + return "Game over, black mates!"; + case WHITE_STALEMATE: + case BLACK_STALEMATE: + return "Game over, draw by stalemate!"; + case DRAW_REP: + { + String ret = "Game over, draw by repetition!"; + if ((drawStateMoveStr != null) && (drawStateMoveStr.length() > 0)) { + ret = ret + " [" + drawStateMoveStr + "]"; + } + return ret; + } + case DRAW_50: + { + String ret = "Game over, draw by 50 move rule!"; + if ((drawStateMoveStr != null) && (drawStateMoveStr.length() > 0)) { + ret = ret + " [" + drawStateMoveStr + "]"; + } + return ret; + } + case DRAW_NO_MATE: + return "Game over, draw by impossibility of mate!"; + case DRAW_AGREE: + return "Game over, draw by agreement!"; + case RESIGN_WHITE: + return "Game over, white resigns!"; + case RESIGN_BLACK: + return "Game over, black resigns!"; + default: + throw new RuntimeException(); + } + } + + /** + * Get the last played move, or null if no moves played yet. + */ + public Move getLastMove() { + Move m = null; + if (currentMove > 0) { + m = moveList.get(currentMove - 1); + } + return m; + } + + public enum GameState { + ALIVE, + WHITE_MATE, // White mates + BLACK_MATE, // Black mates + WHITE_STALEMATE, // White is stalemated + BLACK_STALEMATE, // Black is stalemated + DRAW_REP, // Draw by 3-fold repetition + DRAW_50, // Draw by 50 move rule + DRAW_NO_MATE, // Draw by impossibility of check mate + DRAW_AGREE, // Draw by agreement + RESIGN_WHITE, // White resigns + RESIGN_BLACK // Black resigns + } + + /** + * Get the current state of the game. + */ + public GameState getGameState() { + MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + if (moves.size == 0) { + if (MoveGen.inCheck(pos)) { + return pos.whiteMove ? GameState.BLACK_MATE : GameState.WHITE_MATE; + } else { + return pos.whiteMove ? GameState.WHITE_STALEMATE : GameState.BLACK_STALEMATE; + } + } + if (insufficientMaterial()) { + return GameState.DRAW_NO_MATE; + } + if (resignState != GameState.ALIVE) { + return resignState; + } + return drawState; + } + + /** + * Check if a draw offer is available. + * @return True if the current player has the option to accept a draw offer. + */ + public boolean haveDrawOffer() { + if (currentMove > 0) { + return drawOfferList.get(currentMove - 1); + } else { + return false; + } + } + + /** + * Handle a special command. + * @param moveStr The command to handle + * @return True if command handled, false otherwise. + */ + protected boolean handleCommand(String moveStr) { + if (moveStr.equals("new")) { + moveList = new ArrayList(); + uiInfoList = new ArrayList(); + drawOfferList = new ArrayList(); + currentMove = 0; + pendingDrawOffer = false; + drawState = GameState.ALIVE; + resignState = GameState.ALIVE; + try { + pos = TextIO.readFEN(TextIO.startPosFEN); + } catch (ChessParseError ex) { + throw new RuntimeException(); + } + whitePlayer.clearTT(); + blackPlayer.clearTT(); + activateHumanPlayer(); + return true; + } else if (moveStr.equals("undo")) { + if (currentMove > 0) { + pos.unMakeMove(moveList.get(currentMove - 1), uiInfoList.get(currentMove - 1)); + currentMove--; + pendingDrawOffer = false; + drawState = GameState.ALIVE; + resignState = GameState.ALIVE; + return handleCommand("swap"); + } else { + System.out.println("Nothing to undo"); + } + return true; + } else if (moveStr.equals("redo")) { + if (currentMove < moveList.size()) { + pos.makeMove(moveList.get(currentMove), uiInfoList.get(currentMove)); + currentMove++; + pendingDrawOffer = false; + return handleCommand("swap"); + } else { + System.out.println("Nothing to redo"); + } + return true; + } else if (moveStr.equals("swap") || moveStr.equals("go")) { + Player tmp = whitePlayer; + whitePlayer = blackPlayer; + blackPlayer = tmp; + return true; + } else if (moveStr.equals("list")) { + listMoves(); + return true; + } else if (moveStr.startsWith("setpos ")) { + String fen = moveStr.substring(moveStr.indexOf(" ") + 1); + Position newPos = null; + try { + newPos = TextIO.readFEN(fen); + } catch (ChessParseError ex) { + System.out.printf("Invalid FEN: %s (%s)%n", fen, ex.getMessage()); + } + if (newPos != null) { + handleCommand("new"); + pos = newPos; + activateHumanPlayer(); + } + return true; + } else if (moveStr.equals("getpos")) { + String fen = TextIO.toFEN(pos); + System.out.println(fen); + return true; + } else if (moveStr.startsWith("draw ")) { + if (getGameState() == GameState.ALIVE) { + String drawCmd = moveStr.substring(moveStr.indexOf(" ") + 1); + return handleDrawCmd(drawCmd); + } else { + return true; + } + } else if (moveStr.equals("resign")) { + if (getGameState()== GameState.ALIVE) { + resignState = pos.whiteMove ? GameState.RESIGN_WHITE : GameState.RESIGN_BLACK; + return true; + } else { + return true; + } + } else if (moveStr.startsWith("book")) { + String bookCmd = moveStr.substring(moveStr.indexOf(" ") + 1); + return handleBookCmd(bookCmd); + } else if (moveStr.startsWith("time")) { + try { + String timeStr = moveStr.substring(moveStr.indexOf(" ") + 1); + int timeLimit = Integer.parseInt(timeStr); + whitePlayer.timeLimit(timeLimit, timeLimit, false); + blackPlayer.timeLimit(timeLimit, timeLimit, false); + return true; + } + catch (NumberFormatException nfe) { + System.out.printf("Number format exception: %s\n", nfe.getMessage()); + return false; + } + } else if (moveStr.startsWith("perft ")) { + try { + String depthStr = moveStr.substring(moveStr.indexOf(" ") + 1); + int depth = Integer.parseInt(depthStr); + MoveGen moveGen = new MoveGen(); + long t0 = System.currentTimeMillis(); + long nodes = perfT(moveGen, pos, depth); + long t1 = System.currentTimeMillis(); + System.out.printf("perft(%d) = %d, t=%.3fs\n", depth, nodes, (t1 - t0)*1e-3); + } + catch (NumberFormatException nfe) { + System.out.printf("Number format exception: %s\n", nfe.getMessage()); + return false; + } + return true; + } else { + return false; + } + } + + /** Swap players around if needed to make the human player in control of the next move. */ + protected void activateHumanPlayer() { + if (!(pos.whiteMove ? whitePlayer : blackPlayer).isHumanPlayer()) { + Player tmp = whitePlayer; + whitePlayer = blackPlayer; + blackPlayer = tmp; + } + } + + public List getPosHistory() { + List ret = new ArrayList(); + + Position pos = new Position(this.pos); + for (int i = currentMove; i > 0; i--) { + pos.unMakeMove(moveList.get(i - 1), uiInfoList.get(i - 1)); + } + ret.add(TextIO.toFEN(pos)); // Store initial FEN + + StringBuilder moves = new StringBuilder(); + for (int i = 0; i < moveList.size(); i++) { + Move move = moveList.get(i); + String strMove = TextIO.moveToString(pos, move, false); + moves.append(String.format(" %s", strMove)); + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + } + ret.add(moves.toString()); // Store move list string + int numUndo = moveList.size() - currentMove; + ret.add(((Integer)numUndo).toString()); + return ret; + } + + /** + * Print a list of all moves. + */ + private void listMoves() { + String movesStr = getMoveListString(false); + System.out.printf("%s", movesStr); + } + + final public String getMoveListString(boolean compressed) { + StringBuilder ret = new StringBuilder(); + + // Undo all moves in move history. + Position pos = new Position(this.pos); + for (int i = currentMove; i > 0; i--) { + pos.unMakeMove(moveList.get(i - 1), uiInfoList.get(i - 1)); + } + + // Print all moves + String whiteMove = ""; + String blackMove = ""; + for (int i = 0; i < currentMove; i++) { + Move move = moveList.get(i); + String strMove = TextIO.moveToString(pos, move, false); + if (drawOfferList.get(i)) { + strMove += " (d)"; + } + if (pos.whiteMove) { + whiteMove = strMove; + } else { + blackMove = strMove; + if (whiteMove.length() == 0) { + whiteMove = "..."; + } + if (compressed) { + ret.append(String.format("%d. %s %s ", + pos.fullMoveCounter, whiteMove, blackMove)); + } else { + ret.append(String.format("%3d. %-10s %-10s%n", + pos.fullMoveCounter, whiteMove, blackMove)); + } + whiteMove = ""; + blackMove = ""; + } + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + } + if ((whiteMove.length() > 0) || (blackMove.length() > 0)) { + if (whiteMove.length() == 0) { + whiteMove = "..."; + } + if (compressed) { + ret.append(String.format("%d. %s %s ", + pos.fullMoveCounter, whiteMove, blackMove)); + } else { + ret.append(String.format("%3d. %-8s %-8s%n", + pos.fullMoveCounter, whiteMove, blackMove)); + } + } + String gameResult = getPGNResultString(); + if (!gameResult.equals("*")) { + if (compressed) { + ret.append(gameResult); + } else { + ret.append(String.format("%s%n", gameResult)); + } + } + return ret.toString(); + } + + public final String getPGNResultString() { + String gameResult = "*"; + switch (getGameState()) { + case ALIVE: + break; + case WHITE_MATE: + case RESIGN_BLACK: + gameResult = "1-0"; + break; + case BLACK_MATE: + case RESIGN_WHITE: + gameResult = "0-1"; + break; + case WHITE_STALEMATE: + case BLACK_STALEMATE: + case DRAW_REP: + case DRAW_50: + case DRAW_NO_MATE: + case DRAW_AGREE: + gameResult = "1/2-1/2"; + break; + } + return gameResult; + } + + /** Return a list of previous positions in this game, back to the last "zeroing" move. */ + public ArrayList getHistory() { + ArrayList posList = new ArrayList(); + Position pos = new Position(this.pos); + for (int i = currentMove; i > 0; i--) { + if (pos.halfMoveClock == 0) + break; + pos.unMakeMove(moveList.get(i- 1), uiInfoList.get(i- 1)); + posList.add(new Position(pos)); + } + Collections.reverse(posList); + return posList; + } + + private boolean handleDrawCmd(String drawCmd) { + if (drawCmd.startsWith("rep") || drawCmd.startsWith("50")) { + boolean rep = drawCmd.startsWith("rep"); + Move m = null; + String ms = drawCmd.substring(drawCmd.indexOf(" ") + 1); + if (ms.length() > 0) { + m = TextIO.stringToMove(pos, ms); + } + boolean valid; + if (rep) { + valid = false; + List oldPositions = new ArrayList(); + if (m != null) { + UndoInfo ui = new UndoInfo(); + Position tmpPos = new Position(pos); + tmpPos.makeMove(m, ui); + oldPositions.add(tmpPos); + } + oldPositions.add(pos); + Position tmpPos = pos; + for (int i = currentMove - 1; i >= 0; i--) { + tmpPos = new Position(tmpPos); + tmpPos.unMakeMove(moveList.get(i), uiInfoList.get(i)); + oldPositions.add(tmpPos); + } + int repetitions = 0; + Position firstPos = oldPositions.get(0); + for (Position p : oldPositions) { + if (p.drawRuleEquals(firstPos)) + repetitions++; + } + if (repetitions >= 3) { + valid = true; + } + } else { + Position tmpPos = new Position(pos); + if (m != null) { + UndoInfo ui = new UndoInfo(); + tmpPos.makeMove(m, ui); + } + valid = tmpPos.halfMoveClock >= 100; + } + if (valid) { + drawState = rep ? GameState.DRAW_REP : GameState.DRAW_50; + drawStateMoveStr = null; + if (m != null) { + drawStateMoveStr = TextIO.moveToString(pos, m, false); + } + } else { + pendingDrawOffer = true; + if (m != null) { + processString(ms); + } + } + return true; + } else if (drawCmd.startsWith("offer ")) { + pendingDrawOffer = true; + String ms = drawCmd.substring(drawCmd.indexOf(" ") + 1); + if (TextIO.stringToMove(pos, ms) != null) { + processString(ms); + } + return true; + } else if (drawCmd.equals("accept")) { + if (haveDrawOffer()) { + drawState = GameState.DRAW_AGREE; + } + return true; + } else { + return false; + } + } + + private boolean handleBookCmd(String bookCmd) { + if (bookCmd.equals("off")) { + whitePlayer.useBook(false); + blackPlayer.useBook(false); + return true; + } else if (bookCmd.equals("on")) { + whitePlayer.useBook(true); + whitePlayer.useBook(true); + return true; + } + return false; + } + + private boolean insufficientMaterial() { + if (pos.pieceTypeBB[Piece.WQUEEN] != 0) return false; + if (pos.pieceTypeBB[Piece.WROOK] != 0) return false; + if (pos.pieceTypeBB[Piece.WPAWN] != 0) return false; + if (pos.pieceTypeBB[Piece.BQUEEN] != 0) return false; + if (pos.pieceTypeBB[Piece.BROOK] != 0) return false; + if (pos.pieceTypeBB[Piece.BPAWN] != 0) return false; + int wb = Long.bitCount(pos.pieceTypeBB[Piece.WBISHOP]); + int wn = Long.bitCount(pos.pieceTypeBB[Piece.WKNIGHT]); + int bb = Long.bitCount(pos.pieceTypeBB[Piece.BBISHOP]); + int bn = Long.bitCount(pos.pieceTypeBB[Piece.BKNIGHT]); + if (wb + wn + bb + bn <= 1) { + return true; // King + bishop/knight vs king is draw + } + if (wn + bn == 0) { + // Only bishops. If they are all on the same color, the position is a draw. + long bMask = pos.pieceTypeBB[Piece.WBISHOP] | pos.pieceTypeBB[Piece.BBISHOP]; + if (((bMask & BitBoard.maskDarkSq) == 0) || + ((bMask & BitBoard.maskLightSq) == 0)) + return true; + } + + return false; + } + + final static long perfT(MoveGen moveGen, Position pos, int depth) { + if (depth == 0) + return 1; + long nodes = 0; + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + if (depth == 1) { + int ret = moves.size; + moveGen.returnMoveList(moves); + return ret; + } + UndoInfo ui = new UndoInfo(); + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + pos.makeMove(m, ui); + nodes += perfT(moveGen, pos, depth - 1); + pos.unMakeMove(m, ui); + } + moveGen.returnMoveList(moves); + return nodes; + } +} diff --git a/CuckooChessEngine/src/chess/History.java b/CuckooChessEngine/src/chess/History.java new file mode 100644 index 0000000..4935264 --- /dev/null +++ b/CuckooChessEngine/src/chess/History.java @@ -0,0 +1,80 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +/** + * Implements the relative history heuristic. + * @author petero + */ +public final class History { + private final int countSuccess[][]; + private final int countFail[][]; + private final int score[][]; + + public History() { + countSuccess = new int[Piece.nPieceTypes][64]; + countFail = new int[Piece.nPieceTypes][64]; + score = new int[Piece.nPieceTypes][64]; + for (int p = 0; p < Piece.nPieceTypes; p++) { + for (int sq = 0; sq < 64; sq++) { + countSuccess[p][sq] = 0; + countFail[p][sq] = 0; + score[p][sq] = -1; + } + } + } + + /** Record move as a success. */ + public final void addSuccess(Position pos, Move m, int depth) { + int p = pos.getPiece(m.from); + int cnt = depth; + int val = countSuccess[p][m.to] + cnt; + if (val > 1000) { + val /= 2; + countFail[p][m.to] /= 2; + } + countSuccess[p][m.to] = val; + score[p][m.to] = -1; + } + + /** Record move as a failure. */ + public final void addFail(Position pos, Move m, int depth) { + int p = pos.getPiece(m.from); + int cnt = depth; + countFail[p][m.to] += cnt; + score[p][m.to] = -1; + } + + /** Get a score between 0 and 49, depending of the success/fail ratio of the move. */ + public final int getHistScore(Position pos, Move m) { + int p = pos.getPiece(m.from); + int ret = score[p][m.to]; + if (ret >= 0) + return ret; + int succ = countSuccess[p][m.to]; + int fail = countFail[p][m.to]; + if (succ + fail > 0) { + ret = succ * 49 / (succ + fail); + } else { + ret = 0; + } + score[p][m.to] = ret; + return ret; + } +} diff --git a/CuckooChessEngine/src/chess/HumanPlayer.java b/CuckooChessEngine/src/chess/HumanPlayer.java new file mode 100644 index 0000000..e0e29f9 --- /dev/null +++ b/CuckooChessEngine/src/chess/HumanPlayer.java @@ -0,0 +1,73 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; + +/** + * A player that reads input from the keyboard. + * @author petero + */ +public class HumanPlayer implements Player { + private String lastCmd = ""; + private BufferedReader in; + + public HumanPlayer() { + in = new BufferedReader(new InputStreamReader(System.in)); + } + + @Override + public String getCommand(Position pos, boolean drawOffer, List history) { + try { + String color = pos.whiteMove ? "white" : "black"; + System.out.print(String.format("Enter move (%s):", color)); + String moveStr = in.readLine(); + if (moveStr == null) + return "quit"; + if (moveStr.length() == 0) { + return lastCmd; + } else { + lastCmd = moveStr; + } + return moveStr; + } catch (IOException ex) { + return "quit"; + } + } + + @Override + public boolean isHumanPlayer() { + return true; + } + + @Override + public void useBook(boolean bookOn) { + } + + @Override + public void timeLimit(int minTimeLimit, int maxTimeLimit, boolean randomMode) { + } + + @Override + public void clearTT() { + } +} diff --git a/CuckooChessEngine/src/chess/KillerTable.java b/CuckooChessEngine/src/chess/KillerTable.java new file mode 100644 index 0000000..372639b --- /dev/null +++ b/CuckooChessEngine/src/chess/KillerTable.java @@ -0,0 +1,83 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +/** + * Implement a table of killer moves for the killer heuristic. + * @author petero + */ +public class KillerTable { + /** There is one KTEntry for each ply in the search tree. */ + static final class KTEntry { + public KTEntry() { + move0 = move1 = 0; + } + int move0; + int move1; + } + KTEntry[] ktList; + + /** Create an empty killer table. */ + public KillerTable() { + ktList = new KTEntry[200]; + for (int i = 0; i < ktList.length; i++) + ktList[i] = new KTEntry(); + } + + /** Add a killer move to the table. Moves are replaced on an LRU basis. */ + final public void addKiller(int ply, Move m) { + if (ply >= ktList.length) + return; + int move = (short)(m.from + (m.to << 6) + (m.promoteTo << 12)); + KTEntry ent = ktList[ply]; + if (move != ent.move0) { + ent.move1 = ent.move0; + ent.move0 = move; + } + } + + /** + * Get a score for move m based on hits in the killer table. + * The score is 4 for primary hit at ply. + * The score is 3 for secondary hit at ply. + * The score is 2 for primary hit at ply - 2. + * The score is 1 for secondary hit at ply - 2. + * The score is 0 otherwise. + */ + final public int getKillerScore(int ply, Move m) { + int move = (short)(m.from + (m.to << 6) + (m.promoteTo << 12)); + if (ply < ktList.length) { + KTEntry ent = ktList[ply]; + if (move == ent.move0) { + return 4; + } else if (move == ent.move1) { + return 3; + } + } + if ((ply - 2 >= 0) && (ply - 2 < ktList.length)) { + KTEntry ent = ktList[ply - 2]; + if (move == ent.move0) { + return 2; + } else if (move == ent.move1) { + return 1; + } + } + return 0; + } +} diff --git a/CuckooChessEngine/src/chess/Move.java b/CuckooChessEngine/src/chess/Move.java new file mode 100644 index 0000000..7f7cc51 --- /dev/null +++ b/CuckooChessEngine/src/chess/Move.java @@ -0,0 +1,97 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.util.Comparator; + +/** + * + * @author petero + */ +public class Move { + /** From square, 0-63. */ + public int from; + + /** To square, 0-63. */ + public int to; + + /** Promotion piece. */ + public int promoteTo; + + public int score; + + /** Create a move object. */ + public Move(int from, int to, int promoteTo) { + this.from = from; + this.to = to; + this.promoteTo = promoteTo; + this.score = 0; + } + + public Move(int from, int to, int promoteTo, int score) { + this.from = from; + this.to = to; + this.promoteTo = promoteTo; + this.score = score; + } + + static public class SortByScore implements Comparator { + public int compare(Move sm1, Move sm2) { + return sm2.score - sm1.score; + } + } + + public Move(Move m) { + this.from = m.from; + this.to = m.to; + this.promoteTo = m.promoteTo; + this.score = m.score; + } + + public void copyFrom(Move m) { + from = m.from; + to = m.to; + promoteTo = m.promoteTo; +// score = m.score; + } + + /** Note that score is not included in the comparison. */ + @Override + public boolean equals(Object o) { + if ((o == null) || (o.getClass() != this.getClass())) + return false; + Move other = (Move)o; + if (from != other.from) + return false; + if (to != other.to) + return false; + if (promoteTo != other.promoteTo) + return false; + return true; + } + @Override + public int hashCode() { + return (from * 64 + to) * 16 + promoteTo; + } + + /** Useful for debugging. */ + public final String toString() { + return TextIO.moveToUCIString(this); + } +} diff --git a/CuckooChessEngine/src/chess/MoveGen.java b/CuckooChessEngine/src/chess/MoveGen.java new file mode 100644 index 0000000..ee112b4 --- /dev/null +++ b/CuckooChessEngine/src/chess/MoveGen.java @@ -0,0 +1,1064 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +/** + * + * @author petero + */ +public final class MoveGen { + static final MoveGen instance; + static { + instance = new MoveGen(); + } + + public final static class MoveList { + public final Move[] m; + public int size; + MoveList() { + m = new Move[MAX_MOVES]; + this.size = 0; + } + } + + /** + * Generate and return a list of pseudo-legal moves. + * Pseudo-legal means that the moves doesn't necessarily defend from check threats. + */ + public final MoveList pseudoLegalMoves(Position pos) { + MoveList moveList = getMoveListObj(); + final long occupied = pos.whiteBB | pos.blackBB; + if (pos.whiteMove) { + // Queen moves + long squares = pos.pieceTypeBB[Piece.WQUEEN]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & ~pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Rook moves + squares = pos.pieceTypeBB[Piece.WROOK]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.rookAttacks(sq, occupied) & ~pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Bishop moves + squares = pos.pieceTypeBB[Piece.WBISHOP]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // King moves + { + int sq = pos.getKingSq(true); + long m = BitBoard.kingAttacks[sq] & ~pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + final int k0 = 4; + if (sq == k0) { + final long OO_SQ = 0x60L; + final long OOO_SQ = 0xEL; + if (((pos.getCastleMask() & (1 << Position.H1_CASTLE)) != 0) && + ((OO_SQ & (pos.whiteBB | pos.blackBB)) == 0) && + (pos.getPiece(k0 + 3) == Piece.WROOK) && + !sqAttacked(pos, k0) && + !sqAttacked(pos, k0 + 1)) { + setMove(moveList, k0, k0 + 2, Piece.EMPTY); + } + if (((pos.getCastleMask() & (1 << Position.A1_CASTLE)) != 0) && + ((OOO_SQ & (pos.whiteBB | pos.blackBB)) == 0) && + (pos.getPiece(k0 - 4) == Piece.WROOK) && + !sqAttacked(pos, k0) && + !sqAttacked(pos, k0 - 1)) { + setMove(moveList, k0, k0 - 2, Piece.EMPTY); + } + } + } + + // Knight moves + long knights = pos.pieceTypeBB[Piece.WKNIGHT]; + while (knights != 0) { + int sq = BitBoard.numberOfTrailingZeros(knights); + long m = BitBoard.knightAttacks[sq] & ~pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + knights &= knights-1; + } + + // Pawn moves + long pawns = pos.pieceTypeBB[Piece.WPAWN]; + long m = (pawns << 8) & ~occupied; + if (addPawnMovesByMask(moveList, pos, m, -8, true)) return moveList; + m = ((m & BitBoard.maskRow3) << 8) & ~occupied; + addPawnDoubleMovesByMask(moveList, pos, m, -16); + + int epSquare = pos.getEpSquare(); + long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L; + m = (pawns << 7) & BitBoard.maskAToGFiles & (pos.blackBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, -7, true)) return moveList; + + m = (pawns << 9) & BitBoard.maskBToHFiles & (pos.blackBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, -9, true)) return moveList; + } else { + // Queen moves + long squares = pos.pieceTypeBB[Piece.BQUEEN]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & ~pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Rook moves + squares = pos.pieceTypeBB[Piece.BROOK]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.rookAttacks(sq, occupied) & ~pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Bishop moves + squares = pos.pieceTypeBB[Piece.BBISHOP]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // King moves + { + int sq = pos.getKingSq(false); + long m = BitBoard.kingAttacks[sq] & ~pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + final int k0 = 60; + if (sq == k0) { + final long OO_SQ = 0x6000000000000000L; + final long OOO_SQ = 0xE00000000000000L; + if (((pos.getCastleMask() & (1 << Position.H8_CASTLE)) != 0) && + ((OO_SQ & (pos.whiteBB | pos.blackBB)) == 0) && + (pos.getPiece(k0 + 3) == Piece.BROOK) && + !sqAttacked(pos, k0) && + !sqAttacked(pos, k0 + 1)) { + setMove(moveList, k0, k0 + 2, Piece.EMPTY); + } + if (((pos.getCastleMask() & (1 << Position.A8_CASTLE)) != 0) && + ((OOO_SQ & (pos.whiteBB | pos.blackBB)) == 0) && + (pos.getPiece(k0 - 4) == Piece.BROOK) && + !sqAttacked(pos, k0) && + !sqAttacked(pos, k0 - 1)) { + setMove(moveList, k0, k0 - 2, Piece.EMPTY); + } + } + } + + // Knight moves + long knights = pos.pieceTypeBB[Piece.BKNIGHT]; + while (knights != 0) { + int sq = BitBoard.numberOfTrailingZeros(knights); + long m = BitBoard.knightAttacks[sq] & ~pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + knights &= knights-1; + } + + // Pawn moves + long pawns = pos.pieceTypeBB[Piece.BPAWN]; + long m = (pawns >>> 8) & ~occupied; + if (addPawnMovesByMask(moveList, pos, m, 8, true)) return moveList; + m = ((m & BitBoard.maskRow6) >>> 8) & ~occupied; + addPawnDoubleMovesByMask(moveList, pos, m, 16); + + int epSquare = pos.getEpSquare(); + long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L; + m = (pawns >>> 9) & BitBoard.maskAToGFiles & (pos.whiteBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, 9, true)) return moveList; + + m = (pawns >>> 7) & BitBoard.maskBToHFiles & (pos.whiteBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, 7, true)) return moveList; + } + return moveList; + } + + /** + * Generate and return a list of pseudo-legal check evasion moves. + * Pseudo-legal means that the moves doesn't necessarily defend from check threats. + */ + public final MoveList checkEvasions(Position pos) { + MoveList moveList = getMoveListObj(); + final long occupied = pos.whiteBB | pos.blackBB; + if (pos.whiteMove) { + long kingThreats = pos.pieceTypeBB[Piece.BKNIGHT] & BitBoard.knightAttacks[pos.wKingSq]; + long rookPieces = pos.pieceTypeBB[Piece.BROOK] | pos.pieceTypeBB[Piece.BQUEEN]; + if (rookPieces != 0) + kingThreats |= rookPieces & BitBoard.rookAttacks(pos.wKingSq, occupied); + long bishPieces = pos.pieceTypeBB[Piece.BBISHOP] | pos.pieceTypeBB[Piece.BQUEEN]; + if (bishPieces != 0) + kingThreats |= bishPieces & BitBoard.bishopAttacks(pos.wKingSq, occupied); + kingThreats |= pos.pieceTypeBB[Piece.BPAWN] & BitBoard.wPawnAttacks[pos.wKingSq]; + long validTargets = 0; + if ((kingThreats != 0) && ((kingThreats & (kingThreats-1)) == 0)) { // Exactly one attacking piece + int threatSq = BitBoard.numberOfTrailingZeros(kingThreats); + validTargets = kingThreats | BitBoard.squaresBetween[pos.wKingSq][threatSq]; + } + validTargets |= pos.pieceTypeBB[Piece.BKING]; + // Queen moves + long squares = pos.pieceTypeBB[Piece.WQUEEN]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & + ~pos.whiteBB & validTargets; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Rook moves + squares = pos.pieceTypeBB[Piece.WROOK]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.rookAttacks(sq, occupied) & ~pos.whiteBB & validTargets; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Bishop moves + squares = pos.pieceTypeBB[Piece.WBISHOP]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.whiteBB & validTargets; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // King moves + { + int sq = pos.getKingSq(true); + long m = BitBoard.kingAttacks[sq] & ~pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + } + + // Knight moves + long knights = pos.pieceTypeBB[Piece.WKNIGHT]; + while (knights != 0) { + int sq = BitBoard.numberOfTrailingZeros(knights); + long m = BitBoard.knightAttacks[sq] & ~pos.whiteBB & validTargets; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + knights &= knights-1; + } + + // Pawn moves + long pawns = pos.pieceTypeBB[Piece.WPAWN]; + long m = (pawns << 8) & ~occupied; + if (addPawnMovesByMask(moveList, pos, m & validTargets, -8, true)) return moveList; + m = ((m & BitBoard.maskRow3) << 8) & ~occupied; + addPawnDoubleMovesByMask(moveList, pos, m & validTargets, -16); + + int epSquare = pos.getEpSquare(); + long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L; + m = (pawns << 7) & BitBoard.maskAToGFiles & ((pos.blackBB & validTargets) | epMask); + if (addPawnMovesByMask(moveList, pos, m, -7, true)) return moveList; + + m = (pawns << 9) & BitBoard.maskBToHFiles & ((pos.blackBB & validTargets) | epMask); + if (addPawnMovesByMask(moveList, pos, m, -9, true)) return moveList; + } else { + long kingThreats = pos.pieceTypeBB[Piece.WKNIGHT] & BitBoard.knightAttacks[pos.bKingSq]; + long rookPieces = pos.pieceTypeBB[Piece.WROOK] | pos.pieceTypeBB[Piece.WQUEEN]; + if (rookPieces != 0) + kingThreats |= rookPieces & BitBoard.rookAttacks(pos.bKingSq, occupied); + long bishPieces = pos.pieceTypeBB[Piece.WBISHOP] | pos.pieceTypeBB[Piece.WQUEEN]; + if (bishPieces != 0) + kingThreats |= bishPieces & BitBoard.bishopAttacks(pos.bKingSq, occupied); + kingThreats |= pos.pieceTypeBB[Piece.WPAWN] & BitBoard.bPawnAttacks[pos.bKingSq]; + long validTargets = 0; + if ((kingThreats != 0) && ((kingThreats & (kingThreats-1)) == 0)) { // Exactly one attacking piece + int threatSq = BitBoard.numberOfTrailingZeros(kingThreats); + validTargets = kingThreats | BitBoard.squaresBetween[pos.bKingSq][threatSq]; + } + validTargets |= pos.pieceTypeBB[Piece.WKING]; + // Queen moves + long squares = pos.pieceTypeBB[Piece.BQUEEN]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & + ~pos.blackBB & validTargets; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Rook moves + squares = pos.pieceTypeBB[Piece.BROOK]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.rookAttacks(sq, occupied) & ~pos.blackBB & validTargets; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Bishop moves + squares = pos.pieceTypeBB[Piece.BBISHOP]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.bishopAttacks(sq, occupied) & ~pos.blackBB & validTargets; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // King moves + { + int sq = pos.getKingSq(false); + long m = BitBoard.kingAttacks[sq] & ~pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + } + + // Knight moves + long knights = pos.pieceTypeBB[Piece.BKNIGHT]; + while (knights != 0) { + int sq = BitBoard.numberOfTrailingZeros(knights); + long m = BitBoard.knightAttacks[sq] & ~pos.blackBB & validTargets; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + knights &= knights-1; + } + + // Pawn moves + long pawns = pos.pieceTypeBB[Piece.BPAWN]; + long m = (pawns >>> 8) & ~occupied; + if (addPawnMovesByMask(moveList, pos, m & validTargets, 8, true)) return moveList; + m = ((m & BitBoard.maskRow6) >>> 8) & ~occupied; + addPawnDoubleMovesByMask(moveList, pos, m & validTargets, 16); + + int epSquare = pos.getEpSquare(); + long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L; + m = (pawns >>> 9) & BitBoard.maskAToGFiles & ((pos.whiteBB & validTargets) | epMask); + if (addPawnMovesByMask(moveList, pos, m, 9, true)) return moveList; + + m = (pawns >>> 7) & BitBoard.maskBToHFiles & ((pos.whiteBB & validTargets) | epMask); + if (addPawnMovesByMask(moveList, pos, m, 7, true)) return moveList; + } + + /* Extra debug checks + { + ArrayList allMoves = pseudoLegalMoves(pos); + allMoves = MoveGen.removeIllegal(pos, allMoves); + HashSet evMoves = new HashSet(); + for (Move m : moveList) + evMoves.add(TextIO.moveToUCIString(m)); + for (Move m : allMoves) + if (!evMoves.contains(TextIO.moveToUCIString(m))) + throw new RuntimeException(); + } + */ + + return moveList; + } + + /** Generate captures, checks, and possibly some other moves that are too hard to filter out. */ + public final MoveList pseudoLegalCapturesAndChecks(Position pos) { + MoveList moveList = getMoveListObj(); + long occupied = pos.whiteBB | pos.blackBB; + if (pos.whiteMove) { + int bKingSq = pos.getKingSq(false); + long discovered = 0; // Squares that could generate discovered checks + long kRookAtk = BitBoard.rookAttacks(bKingSq, occupied); + if ((BitBoard.rookAttacks(bKingSq, occupied & ~kRookAtk) & + (pos.pieceTypeBB[Piece.WQUEEN] | pos.pieceTypeBB[Piece.WROOK])) != 0) + discovered |= kRookAtk; + long kBishAtk = BitBoard.bishopAttacks(bKingSq, occupied); + if ((BitBoard.bishopAttacks(bKingSq, occupied & ~kBishAtk) & + (pos.pieceTypeBB[Piece.WQUEEN] | pos.pieceTypeBB[Piece.WBISHOP])) != 0) + discovered |= kBishAtk; + + // Queen moves + long squares = pos.pieceTypeBB[Piece.WQUEEN]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)); + if ((discovered & (1L<= 0) ? (1L << epSquare) : 0L; + long m = (pawns << 7) & BitBoard.maskAToGFiles & (pos.blackBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, -7, false)) return moveList; + m = (pawns << 9) & BitBoard.maskBToHFiles & (pos.blackBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, -9, false)) return moveList; + + // Discovered checks and promotions + long pawnAll = discovered | BitBoard.maskRow7; + m = ((pawns & pawnAll) << 8) & ~(pos.whiteBB | pos.blackBB); + if (addPawnMovesByMask(moveList, pos, m, -8, false)) return moveList; + m = ((m & BitBoard.maskRow3) << 8) & ~(pos.whiteBB | pos.blackBB); + addPawnDoubleMovesByMask(moveList, pos, m, -16); + + // Normal checks + m = ((pawns & ~pawnAll) << 8) & ~(pos.whiteBB | pos.blackBB); + if (addPawnMovesByMask(moveList, pos, m & BitBoard.bPawnAttacks[bKingSq], -8, false)) return moveList; + m = ((m & BitBoard.maskRow3) << 8) & ~(pos.whiteBB | pos.blackBB); + addPawnDoubleMovesByMask(moveList, pos, m & BitBoard.bPawnAttacks[bKingSq], -16); + } else { + int wKingSq = pos.getKingSq(true); + long discovered = 0; // Squares that could generate discovered checks + long kRookAtk = BitBoard.rookAttacks(wKingSq, occupied); + if ((BitBoard.rookAttacks(wKingSq, occupied & ~kRookAtk) & + (pos.pieceTypeBB[Piece.BQUEEN] | pos.pieceTypeBB[Piece.BROOK])) != 0) + discovered |= kRookAtk; + long kBishAtk = BitBoard.bishopAttacks(wKingSq, occupied); + if ((BitBoard.bishopAttacks(wKingSq, occupied & ~kBishAtk) & + (pos.pieceTypeBB[Piece.BQUEEN] | pos.pieceTypeBB[Piece.BBISHOP])) != 0) + discovered |= kBishAtk; + + // Queen moves + long squares = pos.pieceTypeBB[Piece.BQUEEN]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)); + if ((discovered & (1L<= 0) ? (1L << epSquare) : 0L; + long m = (pawns >>> 9) & BitBoard.maskAToGFiles & (pos.whiteBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, 9, false)) return moveList; + m = (pawns >>> 7) & BitBoard.maskBToHFiles & (pos.whiteBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, 7, false)) return moveList; + + // Discovered checks and promotions + long pawnAll = discovered | BitBoard.maskRow2; + m = ((pawns & pawnAll) >>> 8) & ~(pos.whiteBB | pos.blackBB); + if (addPawnMovesByMask(moveList, pos, m, 8, false)) return moveList; + m = ((m & BitBoard.maskRow6) >>> 8) & ~(pos.whiteBB | pos.blackBB); + addPawnDoubleMovesByMask(moveList, pos, m, 16); + + // Normal checks + m = ((pawns & ~pawnAll) >>> 8) & ~(pos.whiteBB | pos.blackBB); + if (addPawnMovesByMask(moveList, pos, m & BitBoard.wPawnAttacks[wKingSq], 8, false)) return moveList; + m = ((m & BitBoard.maskRow6) >>> 8) & ~(pos.whiteBB | pos.blackBB); + addPawnDoubleMovesByMask(moveList, pos, m & BitBoard.wPawnAttacks[wKingSq], 16); + } + + return moveList; + } + + public final MoveList pseudoLegalCaptures(Position pos) { + MoveList moveList = getMoveListObj(); + long occupied = pos.whiteBB | pos.blackBB; + if (pos.whiteMove) { + // Queen moves + long squares = pos.pieceTypeBB[Piece.WQUEEN]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Rook moves + squares = pos.pieceTypeBB[Piece.WROOK]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.rookAttacks(sq, occupied) & pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Bishop moves + squares = pos.pieceTypeBB[Piece.WBISHOP]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.bishopAttacks(sq, occupied) & pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Knight moves + long knights = pos.pieceTypeBB[Piece.WKNIGHT]; + while (knights != 0) { + int sq = BitBoard.numberOfTrailingZeros(knights); + long m = BitBoard.knightAttacks[sq] & pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + knights &= knights-1; + } + + // King moves + int sq = pos.getKingSq(true); + long m = BitBoard.kingAttacks[sq] & pos.blackBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + + // Pawn moves + long pawns = pos.pieceTypeBB[Piece.WPAWN]; + m = (pawns << 8) & ~(pos.whiteBB | pos.blackBB); + m &= BitBoard.maskRow8; + if (addPawnMovesByMask(moveList, pos, m, -8, false)) return moveList; + + int epSquare = pos.getEpSquare(); + long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L; + m = (pawns << 7) & BitBoard.maskAToGFiles & (pos.blackBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, -7, false)) return moveList; + m = (pawns << 9) & BitBoard.maskBToHFiles & (pos.blackBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, -9, false)) return moveList; + } else { + // Queen moves + long squares = pos.pieceTypeBB[Piece.BQUEEN]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = (BitBoard.rookAttacks(sq, occupied) | BitBoard.bishopAttacks(sq, occupied)) & pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Rook moves + squares = pos.pieceTypeBB[Piece.BROOK]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.rookAttacks(sq, occupied) & pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Bishop moves + squares = pos.pieceTypeBB[Piece.BBISHOP]; + while (squares != 0) { + int sq = BitBoard.numberOfTrailingZeros(squares); + long m = BitBoard.bishopAttacks(sq, occupied) & pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + squares &= squares-1; + } + + // Knight moves + long knights = pos.pieceTypeBB[Piece.BKNIGHT]; + while (knights != 0) { + int sq = BitBoard.numberOfTrailingZeros(knights); + long m = BitBoard.knightAttacks[sq] & pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + knights &= knights-1; + } + + // King moves + int sq = pos.getKingSq(false); + long m = BitBoard.kingAttacks[sq] & pos.whiteBB; + if (addMovesByMask(moveList, pos, sq, m)) return moveList; + + // Pawn moves + long pawns = pos.pieceTypeBB[Piece.BPAWN]; + m = (pawns >>> 8) & ~(pos.whiteBB | pos.blackBB); + m &= BitBoard.maskRow1; + if (addPawnMovesByMask(moveList, pos, m, 8, false)) return moveList; + + int epSquare = pos.getEpSquare(); + long epMask = (epSquare >= 0) ? (1L << epSquare) : 0L; + m = (pawns >>> 9) & BitBoard.maskAToGFiles & (pos.whiteBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, 9, false)) return moveList; + m = (pawns >>> 7) & BitBoard.maskBToHFiles & (pos.whiteBB | epMask); + if (addPawnMovesByMask(moveList, pos, m, 7, false)) return moveList; + } + return moveList; + } + + /** + * Return true if the side to move is in check. + */ + public static final boolean inCheck(Position pos) { + int kingSq = pos.getKingSq(pos.whiteMove); + return sqAttacked(pos, kingSq); + } + + /** + * Return the next piece in a given direction, starting from sq. + */ + private static final int nextPiece(Position pos, int sq, int delta) { + while (true) { + sq += delta; + int p = pos.getPiece(sq); + if (p != Piece.EMPTY) + return p; + } + } + + /** Like nextPiece(), but handles board edges. */ + private static final int nextPieceSafe(Position pos, int sq, int delta) { + int dx = 0, dy = 0; + switch (delta) { + case 1: dx=1; dy=0; break; + case 9: dx=1; dy=1; break; + case 8: dx=0; dy=1; break; + case 7: dx=-1; dy=1; break; + case -1: dx=-1; dy=0; break; + case -9: dx=-1; dy=-1; break; + case -8: dx=0; dy=-1; break; + case -7: dx=1; dy=-1; break; + } + int x = Position.getX(sq); + int y = Position.getY(sq); + while (true) { + x += dx; + y += dy; + if ((x < 0) || (x > 7) || (y < 0) || (y > 7)) { + return Piece.EMPTY; + } + int p = pos.getPiece(Position.getSquare(x, y)); + if (p != Piece.EMPTY) + return p; + } + } + + /** + * Return true if making a move delivers check to the opponent + */ + public static final boolean givesCheck(Position pos, Move m) { + boolean wtm = pos.whiteMove; + int oKingSq = pos.getKingSq(!wtm); + int oKing = wtm ? Piece.BKING : Piece.WKING; + int p = Piece.makeWhite(m.promoteTo == Piece.EMPTY ? pos.getPiece(m.from) : m.promoteTo); + int d1 = BitBoard.getDirection(m.to, oKingSq); + switch (d1) { + case 8: case -8: case 1: case -1: // Rook direction + if ((p == Piece.WQUEEN) || (p == Piece.WROOK)) + if ((d1 != 0) && (MoveGen.nextPiece(pos, m.to, d1) == oKing)) + return true; + break; + case 9: case 7: case -9: case -7: // Bishop direction + if ((p == Piece.WQUEEN) || (p == Piece.WBISHOP)) { + if ((d1 != 0) && (MoveGen.nextPiece(pos, m.to, d1) == oKing)) + return true; + } else if (p == Piece.WPAWN) { + if (((d1 > 0) == wtm) && (pos.getPiece(m.to + d1) == oKing)) + return true; + } + break; + default: + if (d1 != 0) { // Knight direction + if (p == Piece.WKNIGHT) + return true; + } + } + int d2 = BitBoard.getDirection(m.from, oKingSq); + if ((d2 != 0) && (d2 != d1) && (MoveGen.nextPiece(pos, m.from, d2) == oKing)) { + int p2 = MoveGen.nextPieceSafe(pos, m.from, -d2); + switch (d2) { + case 8: case -8: case 1: case -1: // Rook direction + if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) || + (p2 == (wtm ? Piece.WROOK : Piece.BROOK))) + return true; + break; + case 9: case 7: case -9: case -7: // Bishop direction + if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) || + (p2 == (wtm ? Piece.WBISHOP : Piece.BBISHOP))) + return true; + break; + } + } + if ((m.promoteTo != Piece.EMPTY) && (d1 != 0) && (d1 == d2)) { + switch (d1) { + case 8: case -8: case 1: case -1: // Rook direction + if ((p == Piece.WQUEEN) || (p == Piece.WROOK)) + if ((d1 != 0) && (MoveGen.nextPiece(pos, m.from, d1) == oKing)) + return true; + break; + case 9: case 7: case -9: case -7: // Bishop direction + if ((p == Piece.WQUEEN) || (p == Piece.WBISHOP)) { + if ((d1 != 0) && (MoveGen.nextPiece(pos, m.from, d1) == oKing)) + return true; + } + break; + } + } + if (p == Piece.WKING) { + if (m.to - m.from == 2) { // O-O + if (MoveGen.nextPieceSafe(pos, m.from, -1) == oKing) + return true; + if (MoveGen.nextPieceSafe(pos, m.from + 1, wtm ? 8 : -8) == oKing) + return true; + } else if (m.to - m.from == -2) { // O-O-O + if (MoveGen.nextPieceSafe(pos, m.from, 1) == oKing) + return true; + if (MoveGen.nextPieceSafe(pos, m.from - 1, wtm ? 8 : -8) == oKing) + return true; + } + } else if (p == Piece.WPAWN) { + if (pos.getPiece(m.to) == Piece.EMPTY) { + int dx = Position.getX(m.to) - Position.getX(m.from); + if (dx != 0) { // en passant + int epSq = m.from + dx; + int d3 = BitBoard.getDirection(epSq, oKingSq); + switch (d3) { + case 9: case 7: case -9: case -7: + if (MoveGen.nextPiece(pos, epSq, d3) == oKing) { + int p2 = MoveGen.nextPieceSafe(pos, epSq, -d3); + if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) || + (p2 == (wtm ? Piece.WBISHOP : Piece.BBISHOP))) + return true; + } + break; + case 1: + if (MoveGen.nextPiece(pos, Math.max(epSq, m.from), d3) == oKing) { + int p2 = MoveGen.nextPieceSafe(pos, Math.min(epSq, m.from), -d3); + if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) || + (p2 == (wtm ? Piece.WROOK : Piece.BROOK))) + return true; + } + break; + case -1: + if (MoveGen.nextPiece(pos, Math.min(epSq, m.from), d3) == oKing) { + int p2 = MoveGen.nextPieceSafe(pos, Math.max(epSq, m.from), -d3); + if ((p2 == (wtm ? Piece.WQUEEN : Piece.BQUEEN)) || + (p2 == (wtm ? Piece.WROOK : Piece.BROOK))) + return true; + } + break; + } + } + } + } + return false; + } + + /** + * Return true if the side to move can take the opponents king. + */ + public static final boolean canTakeKing(Position pos) { + pos.setWhiteMove(!pos.whiteMove); + boolean ret = inCheck(pos); + pos.setWhiteMove(!pos.whiteMove); + return ret; + } + + /** + * Return true if a square is attacked by the opposite side. + */ + public static final boolean sqAttacked(Position pos, int sq) { + if (pos.whiteMove) { + if ((BitBoard.knightAttacks[sq] & pos.pieceTypeBB[Piece.BKNIGHT]) != 0) + return true; + if ((BitBoard.kingAttacks[sq] & pos.pieceTypeBB[Piece.BKING]) != 0) + return true; + if ((BitBoard.wPawnAttacks[sq] & pos.pieceTypeBB[Piece.BPAWN]) != 0) + return true; + long occupied = pos.whiteBB | pos.blackBB; + long bbQueen = pos.pieceTypeBB[Piece.BQUEEN]; + if ((BitBoard.bishopAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.BBISHOP] | bbQueen)) != 0) + return true; + if ((BitBoard.rookAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.BROOK] | bbQueen)) != 0) + return true; + } else { + if ((BitBoard.knightAttacks[sq] & pos.pieceTypeBB[Piece.WKNIGHT]) != 0) + return true; + if ((BitBoard.kingAttacks[sq] & pos.pieceTypeBB[Piece.WKING]) != 0) + return true; + if ((BitBoard.bPawnAttacks[sq] & pos.pieceTypeBB[Piece.WPAWN]) != 0) + return true; + long occupied = pos.whiteBB | pos.blackBB; + long bbQueen = pos.pieceTypeBB[Piece.WQUEEN]; + if ((BitBoard.bishopAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.WBISHOP] | bbQueen)) != 0) + return true; + if ((BitBoard.rookAttacks(sq, occupied) & (pos.pieceTypeBB[Piece.WROOK] | bbQueen)) != 0) + return true; + } + return false; + } + + /** + * Remove all illegal moves from moveList. + * "moveList" is assumed to be a list of pseudo-legal moves. + * This function removes the moves that don't defend from check threats. + */ + public static final void removeIllegal(Position pos, MoveList moveList) { + int length = 0; + UndoInfo ui = new UndoInfo(); + + boolean isInCheck = inCheck(pos); + final long occupied = pos.whiteBB | pos.blackBB; + int kSq = pos.getKingSq(pos.whiteMove); + long kingAtks = BitBoard.rookAttacks(kSq, occupied) | BitBoard.bishopAttacks(kSq, occupied); + int epSquare = pos.getEpSquare(); + if (isInCheck) { + kingAtks |= pos.pieceTypeBB[pos.whiteMove ? Piece.BKNIGHT : Piece.WKNIGHT]; + for (int mi = 0; mi < moveList.size; mi++) { + Move m = moveList.m[mi]; + boolean legal; + if ((m.from != kSq) && ((kingAtks & (1L<= 56) { // White promotion + setMove(moveList, sq0, sq, Piece.WQUEEN); + setMove(moveList, sq0, sq, Piece.WKNIGHT); + if (allPromotions) { + setMove(moveList, sq0, sq, Piece.WROOK); + setMove(moveList, sq0, sq, Piece.WBISHOP); + } + } else { // Black promotion + setMove(moveList, sq0, sq, Piece.BQUEEN); + setMove(moveList, sq0, sq, Piece.BKNIGHT); + if (allPromotions) { + setMove(moveList, sq0, sq, Piece.BROOK); + setMove(moveList, sq0, sq, Piece.BBISHOP); + } + } + promMask &= (promMask - 1); + } + while (mask != 0) { + int sq = BitBoard.numberOfTrailingZeros(mask); + setMove(moveList, sq + delta, sq, Piece.EMPTY); + mask &= (mask - 1); + } + return false; + } + + private final static void addPawnDoubleMovesByMask(MoveList moveList, Position pos, + long mask, int delta) { + while (mask != 0) { + int sq = BitBoard.numberOfTrailingZeros(mask); + setMove(moveList, sq + delta, sq, Piece.EMPTY); + mask &= (mask - 1); + } + } + + private final static boolean addMovesByMask(MoveList moveList, Position pos, int sq0, long mask) { + long oKingMask = pos.pieceTypeBB[pos.whiteMove ? Piece.BKING : Piece.WKING]; + if ((mask & oKingMask) != 0) { + int sq = BitBoard.numberOfTrailingZeros(mask & oKingMask); + moveList.size = 0; + setMove(moveList, sq0, sq, Piece.EMPTY); + return true; + } + while (mask != 0) { + int sq = BitBoard.numberOfTrailingZeros(mask); + setMove(moveList, sq0, sq, Piece.EMPTY); + mask &= (mask - 1); + } + return false; + } + + private final static void setMove(MoveList moveList, int from, int to, int promoteTo) { + Move m = moveList.m[moveList.size++]; + m.from = from; + m.to = to; + m.promoteTo = promoteTo; + m.score = 0; + } + + // Code to handle the Move cache. + private Object[] moveListCache = new Object[200]; + private int moveListsInCache = 0; + + private static final int MAX_MOVES = 256; + + private final MoveList getMoveListObj() { + MoveList ml; + if (moveListsInCache > 0) { + ml = (MoveList)moveListCache[--moveListsInCache]; + ml.size = 0; + } else { + ml = new MoveList(); + for (int i = 0; i < MAX_MOVES; i++) + ml.m[i] = new Move(0, 0, Piece.EMPTY); + } + return ml; + } + + /** Return all move objects in moveList to the move cache. */ + public final void returnMoveList(MoveList moveList) { + if (moveListsInCache < moveListCache.length) { + moveListCache[moveListsInCache++] = moveList; + } + } +} diff --git a/CuckooChessEngine/src/chess/Parameters.java b/CuckooChessEngine/src/chess/Parameters.java new file mode 100644 index 0000000..92df884 --- /dev/null +++ b/CuckooChessEngine/src/chess/Parameters.java @@ -0,0 +1,166 @@ +package chess; + +import java.util.ArrayList; +import java.util.Map; +import java.util.TreeMap; + +public class Parameters { + public static enum Type { + CHECK, + SPIN, + COMBO, + BUTTON, + STRING + } + + public static class ParamBase { + public String name; + public Type type; + public boolean visible; + } + + public static final class CheckParam extends ParamBase { + public boolean value; + public boolean defaultValue; + CheckParam(String name, boolean visible, boolean def) { + this.name = name; + this.type = Type.CHECK; + this.visible = visible; + this.value = def; + this.defaultValue = def; + } + } + + public static final class SpinParam extends ParamBase { + public int minValue; + public int maxValue; + public int value; + public int defaultValue; + SpinParam(String name, boolean visible, int minV, int maxV, int def) { + this.name = name; + this.type = Type.SPIN; + this.visible = visible; + this.minValue = minV; + this.maxValue = maxV; + this.value = def; + this.defaultValue = def; + } + } + + public static final class ComboParam extends ParamBase { + public String[] allowedValues; + public String value; + public String defaultValue; + ComboParam(String name, boolean visible, String[] allowed, String def) { + this.name = name; + this.type = Type.COMBO; + this.visible = visible; + this.allowedValues = allowed; + this.value = def; + this.defaultValue = def; + } + } + + public static final class ButtonParam extends ParamBase { + ButtonParam(String name, boolean visible) { + this.name = name; + this.type = Type.BUTTON; + this.visible = visible; + } + } + + public static final class StringParam extends ParamBase { + public String value; + public String defaultValue; + StringParam(String name, boolean visible, String def) { + this.name = name; + this.type = Type.STRING; + this.visible = visible; + this.value = def; + this.defaultValue = def; + } + } + + public static Parameters instance() { + return inst; + } + public final String[] getParamNames() { + ArrayList parNames = new ArrayList(); + for (Map.Entry e : params.entrySet()) + if (e.getValue().visible) + parNames.add(e.getKey()); + return parNames.toArray(new String[parNames.size()]); + } + + public final ParamBase getParam(String name) { + return params.get(name); + } + + private static final Parameters inst = new Parameters(); + private Map params = new TreeMap(); + + private Parameters() { + addPar(new SpinParam("qV", false, -200, 200, 0)); + addPar(new SpinParam("rV", false, -200, 200, 0)); + addPar(new SpinParam("bV", false, -200, 200, 0)); + addPar(new SpinParam("nV", false, -200, 200, 0)); + addPar(new SpinParam("pV", false, -200, 200, 0)); + } + + private final void addPar(ParamBase p) { + params.put(p.name.toLowerCase(), p); + } + + final boolean getBooleanPar(String name) { + return ((CheckParam)params.get(name.toLowerCase())).value; + } + final int getIntPar(String name) { + int ret = ((SpinParam)params.get(name.toLowerCase())).value; + return ret; + } + final String getStringPar(String name) { + return ((StringParam)params.get(name.toLowerCase())).value; + } + + public final void set(String name, String value) { + ParamBase p = params.get(name.toLowerCase()); + if (p == null) + return; + switch (p.type) { + case CHECK: { + CheckParam cp = (CheckParam)p; + if (value.toLowerCase().equals("true")) + cp.value = true; + else if (value.toLowerCase().equals("false")) + cp.value = false; + break; + } + case SPIN: { + SpinParam sp = (SpinParam)p; + try { + int val = Integer.parseInt(value); + if ((val >= sp.minValue) && (val <= sp.maxValue)) + sp.value = val; + } catch (NumberFormatException ex) { + } + break; + } + case COMBO: { + ComboParam cp = (ComboParam)p; + for (String allowed : cp.allowedValues) + if (allowed.toLowerCase().equals(value.toLowerCase())) { + cp.value = allowed; + break; + } + break; + } + case BUTTON: + break; + case STRING: { + StringParam sp = (StringParam)p; + sp.value = value; + break; + } + } + } +} diff --git a/CuckooChessEngine/src/chess/Piece.java b/CuckooChessEngine/src/chess/Piece.java new file mode 100644 index 0000000..a974356 --- /dev/null +++ b/CuckooChessEngine/src/chess/Piece.java @@ -0,0 +1,57 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +/** + * Constants for different piece types. + * @author petero + */ +public class Piece { + public static final int EMPTY = 0; + + public static final int WKING = 1; + public static final int WQUEEN = 2; + public static final int WROOK = 3; + public static final int WBISHOP = 4; + public static final int WKNIGHT = 5; + public static final int WPAWN = 6; + + public static final int BKING = 7; + public static final int BQUEEN = 8; + public static final int BROOK = 9; + public static final int BBISHOP = 10; + public static final int BKNIGHT = 11; + public static final int BPAWN = 12; + + public static final int nPieceTypes = 13; + + /** + * Return true if p is a white piece, false otherwise. + * Note that if p is EMPTY, an unspecified value is returned. + */ + public static final boolean isWhite(int pType) { + return pType < BKING; + } + public static final int makeWhite(int pType) { + return pType < BKING ? pType : pType - (BKING - WKING); + } + public static final int makeBlack(int pType) { + return ((pType > EMPTY) && (pType < BKING)) ? pType + (BKING - WKING) : pType; + } +} diff --git a/CuckooChessEngine/src/chess/Player.java b/CuckooChessEngine/src/chess/Player.java new file mode 100644 index 0000000..4b24aee --- /dev/null +++ b/CuckooChessEngine/src/chess/Player.java @@ -0,0 +1,59 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.util.List; + +/** + * Interface for human/computer players. + * @author petero + */ +public interface Player { + /** + * Get a command from a player. + * The command can be a valid move string, in which case the move is played + * and the turn goes over to the other player. The command can also be a special + * command, such as "quit", "new", "resign", etc. + * @param history List of earlier positions (not including the current position). + * This makes it possible for the player to correctly handle + * the draw by repetition rule. + */ + public String getCommand(Position pos, boolean drawOffer, List history); + + /** Return true if this player is a human player. */ + public boolean isHumanPlayer(); + + /** + * Inform player whether or not to use an opening book. + * Of course, a human player is likely to ignore this. + */ + public void useBook(boolean bookOn); + + /** + * Inform player about min recommended/max allowed thinking time per move. + * Of course, a human player is likely to ignore this. + */ + public void timeLimit(int minTimeLimit, int maxTimeLimit, boolean randomMode); + + /** + * Inform player that the transposition table should be cleared. + * Of coarse, a human player has a hard time implementing this. + */ + public void clearTT(); +} diff --git a/CuckooChessEngine/src/chess/Position.java b/CuckooChessEngine/src/chess/Position.java new file mode 100644 index 0000000..65c7b40 --- /dev/null +++ b/CuckooChessEngine/src/chess/Position.java @@ -0,0 +1,674 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Stores the state of a chess position. + * All required state is stored, except for all previous positions + * since the last capture or pawn move. That state is only needed + * for three-fold repetition draw detection, and is better stored + * in a separate hash table. + * @author petero + */ +public class Position { + public int[] squares; + + // Bitboards + public long[] pieceTypeBB; + public long whiteBB, blackBB; + + // Piece square table scores + public short[] psScore1, psScore2; + + public boolean whiteMove; + + /** Bit definitions for the castleMask bit mask. */ + public static final int A1_CASTLE = 0; /** White long castle. */ + public static final int H1_CASTLE = 1; /** White short castle. */ + public static final int A8_CASTLE = 2; /** Black long castle. */ + public static final int H8_CASTLE = 3; /** Black short castle. */ + + private int castleMask; + + private int epSquare; + + /** Number of half-moves since last 50-move reset. */ + int halfMoveClock; + + /** Game move number, starting from 1. */ + public int fullMoveCounter; + + private long hashKey; // Cached Zobrist hash key + private long pHashKey; + public int wKingSq, bKingSq; // Cached king positions + public int wMtrl; // Total value of all white pieces and pawns + public int bMtrl; // Total value of all black pieces and pawns + public int wMtrlPawns; // Total value of all white pawns + public int bMtrlPawns; // Total value of all black pawns + + /** Initialize board to empty position. */ + public Position() { + squares = new int[64]; + for (int i = 0; i < 64; i++) + squares[i] = Piece.EMPTY; + pieceTypeBB = new long[Piece.nPieceTypes]; + psScore1 = new short[Piece.nPieceTypes]; + psScore2 = new short[Piece.nPieceTypes]; + for (int i = 0; i < Piece.nPieceTypes; i++) { + pieceTypeBB[i] = 0L; + psScore1[i] = 0; + psScore2[i] = 0; + } + whiteBB = blackBB = 0L; + whiteMove = true; + castleMask = 0; + epSquare = -1; + halfMoveClock = 0; + fullMoveCounter = 1; + hashKey = computeZobristHash(); + wKingSq = bKingSq = -1; + wMtrl = bMtrl = -Evaluate.kV; + wMtrlPawns = bMtrlPawns = 0; + } + + public Position(Position other) { + squares = new int[64]; + for (int i = 0; i < 64; i++) + squares[i] = other.squares[i]; + pieceTypeBB = new long[Piece.nPieceTypes]; + psScore1 = new short[Piece.nPieceTypes]; + psScore2 = new short[Piece.nPieceTypes]; + for (int i = 0; i < Piece.nPieceTypes; i++) { + pieceTypeBB[i] = other.pieceTypeBB[i]; + psScore1[i] = other.psScore1[i]; + psScore2[i] = other.psScore2[i]; + } + whiteBB = other.whiteBB; + blackBB = other.blackBB; + whiteMove = other.whiteMove; + castleMask = other.castleMask; + epSquare = other.epSquare; + halfMoveClock = other.halfMoveClock; + fullMoveCounter = other.fullMoveCounter; + hashKey = other.hashKey; + pHashKey = other.pHashKey; + wKingSq = other.wKingSq; + bKingSq = other.bKingSq; + wMtrl = other.wMtrl; + bMtrl = other.bMtrl; + wMtrlPawns = other.wMtrlPawns; + bMtrlPawns = other.bMtrlPawns; + } + + @Override + public boolean equals(Object o) { + if ((o == null) || (o.getClass() != this.getClass())) + return false; + Position other = (Position)o; + if (!drawRuleEquals(other)) + return false; + if (halfMoveClock != other.halfMoveClock) + return false; + if (fullMoveCounter != other.fullMoveCounter) + return false; + if (hashKey != other.hashKey) + return false; + if (pHashKey != other.pHashKey) + return false; + return true; + } + @Override + public int hashCode() { + return (int)hashKey; + } + + /** + * Return Zobrist hash value for the current position. + * Everything except the move counters are included in the hash value. + */ + public final long zobristHash() { + return hashKey; + } + public final long pawnZobristHash() { + return pHashKey; + } + public final long kingZobristHash() { + return psHashKeys[Piece.WKING][wKingSq] ^ + psHashKeys[Piece.BKING][bKingSq]; + } + + public final long historyHash() { + long ret = hashKey; + if (halfMoveClock >= 80) { + ret ^= moveCntKeys[Math.min(halfMoveClock, 100)]; + } + return ret; + } + + /** + * Decide if two positions are equal in the sense of the draw by repetition rule. + * @return True if positions are equal, false otherwise. + */ + final public boolean drawRuleEquals(Position other) { + for (int i = 0; i < 64; i++) { + if (squares[i] != other.squares[i]) + return false; + } + if (whiteMove != other.whiteMove) + return false; + if (castleMask != other.castleMask) + return false; + if (epSquare != other.epSquare) + return false; + return true; + } + + public final void setWhiteMove(boolean whiteMove) { + if (whiteMove != this.whiteMove) { + hashKey ^= whiteHashKey; + this.whiteMove = whiteMove; + } + } + /** Return index in squares[] vector corresponding to (x,y). */ + public final static int getSquare(int x, int y) { + return y * 8 + x; + } + /** Return x position (file) corresponding to a square. */ + public final static int getX(int square) { + return square & 7; + } + /** Return y position (rank) corresponding to a square. */ + public final static int getY(int square) { + return square >> 3; + } + /** Return true if (x,y) is a dark square. */ + public final static boolean darkSquare(int x, int y) { + return (x & 1) == (y & 1); + } + + /** Return piece occupying a square. */ + public final int getPiece(int square) { + return squares[square]; + } + + /** Move a non-pawn piece to an empty square. */ + private final void movePieceNotPawn(int from, int to) { + final int piece = squares[from]; + hashKey ^= psHashKeys[piece][from]; + hashKey ^= psHashKeys[piece][to]; + hashKey ^= psHashKeys[Piece.EMPTY][from]; + hashKey ^= psHashKeys[Piece.EMPTY][to]; + + squares[from] = Piece.EMPTY; + squares[to] = piece; + + final long sqMaskF = 1L << from; + final long sqMaskT = 1L << to; + pieceTypeBB[piece] &= ~sqMaskF; + pieceTypeBB[piece] |= sqMaskT; + if (Piece.isWhite(piece)) { + whiteBB &= ~sqMaskF; + whiteBB |= sqMaskT; + if (piece == Piece.WKING) + wKingSq = to; + } else { + blackBB &= ~sqMaskF; + blackBB |= sqMaskT; + if (piece == Piece.BKING) + bKingSq = to; + } + + psScore1[piece] += Evaluate.psTab1[piece][to] - Evaluate.psTab1[piece][from]; + psScore2[piece] += Evaluate.psTab2[piece][to] - Evaluate.psTab2[piece][from]; + } + + /** Set a square to a piece value. */ + public final void setPiece(int square, int piece) { + int removedPiece = squares[square]; + squares[square] = piece; + + // Update hash key + hashKey ^= psHashKeys[removedPiece][square]; + hashKey ^= psHashKeys[piece][square]; + + // Update bitboards + final long sqMask = 1L << square; + pieceTypeBB[removedPiece] &= ~sqMask; + pieceTypeBB[piece] |= sqMask; + + if (removedPiece != Piece.EMPTY) { + int pVal = Evaluate.pieceValue[removedPiece]; + if (Piece.isWhite(removedPiece)) { + wMtrl -= pVal; + whiteBB &= ~sqMask; + if (removedPiece == Piece.WPAWN) { + wMtrlPawns -= pVal; + pHashKey ^= psHashKeys[Piece.WPAWN][square]; + } + } else { + bMtrl -= pVal; + blackBB &= ~sqMask; + if (removedPiece == Piece.BPAWN) { + bMtrlPawns -= pVal; + pHashKey ^= psHashKeys[Piece.BPAWN][square]; + } + } + } + + if (piece != Piece.EMPTY) { + int pVal = Evaluate.pieceValue[piece]; + if (Piece.isWhite(piece)) { + wMtrl += pVal; + whiteBB |= sqMask; + if (piece == Piece.WPAWN) { + wMtrlPawns += pVal; + pHashKey ^= psHashKeys[Piece.WPAWN][square]; + } + if (piece == Piece.WKING) + wKingSq = square; + } else { + bMtrl += pVal; + blackBB |= sqMask; + if (piece == Piece.BPAWN) { + bMtrlPawns += pVal; + pHashKey ^= psHashKeys[Piece.BPAWN][square]; + } + if (piece == Piece.BKING) + bKingSq = square; + } + } + + // Update piece/square table scores + psScore1[removedPiece] -= Evaluate.psTab1[removedPiece][square]; + psScore2[removedPiece] -= Evaluate.psTab2[removedPiece][square]; + psScore1[piece] += Evaluate.psTab1[piece][square]; + psScore2[piece] += Evaluate.psTab2[piece][square]; + } + + /** + * Set a square to a piece value. + * Special version that only updates enough of the state for the SEE function to be happy. + */ + public final void setSEEPiece(int square, int piece) { + int removedPiece = squares[square]; + + // Update board + squares[square] = piece; + + // Update bitboards + long sqMask = 1L << square; + pieceTypeBB[removedPiece] &= ~sqMask; + pieceTypeBB[piece] |= sqMask; + if (removedPiece != Piece.EMPTY) { + if (Piece.isWhite(removedPiece)) + whiteBB &= ~sqMask; + else + blackBB &= ~sqMask; + } + if (piece != Piece.EMPTY) { + if (Piece.isWhite(piece)) + whiteBB |= sqMask; + else + blackBB |= sqMask; + } + } + + /** Return true if white long castling right has not been lost. */ + public final boolean a1Castle() { + return (castleMask & (1 << A1_CASTLE)) != 0; + } + /** Return true if white short castling right has not been lost. */ + public final boolean h1Castle() { + return (castleMask & (1 << H1_CASTLE)) != 0; + } + /** Return true if black long castling right has not been lost. */ + public final boolean a8Castle() { + return (castleMask & (1 << A8_CASTLE)) != 0; + } + /** Return true if black short castling right has not been lost. */ + public final boolean h8Castle() { + return (castleMask & (1 << H8_CASTLE)) != 0; + } + /** Bitmask describing castling rights. */ + public final int getCastleMask() { + return castleMask; + } + public final void setCastleMask(int castleMask) { + hashKey ^= castleHashKeys[this.castleMask]; + hashKey ^= castleHashKeys[castleMask]; + this.castleMask = castleMask; + } + + /** En passant square, or -1 if no ep possible. */ + public final int getEpSquare() { + return epSquare; + } + public final void setEpSquare(int epSquare) { + if (this.epSquare != epSquare) { + hashKey ^= epHashKeys[(this.epSquare >= 0) ? getX(this.epSquare) + 1 : 0]; + hashKey ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0]; + this.epSquare = epSquare; + } + } + + + public final int getKingSq(boolean whiteMove) { + return whiteMove ? wKingSq : bKingSq; + } + + /** Apply a move to the current position. */ + public final void makeMove(Move move, UndoInfo ui) { + ui.capturedPiece = squares[move.to]; + ui.castleMask = castleMask; + ui.epSquare = epSquare; + ui.halfMoveClock = halfMoveClock; + boolean wtm = whiteMove; + + final int p = squares[move.from]; + int capP = squares[move.to]; + long fromMask = 1L << move.from; + + int prevEpSquare = epSquare; + setEpSquare(-1); + + if ((capP != Piece.EMPTY) || (((pieceTypeBB[Piece.WPAWN] | pieceTypeBB[Piece.BPAWN]) & fromMask) != 0)) { + halfMoveClock = 0; + + // Handle en passant and epSquare + if (p == Piece.WPAWN) { + if (move.to - move.from == 2 * 8) { + int x = Position.getX(move.to); + if ( ((x > 0) && (squares[move.to - 1] == Piece.BPAWN)) || + ((x < 7) && (squares[move.to + 1] == Piece.BPAWN))) { + setEpSquare(move.from + 8); + } + } else if (move.to == prevEpSquare) { + setPiece(move.to - 8, Piece.EMPTY); + } + } else if (p == Piece.BPAWN) { + if (move.to - move.from == -2 * 8) { + int x = Position.getX(move.to); + if ( ((x > 0) && (squares[move.to - 1] == Piece.WPAWN)) || + ((x < 7) && (squares[move.to + 1] == Piece.WPAWN))) { + setEpSquare(move.from - 8); + } + } else if (move.to == prevEpSquare) { + setPiece(move.to + 8, Piece.EMPTY); + } + } + + if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) { + if (wtm) { + setCastleMask(castleMask & ~(1 << Position.A1_CASTLE)); + setCastleMask(castleMask & ~(1 << Position.H1_CASTLE)); + } else { + setCastleMask(castleMask & ~(1 << Position.A8_CASTLE)); + setCastleMask(castleMask & ~(1 << Position.H8_CASTLE)); + } + } + + // Perform move + setPiece(move.from, Piece.EMPTY); + // Handle promotion + if (move.promoteTo != Piece.EMPTY) { + setPiece(move.to, move.promoteTo); + } else { + setPiece(move.to, p); + } + } else { + halfMoveClock++; + + // Handle castling + if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) { + int k0 = move.from; + if (move.to == k0 + 2) { // O-O + movePieceNotPawn(k0 + 3, k0 + 1); + } else if (move.to == k0 - 2) { // O-O-O + movePieceNotPawn(k0 - 4, k0 - 1); + } + if (wtm) { + setCastleMask(castleMask & ~(1 << Position.A1_CASTLE)); + setCastleMask(castleMask & ~(1 << Position.H1_CASTLE)); + } else { + setCastleMask(castleMask & ~(1 << Position.A8_CASTLE)); + setCastleMask(castleMask & ~(1 << Position.H8_CASTLE)); + } + } + + // Perform move + movePieceNotPawn(move.from, move.to); + } + if (wtm) { + // Update castling rights when rook moves + if ((BitBoard.maskCorners & fromMask) != 0) { + if (p == Piece.WROOK) + removeCastleRights(move.from); + } + if ((BitBoard.maskCorners & (1L << move.to)) != 0) { + if (capP == Piece.BROOK) + removeCastleRights(move.to); + } + } else { + fullMoveCounter++; + // Update castling rights when rook moves + if ((BitBoard.maskCorners & fromMask) != 0) { + if (p == Piece.BROOK) + removeCastleRights(move.from); + } + if ((BitBoard.maskCorners & (1L << move.to)) != 0) { + if (capP == Piece.WROOK) + removeCastleRights(move.to); + } + } + + hashKey ^= whiteHashKey; + whiteMove = !wtm; + } + + public final void unMakeMove(Move move, UndoInfo ui) { + hashKey ^= whiteHashKey; + whiteMove = !whiteMove; + int p = squares[move.to]; + setPiece(move.from, p); + setPiece(move.to, ui.capturedPiece); + setCastleMask(ui.castleMask); + setEpSquare(ui.epSquare); + halfMoveClock = ui.halfMoveClock; + boolean wtm = whiteMove; + if (move.promoteTo != Piece.EMPTY) { + p = wtm ? Piece.WPAWN : Piece.BPAWN; + setPiece(move.from, p); + } + if (!wtm) { + fullMoveCounter--; + } + + // Handle castling + int king = wtm ? Piece.WKING : Piece.BKING; + if (p == king) { + int k0 = move.from; + if (move.to == k0 + 2) { // O-O + movePieceNotPawn(k0 + 1, k0 + 3); + } else if (move.to == k0 - 2) { // O-O-O + movePieceNotPawn(k0 - 1, k0 - 4); + } + } + + // Handle en passant + if (move.to == epSquare) { + if (p == Piece.WPAWN) { + setPiece(move.to - 8, Piece.BPAWN); + } else if (p == Piece.BPAWN) { + setPiece(move.to + 8, Piece.WPAWN); + } + } + } + + /** + * Apply a move to the current position. + * Special version that only updates enough of the state for the SEE function to be happy. + */ + public final void makeSEEMove(Move move, UndoInfo ui) { + ui.capturedPiece = squares[move.to]; + boolean wtm = whiteMove; + + int p = squares[move.from]; + long fromMask = 1L << move.from; + + // Handle castling + if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) { + int k0 = move.from; + if (move.to == k0 + 2) { // O-O + setSEEPiece(k0 + 1, squares[k0 + 3]); + setSEEPiece(k0 + 3, Piece.EMPTY); + } else if (move.to == k0 - 2) { // O-O-O + setSEEPiece(k0 - 1, squares[k0 - 4]); + setSEEPiece(k0 - 4, Piece.EMPTY); + } + } + + // Handle en passant + if (move.to == epSquare) { + if (p == Piece.WPAWN) { + setSEEPiece(move.to - 8, Piece.EMPTY); + } else if (p == Piece.BPAWN) { + setSEEPiece(move.to + 8, Piece.EMPTY); + } + } + + // Perform move + setSEEPiece(move.from, Piece.EMPTY); + setSEEPiece(move.to, p); + whiteMove = !wtm; + } + + public final void unMakeSEEMove(Move move, UndoInfo ui) { + whiteMove = !whiteMove; + int p = squares[move.to]; + setSEEPiece(move.from, p); + setSEEPiece(move.to, ui.capturedPiece); + boolean wtm = whiteMove; + + // Handle castling + int king = wtm ? Piece.WKING : Piece.BKING; + if (p == king) { + int k0 = move.from; + if (move.to == k0 + 2) { // O-O + setSEEPiece(k0 + 3, squares[k0 + 1]); + setSEEPiece(k0 + 1, Piece.EMPTY); + } else if (move.to == k0 - 2) { // O-O-O + setSEEPiece(k0 - 4, squares[k0 - 1]); + setSEEPiece(k0 - 1, Piece.EMPTY); + } + } + + // Handle en passant + if (move.to == epSquare) { + if (p == Piece.WPAWN) { + setSEEPiece(move.to - 8, Piece.BPAWN); + } else if (p == Piece.BPAWN) { + setSEEPiece(move.to + 8, Piece.WPAWN); + } + } + } + + private final void removeCastleRights(int square) { + if (square == Position.getSquare(0, 0)) { + setCastleMask(castleMask & ~(1 << Position.A1_CASTLE)); + } else if (square == Position.getSquare(7, 0)) { + setCastleMask(castleMask & ~(1 << Position.H1_CASTLE)); + } else if (square == Position.getSquare(0, 7)) { + setCastleMask(castleMask & ~(1 << Position.A8_CASTLE)); + } else if (square == Position.getSquare(7, 7)) { + setCastleMask(castleMask & ~(1 << Position.H8_CASTLE)); + } + } + + /* ------------- Hashing code ------------------ */ + + static final long[][] psHashKeys; // [piece][square] + private static final long whiteHashKey; + private static final long[] castleHashKeys; // [castleMask] + private static final long[] epHashKeys; // [epFile + 1] (epFile==-1 for no ep) + private static final long[] moveCntKeys; // [min(halfMoveClock, 100)] + + static { + psHashKeys = new long[Piece.nPieceTypes][64]; + castleHashKeys = new long[16]; + epHashKeys = new long[9]; + moveCntKeys = new long[101]; + int rndNo = 0; + for (int p = 0; p < Piece.nPieceTypes; p++) { + for (int sq = 0; sq < 64; sq++) { + psHashKeys[p][sq] = getRandomHashVal(rndNo++); + } + } + whiteHashKey = getRandomHashVal(rndNo++); + for (int cm = 0; cm < castleHashKeys.length; cm++) + castleHashKeys[cm] = getRandomHashVal(rndNo++); + for (int f = 0; f < epHashKeys.length; f++) + epHashKeys[f] = getRandomHashVal(rndNo++); + for (int mc = 0; mc < moveCntKeys.length; mc++) + moveCntKeys[mc] = getRandomHashVal(rndNo++); + } + + /** + * Compute the Zobrist hash value non-incrementally. Only useful for test programs. + */ + final long computeZobristHash() { + long hash = 0; + for (int sq = 0; sq < 64; sq++) { + int p = squares[sq]; + hash ^= psHashKeys[p][sq]; + if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) + pHashKey ^= psHashKeys[p][sq]; + } + if (whiteMove) + hash ^= whiteHashKey; + hash ^= castleHashKeys[castleMask]; + hash ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0]; + return hash; + } + + private final static long getRandomHashVal(int rndNo) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] input = new byte[4]; + for (int i = 0; i < 4; i++) + input[i] = (byte)((rndNo >> (i * 8)) & 0xff); + byte[] digest = md.digest(input); + long ret = 0; + for (int i = 0; i < 8; i++) { + ret ^= ((long)digest[i]) << (i * 8); + } + return ret; + } catch (NoSuchAlgorithmException ex) { + throw new UnsupportedOperationException("SHA-1 not available"); + } + } + + /** Useful for debugging. */ + public final String toString() { + return TextIO.asciiBoard(this) + (whiteMove ? "white\n" : "black\n") + + Long.toHexString(zobristHash()) + "\n"; + } +} diff --git a/CuckooChessEngine/src/chess/Search.java b/CuckooChessEngine/src/chess/Search.java new file mode 100644 index 0000000..d7d6f80 --- /dev/null +++ b/CuckooChessEngine/src/chess/Search.java @@ -0,0 +1,1220 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import chess.TranspositionTable.TTEntry; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +/** + * + * @author petero + */ +public class Search { + final static int plyScale = 8; // Fractional ply resolution + + Position pos; + MoveGen moveGen; + Evaluate eval; + KillerTable kt; + History ht; + long[] posHashList; // List of hashes for previous positions up to the last "zeroing" move. + int posHashListSize; // Number of used entries in posHashList + int posHashFirstNew; // First entry in posHashList that has not been played OTB. + TranspositionTable tt; + TreeLogger log = null; + + private static final class SearchTreeInfo { + UndoInfo undoInfo; + Move hashMove; // Temporary storage for local hashMove variable + boolean allowNullMove; // Don't allow two null-moves in a row + Move bestMove; // Copy of the best found move at this ply + Move currentMove; // Move currently being searched + int lmr; // LMR reduction amount + long nodeIdx; + SearchTreeInfo() { + undoInfo = new UndoInfo(); + hashMove = new Move(0, 0, 0); + allowNullMove = true; + bestMove = new Move(0, 0, 0); + } + } + SearchTreeInfo[] searchTreeInfo; + + // Time management + long tStart; // Time when search started + long minTimeMillis; // Minimum recommended thinking time + long maxTimeMillis; // Maximum allowed thinking time + boolean searchNeedMoreTime; // True if negaScout should use up to maxTimeMillis time. + private int maxNodes; // Maximum number of nodes to search (approximately) + int nodesToGo; // Number of nodes until next time check + public int nodesBetweenTimeCheck = 5000; // How often to check remaining time + + // Reduced strength variables + private int strength = 1000; // Strength (0-1000) + boolean weak = false; // Set to strength < 1000 + long randomSeed = 0; + + // Search statistics stuff + int nodes; + int qNodes; + int[] nodesPlyVec; + int[] nodesDepthVec; + int totalNodes; + long tLastStats; // Time when notifyStats was last called + boolean verbose; + + public final static int MATE0 = 32000; + + public final static int UNKNOWN_SCORE = -32767; // Represents unknown static eval score + int q0Eval; // Static eval score at first level of quiescence search + + public Search(Position pos, long[] posHashList, int posHashListSize, TranspositionTable tt) { + this.pos = new Position(pos); + this.moveGen = new MoveGen(); + this.posHashList = posHashList; + this.posHashListSize = posHashListSize; + this.tt = tt; + eval = new Evaluate(); + kt = new KillerTable(); + ht = new History(); + posHashFirstNew = posHashListSize; + initNodeStats(); + minTimeMillis = -1; + maxTimeMillis = -1; + searchNeedMoreTime = false; + maxNodes = -1; + final int vecLen = 200; + searchTreeInfo = new SearchTreeInfo[vecLen]; + for (int i = 0; i < vecLen; i++) { + searchTreeInfo[i] = new SearchTreeInfo(); + } + } + + static final class StopSearch extends Exception { + private static final long serialVersionUID = -5546906604987117015L; + public StopSearch() { + } + public StopSearch(String msg) { + super(msg); + } + } + + /** + * Used to get various search information during search + */ + public interface Listener { + public void notifyDepth(int depth); + public void notifyCurrMove(Move m, int moveNr); + public void notifyPV(int depth, int score, int time, int nodes, int nps, + boolean isMate, boolean upperBound, boolean lowerBound, ArrayList pv); + public void notifyStats(int nodes, int nps, int time); + } + + Listener listener; + public void setListener(Listener listener) { + this.listener = listener; + } + + private final static class MoveInfo { + Move move; + int nodes; + MoveInfo(Move m, int n) { move = m; nodes = n; } + public static final class SortByScore implements Comparator { + public int compare(MoveInfo mi1, MoveInfo mi2) { + if ((mi1 == null) && (mi2 == null)) + return 0; + if (mi1 == null) + return 1; + if (mi2 == null) + return -1; + return mi2.move.score - mi1.move.score; + } + } + public static final class SortByNodes implements Comparator { + public int compare(MoveInfo mi1, MoveInfo mi2) { + if ((mi1 == null) && (mi2 == null)) + return 0; + if (mi1 == null) + return 1; + if (mi2 == null) + return -1; + return mi2.nodes - mi1.nodes; + } + } + } + + final public void timeLimit(int minTimeLimit, int maxTimeLimit) { + minTimeMillis = minTimeLimit; + maxTimeMillis = maxTimeLimit; + } + + final public void setStrength(int strength, long randomSeed) { + if (strength < 0) strength = 0; + if (strength > 1000) strength = 1000; + this.strength = strength; + weak = strength < 1000; + this.randomSeed = randomSeed; + } + + final public Move iterativeDeepening(MoveGen.MoveList scMovesIn, + int maxDepth, int initialMaxNodes, boolean verbose) { + tStart = System.currentTimeMillis(); +// log = TreeLogger.getWriter("/home/petero/treelog.dmp", pos); + totalNodes = 0; + if (scMovesIn.size <= 0) + return null; // No moves to search + MoveInfo[] scMoves = new MoveInfo[scMovesIn.size]; + for (int mi = 0, len = 0; mi < scMovesIn.size; mi++) { + Move m = scMovesIn.m[mi]; + scMoves[len++] = new MoveInfo(m, 0); + } + maxNodes = initialMaxNodes; + nodesToGo = 0; + Position origPos = new Position(pos); + int bestScoreLastIter = 0; + boolean firstIteration = true; + Move bestMove = scMoves[0].move; + this.verbose = verbose; + if ((maxDepth < 0) || (maxDepth > 100)) { + maxDepth = 100; + } + for (int i = 0; i < searchTreeInfo.length; i++) { + searchTreeInfo[i].allowNullMove = true; + } + try { + for (int depthS = plyScale; ; depthS += plyScale, firstIteration = false) { + initNodeStats(); + if (listener != null) listener.notifyDepth(depthS/plyScale); + int aspirationDelta = (Math.abs(bestScoreLastIter) <= MATE0 / 2) ? 20 : 1000; + int alpha = firstIteration ? -Search.MATE0 : Math.max(bestScoreLastIter - aspirationDelta, -Search.MATE0); + int bestScore = -Search.MATE0; + UndoInfo ui = new UndoInfo(); + boolean needMoreTime = false; + for (int mi = 0; mi < scMoves.length; mi++) { + searchNeedMoreTime = (mi > 0); + Move m = scMoves[mi].move; + if ((listener != null) && (System.currentTimeMillis() - tStart >= 1000)) { + listener.notifyCurrMove(m, mi + 1); + } + nodes = qNodes = 0; + posHashList[posHashListSize++] = pos.zobristHash(); + boolean givesCheck = MoveGen.givesCheck(pos, m); + int beta; + if (firstIteration) { + beta = Search.MATE0; + } else { + beta = (mi == 0) ? Math.min(bestScoreLastIter + aspirationDelta, Search.MATE0) : alpha + 1; + } + + int lmrS = 0; + boolean isCapture = (pos.getPiece(m.to) != Piece.EMPTY); + boolean isPromotion = (m.promoteTo != Piece.EMPTY); + if ((depthS >= 3*plyScale) && !isCapture && !isPromotion) { + if (!givesCheck && !passedPawnPush(pos, m)) { + if (mi >= 3) + lmrS = plyScale; + } + } +/* int nodes0 = nodes; + int qNodes0 = qNodes; + System.out.printf("%2d %5s %5d %5d %6s %6s ", + mi, "-", alpha, beta, "-", "-"); + System.out.printf("%-6s...\n", TextIO.moveToUCIString(m)); */ + pos.makeMove(m, ui); + SearchTreeInfo sti = searchTreeInfo[0]; + sti.currentMove = m; + sti.lmr = lmrS; + sti.nodeIdx = -1; + int score = -negaScout(-beta, -alpha, 1, depthS - lmrS - plyScale, -1, givesCheck); + if ((lmrS > 0) && (score > alpha)) { + sti.lmr = 0; + score = -negaScout(-beta, -alpha, 1, depthS - plyScale, -1, givesCheck); + } + int nodesThisMove = nodes + qNodes; + posHashListSize--; + pos.unMakeMove(m, ui); + { + int type = TTEntry.T_EXACT; + if (score <= alpha) { + type = TTEntry.T_LE; + } else if (score >= beta) { + type = TTEntry.T_GE; + } + m.score = score; + tt.insert(pos.historyHash(), m, type, 0, depthS, UNKNOWN_SCORE); + } + if (score >= beta) { + int retryDelta = aspirationDelta * 2; + while (score >= beta) { + beta = Math.min(score + retryDelta, Search.MATE0); + retryDelta = Search.MATE0 * 2; + if (mi != 0) + needMoreTime = true; + bestMove = m; + if (verbose) + System.out.printf("%-6s %6d %6d %6d >=\n", TextIO.moveToString(pos, m, false), + score, nodes, qNodes); + notifyPV(depthS/plyScale, score, false, true, m); + nodes = qNodes = 0; + posHashList[posHashListSize++] = pos.zobristHash(); + pos.makeMove(m, ui); + int score2 = -negaScout(-beta, -score, 1, depthS - plyScale, -1, givesCheck); + score = Math.max(score, score2); + nodesThisMove += nodes + qNodes; + posHashListSize--; + pos.unMakeMove(m, ui); + } + } else if ((mi == 0) && (score <= alpha)) { + int retryDelta = Search.MATE0 * 2; + while (score <= alpha) { + alpha = Math.max(score - retryDelta, -Search.MATE0); + retryDelta = Search.MATE0 * 2; + needMoreTime = searchNeedMoreTime = true; + if (verbose) + System.out.printf("%-6s %6d %6d %6d <=\n", TextIO.moveToString(pos, m, false), + score, nodes, qNodes); + notifyPV(depthS/plyScale, score, true, false, m); + nodes = qNodes = 0; + posHashList[posHashListSize++] = pos.zobristHash(); + pos.makeMove(m, ui); + score = -negaScout(-score, -alpha, 1, depthS - plyScale, -1, givesCheck); + nodesThisMove += nodes + qNodes; + posHashListSize--; + pos.unMakeMove(m, ui); + } + } + if (verbose || ((listener != null) && !firstIteration)) { + boolean havePV = false; + String PV = ""; + if ((score > alpha) || (mi == 0)) { + havePV = true; + if (verbose) { + PV = TextIO.moveToString(pos, m, false) + " "; + pos.makeMove(m, ui); + PV += tt.extractPV(pos); + pos.unMakeMove(m, ui); + } + } + if (verbose) { +/* System.out.printf("%2d %5d %5d %5d %6d %6d ", + mi, score, alpha, beta, nodes-nodes0, qNodes-qNodes0); + System.out.printf("%-6s\n", TextIO.moveToUCIString(m)); */ + System.out.printf("%-6s %6d %6d %6d%s %s\n", + TextIO.moveToString(pos, m, false), score, + nodes, qNodes, (score > alpha ? " *" : ""), PV); + } + if (havePV && !firstIteration) { + notifyPV(depthS/plyScale, score, false, false, m); + } + } + scMoves[mi].move.score = score; + scMoves[mi].nodes = nodesThisMove; + bestScore = Math.max(bestScore, score); + if (!firstIteration) { + if ((score > alpha) || (mi == 0)) { + alpha = score; + MoveInfo tmp = scMoves[mi]; + for (int i = mi - 1; i >= 0; i--) { + scMoves[i + 1] = scMoves[i]; + } + scMoves[0] = tmp; + bestMove = scMoves[0].move; + } + } + if (!firstIteration) { + long timeLimit = needMoreTime ? maxTimeMillis : minTimeMillis; + if (timeLimit >= 0) { + long tNow = System.currentTimeMillis(); + if (tNow - tStart >= timeLimit) + break; + } + } + } + if (firstIteration) { + Arrays.sort(scMoves, new MoveInfo.SortByScore()); + bestMove = scMoves[0].move; + notifyPV(depthS/plyScale, bestMove.score, false, false, bestMove); + } + long tNow = System.currentTimeMillis(); + if (verbose) { + for (int i = 0; i < 20; i++) { + System.out.printf("%2d %7d %7d\n", i, nodesPlyVec[i], nodesDepthVec[i]); + } + System.out.printf("Time: %.3f depth:%.2f nps:%d\n", (tNow - tStart) * .001, depthS/(double)plyScale, + (int)(totalNodes / ((tNow - tStart) * .001))); + } + if (maxTimeMillis >= 0) { + if (tNow - tStart >= minTimeMillis) + break; + } + if (depthS >= maxDepth * plyScale) + break; + if (maxNodes >= 0) { + if (totalNodes >= maxNodes) + break; + } + int plyToMate = Search.MATE0 - Math.abs(bestScore); + if (depthS >= plyToMate * plyScale) + break; + bestScoreLastIter = bestScore; + + if (!firstIteration) { + // Moves that were hard to search should be searched early in the next iteration + Arrays.sort(scMoves, 1, scMoves.length, new MoveInfo.SortByNodes()); + } + } + } catch (StopSearch ss) { + pos = origPos; + } + notifyStats(); + + if (log != null) { + log.close(); + log = null; + } + return bestMove; + } + + private final void notifyPV(int depth, int score, boolean uBound, boolean lBound, Move m) { + if (listener != null) { + boolean isMate = false; + if (score > MATE0 / 2) { + isMate = true; + score = (MATE0 - score) / 2; + } else if (score < -MATE0 / 2) { + isMate = true; + score = -((MATE0 + score - 1) / 2); + } + long tNow = System.currentTimeMillis(); + int time = (int) (tNow - tStart); + int nps = (time > 0) ? (int)(totalNodes / (time / 1000.0)) : 0; + ArrayList pv = tt.extractPVMoves(pos, m); + listener.notifyPV(depth, score, time, totalNodes, nps, isMate, uBound, lBound, pv); + } + } + + private final void notifyStats() { + long tNow = System.currentTimeMillis(); + if (listener != null) { + int time = (int) (tNow - tStart); + int nps = (time > 0) ? (int)(totalNodes / (time / 1000.0)) : 0; + listener.notifyStats(totalNodes, nps, time); + } + tLastStats = tNow; + } + + private static final Move emptyMove = new Move(0, 0, Piece.EMPTY, 0); + + /** + * Main recursive search algorithm. + * @return Score for the side to make a move, in position given by "pos". + */ + final public int negaScout(int alpha, int beta, int ply, int depth, int recaptureSquare, + final boolean inCheck) throws StopSearch { + if (log != null) { + SearchTreeInfo sti = searchTreeInfo[ply-1]; + long idx = log.logNodeStart(sti.nodeIdx, sti.currentMove, alpha, beta, ply, depth/plyScale); + searchTreeInfo[ply].nodeIdx = idx; + } + if (--nodesToGo <= 0) { + nodesToGo = nodesBetweenTimeCheck; + long tNow = System.currentTimeMillis(); + long timeLimit = searchNeedMoreTime ? maxTimeMillis : minTimeMillis; + if ( ((timeLimit >= 0) && (tNow - tStart >= timeLimit)) || + ((maxNodes >= 0) && (totalNodes >= maxNodes))) { + throw new StopSearch(); + } + if (tNow - tLastStats >= 1000) { + notifyStats(); + } + } + + // Collect statistics + if (verbose) { + if (ply < 20) nodesPlyVec[ply]++; + if (depth < 20*plyScale) nodesDepthVec[depth/plyScale]++; + } + final long hKey = pos.historyHash(); + + // Draw tests + if (canClaimDraw50(pos)) { + if (MoveGen.canTakeKing(pos)) { + int score = MATE0 - ply; + if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, score, TTEntry.T_EXACT, UNKNOWN_SCORE, hKey); + return score; + } + if (inCheck) { + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + if (moves.size == 0) { // Can't claim draw if already check mated. + int score = -(MATE0-(ply+1)); + if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, score, TTEntry.T_EXACT, UNKNOWN_SCORE, hKey); + moveGen.returnMoveList(moves); + return score; + } + moveGen.returnMoveList(moves); + } + if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, 0, TTEntry.T_EXACT, UNKNOWN_SCORE, hKey); + return 0; + } + if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashFirstNew)) { + if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, 0, TTEntry.T_EXACT, UNKNOWN_SCORE, hKey); + return 0; // No need to test for mate here, since it would have been + // discovered the first time the position came up. + } + + int evalScore = UNKNOWN_SCORE; + // Check transposition table + TTEntry ent = tt.probe(hKey); + Move hashMove = null; + SearchTreeInfo sti = searchTreeInfo[ply]; + if (ent.type != TTEntry.T_EMPTY) { + int score = ent.getScore(ply); + evalScore = ent.evalScore; + int plyToMate = MATE0 - Math.abs(score); + int eDepth = ent.getDepth(); + if ((beta == alpha + 1) && ((eDepth >= depth) || (eDepth >= plyToMate*plyScale))) { + if ( (ent.type == TTEntry.T_EXACT) || + (ent.type == TTEntry.T_GE) && (score >= beta) || + (ent.type == TTEntry.T_LE) && (score <= alpha)) { + if (score >= beta) { + hashMove = sti.hashMove; + ent.getMove(hashMove); + if ((hashMove != null) && (hashMove.from != hashMove.to)) + if (pos.getPiece(hashMove.to) == Piece.EMPTY) + kt.addKiller(ply, hashMove); + } + if (log != null) log.logNodeEnd(searchTreeInfo[ply].nodeIdx, score, ent.type, evalScore, hKey); + return score; + } + } + hashMove = sti.hashMove; + ent.getMove(hashMove); + } + + int posExtend = inCheck ? plyScale : 0; // Check extension + + // If out of depth, perform quiescence search + if (depth + posExtend <= 0) { + q0Eval = evalScore; + int score = quiesce(alpha, beta, ply, 0, inCheck); + int type = TTEntry.T_EXACT; + if (score <= alpha) { + type = TTEntry.T_LE; + } else if (score >= beta) { + type = TTEntry.T_GE; + } + emptyMove.score = score; + tt.insert(hKey, emptyMove, type, ply, depth, q0Eval); + if (log != null) log.logNodeEnd(sti.nodeIdx, score, type, evalScore, hKey); + return score; + } + + // Try null-move pruning + // FIXME! Try null-move verification in late endgames. See loss in round 21. + sti.currentMove = emptyMove; + if ( (depth >= 3*plyScale) && !inCheck && sti.allowNullMove && + (Math.abs(beta) <= MATE0 / 2)) { + if (MoveGen.canTakeKing(pos)) { + int score = MATE0 - ply; + if (log != null) log.logNodeEnd(sti.nodeIdx, score, TTEntry.T_EXACT, evalScore, hKey); + return score; + } + boolean nullOk; + if (pos.whiteMove) { + nullOk = (pos.wMtrl > pos.wMtrlPawns) && (pos.wMtrlPawns > 0); + } else { + nullOk = (pos.bMtrl > pos.bMtrlPawns) && (pos.bMtrlPawns > 0); + } + if (nullOk) { + if (evalScore == UNKNOWN_SCORE) + evalScore = eval.evalPos(pos); + if (evalScore < beta) + nullOk = false; + } + if (nullOk) { + final int R = (depth > 6*plyScale) ? 4*plyScale : 3*plyScale; + pos.setWhiteMove(!pos.whiteMove); + int epSquare = pos.getEpSquare(); + pos.setEpSquare(-1); + searchTreeInfo[ply+1].allowNullMove = false; + int score = -negaScout(-beta, -(beta - 1), ply + 1, depth - R, -1, false); + searchTreeInfo[ply+1].allowNullMove = true; + pos.setEpSquare(epSquare); + pos.setWhiteMove(!pos.whiteMove); + if (score >= beta) { + if (score > MATE0 / 2) + score = beta; + emptyMove.score = score; + tt.insert(hKey, emptyMove, TTEntry.T_GE, ply, depth, evalScore); + if (log != null) log.logNodeEnd(sti.nodeIdx, score, TTEntry.T_GE, evalScore, hKey); + return score; + } else { + if ((searchTreeInfo[ply-1].lmr > 0) && (depth < 5*plyScale)) { + Move m1 = searchTreeInfo[ply-1].currentMove; + Move m2 = searchTreeInfo[ply+1].bestMove; // threat move + if (m1.from != m1.to) { + if ((m1.to == m2.from) || (m1.from == m2.to) || + ((BitBoard.squaresBetween[m2.from][m2.to] & (1L << m1.from)) != 0)) { + // if the threat move was made possible by a reduced + // move on the previous ply, the reduction was unsafe. + // Return alpha to trigger a non-reduced re-search. + if (log != null) log.logNodeEnd(sti.nodeIdx, alpha, TTEntry.T_LE, evalScore, hKey); + return alpha; + } + } + } + } + } + } + + // Razoring + if ((Math.abs(alpha) <= MATE0 / 2) && (depth < 4*plyScale) && (beta == alpha + 1)) { + if (evalScore == UNKNOWN_SCORE) { + evalScore = eval.evalPos(pos); + } + final int razorMargin = 250; + if (evalScore < beta - razorMargin) { + q0Eval = evalScore; + int score = quiesce(alpha-razorMargin, beta-razorMargin, ply, 0, inCheck); + if (score <= alpha-razorMargin) { + emptyMove.score = score; + tt.insert(hKey, emptyMove, TTEntry.T_LE, ply, depth, evalScore); + if (log != null) log.logNodeEnd(sti.nodeIdx, score, TTEntry.T_LE, evalScore, hKey); + return score; + } + } + } + + boolean futilityPrune = false; + int futilityScore = alpha; + if (!inCheck && (depth < 5*plyScale) && (posExtend == 0)) { + if ((Math.abs(alpha) <= MATE0 / 2) && (Math.abs(beta) <= MATE0 / 2)) { + int margin; + if (depth <= plyScale) { + margin = 61; + } else if (depth <= 2*plyScale) { + margin = 144; + } else if (depth <= 3*plyScale) { + margin = 268; + } else { + margin = 334; + } + if (evalScore == UNKNOWN_SCORE) { + evalScore = eval.evalPos(pos); + } + futilityScore = evalScore + margin; + if (futilityScore <= alpha) { + futilityPrune = true; + } + } + } + + if ((depth > 4*plyScale) && ((hashMove == null) || (hashMove.from == hashMove.to))) { + boolean isPv = beta > alpha + 1; + if (isPv || (depth > 8 * plyScale)) { + // No hash move. Try internal iterative deepening. + long savedNodeIdx = sti.nodeIdx; + int newDepth = isPv ? depth - 2 * plyScale : depth * 3 / 8; + negaScout(alpha, beta, ply, newDepth, -1, inCheck); + sti.nodeIdx = savedNodeIdx; + ent = tt.probe(hKey); + if (ent.type != TTEntry.T_EMPTY) { + hashMove = sti.hashMove; + ent.getMove(hashMove); + } + } + } + + // Start searching move alternatives + // FIXME! Try hash move before generating move list. + MoveGen.MoveList moves; + if (inCheck) + moves = moveGen.checkEvasions(pos); + else + moves = moveGen.pseudoLegalMoves(pos); + boolean seeDone = false; + boolean hashMoveSelected = true; + if (!selectHashMove(moves, hashMove)) { + scoreMoveList(moves, ply); + seeDone = true; + hashMoveSelected = false; + } + + UndoInfo ui = sti.undoInfo; + boolean haveLegalMoves = false; + int illegalScore = -(MATE0-(ply+1)); + int b = beta; + int bestScore = illegalScore; + int bestMove = -1; + int lmrCount = 0; + for (int mi = 0; mi < moves.size; mi++) { + if ((mi == 1) && !seeDone) { + scoreMoveList(moves, ply, 1); + seeDone = true; + } + if ((mi > 0) || !hashMoveSelected) { + selectBest(moves, mi); + } + Move m = moves.m[mi]; + if (pos.getPiece(m.to) == (pos.whiteMove ? Piece.BKING : Piece.WKING)) { + moveGen.returnMoveList(moves); + int score = MATE0-ply; + if (log != null) log.logNodeEnd(sti.nodeIdx, score, TTEntry.T_EXACT, evalScore, hKey); + return score; // King capture + } + int newCaptureSquare = -1; + boolean isCapture = (pos.getPiece(m.to) != Piece.EMPTY); + boolean isPromotion = (m.promoteTo != Piece.EMPTY); + int sVal = Integer.MIN_VALUE; + // FIXME! Test extending pawn pushes to 7:th rank + boolean mayReduce = (m.score < 53) && (!isCapture || m.score < 0) && !isPromotion; + boolean givesCheck = MoveGen.givesCheck(pos, m); + boolean doFutility = false; + if (futilityPrune && mayReduce && haveLegalMoves) { + if (!givesCheck && !passedPawnPush(pos, m)) + doFutility = true; + } + int score; + if (doFutility) { + score = futilityScore; + } else { + int moveExtend = 0; + if (posExtend == 0) { + final int pV = Evaluate.pV; + if ((m.to == recaptureSquare)) { + if (sVal == Integer.MIN_VALUE) sVal = SEE(m); + int tVal = Evaluate.pieceValue[pos.getPiece(m.to)]; + if (sVal > tVal - pV / 2) + moveExtend = plyScale; + } + if ((moveExtend < plyScale) && isCapture && (pos.wMtrlPawns + pos.bMtrlPawns > pV)) { + // Extend if going into pawn endgame + int capVal = Evaluate.pieceValue[pos.getPiece(m.to)]; + if (pos.whiteMove) { + if ((pos.wMtrl == pos.wMtrlPawns) && (pos.bMtrl - pos.bMtrlPawns == capVal)) + moveExtend = plyScale; + } else { + if ((pos.bMtrl == pos.bMtrlPawns) && (pos.wMtrl - pos.wMtrlPawns == capVal)) + moveExtend = plyScale; + } + } + } + int extend = Math.max(posExtend, moveExtend); + int lmr = 0; + if ((depth >= 3*plyScale) && mayReduce && (extend == 0)) { + if (!givesCheck && !passedPawnPush(pos, m)) { + lmrCount++; + if ((lmrCount > 3) && (depth > 3*plyScale) && !isCapture) { + lmr = 2*plyScale; + } else { + lmr = 1*plyScale; + } + } + } + int newDepth = depth - plyScale + extend - lmr; + if (isCapture && (givesCheck || (depth + extend) > plyScale)) { + // Compute recapture target square, but only if we are not going + // into q-search at the next ply. + int fVal = Evaluate.pieceValue[pos.getPiece(m.from)]; + int tVal = Evaluate.pieceValue[pos.getPiece(m.to)]; + final int pV = Evaluate.pV; + if (Math.abs(tVal - fVal) < pV / 2) { // "Equal" capture + sVal = SEE(m); + if (Math.abs(sVal) < pV / 2) + newCaptureSquare = m.to; + } + } + posHashList[posHashListSize++] = pos.zobristHash(); + pos.makeMove(m, ui); + nodes++; + totalNodes++; + sti.currentMove = m; +/* int nodes0 = nodes; + int qNodes0 = qNodes; + if ((ply < 3) && (newDepth > plyScale)) { + System.out.printf("%2d %5s %5d %5d %6s %6s ", + mi, "-", alpha, beta, "-", "-"); + for (int i = 0; i < ply; i++) + System.out.printf(" "); + System.out.printf("%-6s...\n", TextIO.moveToUCIString(m)); + } */ + sti.lmr = lmr; + score = -negaScout(-b, -alpha, ply + 1, newDepth, newCaptureSquare, givesCheck); + if (((lmr > 0) && (score > alpha)) || + ((score > alpha) && (score < beta) && (b != beta) && (score != illegalScore))) { + sti.lmr = 0; + newDepth += lmr; + score = -negaScout(-beta, -alpha, ply + 1, newDepth, newCaptureSquare, givesCheck); + } +/* if (ply <= 3) { + System.out.printf("%2d %5d %5d %5d %6d %6d ", + mi, score, alpha, beta, nodes-nodes0, qNodes-qNodes0); + for (int i = 0; i < ply; i++) + System.out.printf(" "); + System.out.printf("%-6s\n", TextIO.moveToUCIString(m)); + }*/ + posHashListSize--; + pos.unMakeMove(m, ui); + } + if (weak && haveLegalMoves) + if (weakPlaySkipMove(pos, m, ply)) + score = illegalScore; + m.score = score; + + if (score != illegalScore) { + haveLegalMoves = true; + } + bestScore = Math.max(bestScore, score); + if (score > alpha) { + alpha = score; + bestMove = mi; + sti.bestMove.from = m.from; + sti.bestMove.to = m.to; + sti.bestMove.promoteTo = m.promoteTo; + } + if (alpha >= beta) { + if (pos.getPiece(m.to) == Piece.EMPTY) { + kt.addKiller(ply, m); + ht.addSuccess(pos, m, depth/plyScale); + for (int mi2 = mi - 1; mi2 >= 0; mi2--) { + Move m2 = moves.m[mi2]; + if (pos.getPiece(m2.to) == Piece.EMPTY) + ht.addFail(pos, m2, depth/plyScale); + } + } + tt.insert(hKey, m, TTEntry.T_GE, ply, depth, evalScore); + moveGen.returnMoveList(moves); + if (log != null) log.logNodeEnd(sti.nodeIdx, alpha, TTEntry.T_GE, evalScore, hKey); + return alpha; + } + b = alpha + 1; + } + if (!haveLegalMoves && !inCheck) { + moveGen.returnMoveList(moves); + if (log != null) log.logNodeEnd(sti.nodeIdx, 0, TTEntry.T_EXACT, evalScore, hKey); + return 0; // Stale-mate + } + if (bestMove >= 0) { + tt.insert(hKey, moves.m[bestMove], TTEntry.T_EXACT, ply, depth, evalScore); + if (log != null) log.logNodeEnd(sti.nodeIdx, bestScore, TTEntry.T_EXACT, evalScore, hKey); + } else { + emptyMove.score = bestScore; + tt.insert(hKey, emptyMove, TTEntry.T_LE, ply, depth, evalScore); + if (log != null) log.logNodeEnd(sti.nodeIdx, bestScore, TTEntry.T_LE, evalScore, hKey); + } + moveGen.returnMoveList(moves); + return bestScore; + } + + /** Return true if move should be skipped in order to make engine play weaker. */ + private final boolean weakPlaySkipMove(Position pos, Move m, int ply) { + long rndL = pos.zobristHash() ^ Position.psHashKeys[0][m.from] ^ + Position.psHashKeys[0][m.to] ^ randomSeed; + double rnd = ((rndL & 0x7fffffffffffffffL) % 1000000000) / 1e9; + + double s = strength * 1e-3; + double offs = 4 - 15 * s; + double effPly = ply * Evaluate.interpolate(pos.wMtrl + pos.bMtrl, 0, 30, Evaluate.qV * 4, 100) * 1e-2; + double t = effPly + offs; + double p = 1/(1+Math.exp(t)); // Probability to "see" move + boolean easyMove = ((pos.getPiece(m.to) != Piece.EMPTY) || + (ply < 2) || (searchTreeInfo[ply-2].currentMove.to == m.from)); + if (easyMove) + p = 1-(1-p)*(1-p); + if (rnd > p) + return true; + return false; + } + + private static final boolean passedPawnPush(Position pos, Move m) { + int p = pos.getPiece(m.from); + if (pos.whiteMove) { + if (p != Piece.WPAWN) + return false; + if ((BitBoard.wPawnBlockerMask[m.to] & pos.pieceTypeBB[Piece.BPAWN]) != 0) + return false; + return m.to >= 40; + } else { + if (p != Piece.BPAWN) + return false; + if ((BitBoard.bPawnBlockerMask[m.to] & pos.pieceTypeBB[Piece.WPAWN]) != 0) + return false; + return m.to <= 23; + } + } + + /** + * Quiescence search. Only non-losing captures are searched. + */ + final private int quiesce(int alpha, int beta, int ply, int depth, final boolean inCheck) { + int score; + if (inCheck) { + score = -(MATE0 - (ply+1)); + } else { + if ((depth == 0) && (q0Eval != UNKNOWN_SCORE)) { + score = q0Eval; + } else { + score = eval.evalPos(pos); + if (depth == 0) + q0Eval = score; + } + } + if (score >= beta) { + if ((depth == 0) && (score < MATE0 - ply)) { + if (MoveGen.canTakeKing(pos)) { + // To make stale-mate detection work + score = MATE0 - ply; + } + } + return score; + } + final int evalScore = score; + if (score > alpha) + alpha = score; + int bestScore = score; + final boolean tryChecks = (depth > -3); + MoveGen.MoveList moves; + if (inCheck) { + moves = moveGen.checkEvasions(pos); + } else if (tryChecks) { + moves = moveGen.pseudoLegalCapturesAndChecks(pos); + } else { + moves = moveGen.pseudoLegalCaptures(pos); + } + scoreMoveListMvvLva(moves); + UndoInfo ui = searchTreeInfo[ply].undoInfo; + for (int mi = 0; mi < moves.size; mi++) { + if (mi < 8) { + // If the first 8 moves didn't fail high, this is probably an ALL-node, + // so spending more effort on move ordering is probably wasted time. + selectBest(moves, mi); + } + Move m = moves.m[mi]; + if (pos.getPiece(m.to) == (pos.whiteMove ? Piece.BKING : Piece.WKING)) { + moveGen.returnMoveList(moves); + return MATE0-ply; // King capture + } + boolean givesCheck = false; + boolean givesCheckComputed = false; + if (inCheck) { + // Allow all moves + } else { + if ((pos.getPiece(m.to) == Piece.EMPTY) && (m.promoteTo == Piece.EMPTY)) { + // Non-capture + if (!tryChecks) + continue; + givesCheck = MoveGen.givesCheck(pos, m); + givesCheckComputed = true; + if (!givesCheck) + continue; + if (negSEE(m)) // Needed because m.score is not computed for non-captures + continue; + } else { + if (negSEE(m)) + continue; + int capt = Evaluate.pieceValue[pos.getPiece(m.to)]; + int prom = Evaluate.pieceValue[m.promoteTo]; + int optimisticScore = evalScore + capt + prom + 200; + if (optimisticScore < alpha) { // Delta pruning + if ((pos.wMtrlPawns > 0) && (pos.wMtrl > capt + pos.wMtrlPawns) && + (pos.bMtrlPawns > 0) && (pos.bMtrl > capt + pos.bMtrlPawns)) { + if (depth -1 > -4) { + givesCheck = MoveGen.givesCheck(pos, m); + givesCheckComputed = true; + } + if (!givesCheck) { + if (optimisticScore > bestScore) + bestScore = optimisticScore; + continue; + } + } + } + } + } + + if (!givesCheckComputed) { + if (depth - 1 > -4) { + givesCheck = MoveGen.givesCheck(pos, m); + } + } + final boolean nextInCheck = (depth - 1) > -4 ? givesCheck : false; + + pos.makeMove(m, ui); + qNodes++; + totalNodes++; + score = -quiesce(-beta, -alpha, ply + 1, depth - 1, nextInCheck); + pos.unMakeMove(m, ui); + if (score > bestScore) { + bestScore = score; + if (score > alpha) { + alpha = score; + if (alpha >= beta) { + moveGen.returnMoveList(moves); + return alpha; + } + } + } + } + moveGen.returnMoveList(moves); + return bestScore; + } + + /** Return >0, 0, <0, depending on the sign of SEE(m). */ + final public int signSEE(Move m) { + int p0 = Evaluate.pieceValue[pos.getPiece(m.from)]; + int p1 = Evaluate.pieceValue[pos.getPiece(m.to)]; + if (p0 < p1) + return 1; + return SEE(m); + } + + /** Return true if SEE(m) < 0. */ + final public boolean negSEE(Move m) { + int p0 = Evaluate.pieceValue[pos.getPiece(m.from)]; + int p1 = Evaluate.pieceValue[pos.getPiece(m.to)]; + if (p1 >= p0) + return false; + return SEE(m) < 0; + } + + private int[] captures = new int[64]; // Value of captured pieces + private UndoInfo seeUi = new UndoInfo(); + + /** + * Static exchange evaluation function. + * @return SEE score for m. Positive value is good for the side that makes the first move. + */ + final public int SEE(Move m) { + final int kV = Evaluate.kV; + + final int square = m.to; + if (square == pos.getEpSquare()) { + captures[0] = Evaluate.pV; + } else { + captures[0] = Evaluate.pieceValue[pos.getPiece(square)]; + if (captures[0] == kV) + return kV; + } + int nCapt = 1; // Number of entries in captures[] + + pos.makeSEEMove(m, seeUi); + boolean white = pos.whiteMove; + int valOnSquare = Evaluate.pieceValue[pos.getPiece(square)]; + long occupied = pos.whiteBB | pos.blackBB; + while (true) { + int bestValue = Integer.MAX_VALUE; + long atk; + if (white) { + atk = BitBoard.bPawnAttacks[square] & pos.pieceTypeBB[Piece.WPAWN] & occupied; + if (atk != 0) { + bestValue = Evaluate.pV; + } else { + atk = BitBoard.knightAttacks[square] & pos.pieceTypeBB[Piece.WKNIGHT] & occupied; + if (atk != 0) { + bestValue = Evaluate.nV; + } else { + long bAtk = BitBoard.bishopAttacks(square, occupied) & occupied; + atk = bAtk & pos.pieceTypeBB[Piece.WBISHOP]; + if (atk != 0) { + bestValue = Evaluate.bV; + } else { + long rAtk = BitBoard.rookAttacks(square, occupied) & occupied; + atk = rAtk & pos.pieceTypeBB[Piece.WROOK]; + if (atk != 0) { + bestValue = Evaluate.rV; + } else { + atk = (bAtk | rAtk) & pos.pieceTypeBB[Piece.WQUEEN]; + if (atk != 0) { + bestValue = Evaluate.qV; + } else { + atk = BitBoard.kingAttacks[square] & pos.pieceTypeBB[Piece.WKING] & occupied; + if (atk != 0) { + bestValue = kV; + } else { + break; + } + } + } + } + } + } + } else { + atk = BitBoard.wPawnAttacks[square] & pos.pieceTypeBB[Piece.BPAWN] & occupied; + if (atk != 0) { + bestValue = Evaluate.pV; + } else { + atk = BitBoard.knightAttacks[square] & pos.pieceTypeBB[Piece.BKNIGHT] & occupied; + if (atk != 0) { + bestValue = Evaluate.nV; + } else { + long bAtk = BitBoard.bishopAttacks(square, occupied) & occupied; + atk = bAtk & pos.pieceTypeBB[Piece.BBISHOP]; + if (atk != 0) { + bestValue = Evaluate.bV; + } else { + long rAtk = BitBoard.rookAttacks(square, occupied) & occupied; + atk = rAtk & pos.pieceTypeBB[Piece.BROOK]; + if (atk != 0) { + bestValue = Evaluate.rV; + } else { + atk = (bAtk | rAtk) & pos.pieceTypeBB[Piece.BQUEEN]; + if (atk != 0) { + bestValue = Evaluate.qV; + } else { + atk = BitBoard.kingAttacks[square] & pos.pieceTypeBB[Piece.BKING] & occupied; + if (atk != 0) { + bestValue = kV; + } else { + break; + } + } + } + } + } + } + } + captures[nCapt++] = valOnSquare; + if (valOnSquare == kV) + break; + valOnSquare = bestValue; + occupied &= ~(atk & -atk); + white = !white; + } + pos.unMakeSEEMove(m, seeUi); + + int score = 0; + for (int i = nCapt - 1; i > 0; i--) { + score = Math.max(0, captures[i] - score); + } + return captures[0] - score; + } + + /** + * Compute scores for each move in a move list, using SEE, killer and history information. + * @param moves List of moves to score. + */ + final void scoreMoveList(MoveGen.MoveList moves, int ply) { + scoreMoveList(moves, ply, 0); + } + final void scoreMoveList(MoveGen.MoveList moves, int ply, int startIdx) { + for (int i = startIdx; i < moves.size; i++) { + Move m = moves.m[i]; + boolean isCapture = (pos.getPiece(m.to) != Piece.EMPTY) || (m.promoteTo != Piece.EMPTY); + int score = 0; + if (isCapture) { + int seeScore = isCapture ? signSEE(m) : 0; + int v = pos.getPiece(m.to); + int a = pos.getPiece(m.from); + score = Evaluate.pieceValue[v]/10 * 1000 - Evaluate.pieceValue[a]/10; + if (seeScore > 0) + score += 2000000; + else if (seeScore == 0) + score += 1000000; + else + score -= 1000000; + score *= 100; + } + int ks = kt.getKillerScore(ply, m); + if (ks > 0) { + score += ks + 50; + } else { + int hs = ht.getHistScore(pos, m); + score += hs; + } + m.score = score; + } + } + private final void scoreMoveListMvvLva(MoveGen.MoveList moves) { + for (int i = 0; i < moves.size; i++) { + Move m = moves.m[i]; + int v = pos.getPiece(m.to); + int a = pos.getPiece(m.from); + m.score = Evaluate.pieceValue[v] * 10000 - Evaluate.pieceValue[a]; + } + } + + /** + * Find move with highest score and move it to the front of the list. + */ + final static void selectBest(MoveGen.MoveList moves, int startIdx) { + int bestIdx = startIdx; + int bestScore = moves.m[bestIdx].score; + for (int i = startIdx + 1; i < moves.size; i++) { + int sc = moves.m[i].score; + if (sc > bestScore) { + bestIdx = i; + bestScore = sc; + } + } + if (bestIdx != startIdx) { + Move m = moves.m[startIdx]; + moves.m[startIdx] = moves.m[bestIdx]; + moves.m[bestIdx] = m; + } + } + + /** If hashMove exists in the move list, move the hash move to the front of the list. */ + final static boolean selectHashMove(MoveGen.MoveList moves, Move hashMove) { + if (hashMove == null) { + return false; + } + for (int i = 0; i < moves.size; i++) { + Move m = moves.m[i]; + if (m.equals(hashMove)) { + moves.m[i] = moves.m[0]; + moves.m[0] = m; + m.score = 10000; + return true; + } + } + return false; + } + + public final static boolean canClaimDraw50(Position pos) { + return (pos.halfMoveClock >= 100); + } + + public final static boolean canClaimDrawRep(Position pos, long[] posHashList, int posHashListSize, int posHashFirstNew) { + int reps = 0; + for (int i = posHashListSize - 4; i >= 0; i -= 2) { + if (pos.zobristHash() == posHashList[i]) { + reps++; + if (i >= posHashFirstNew) { + reps++; + break; + } + } + } + return (reps >= 2); + } + + private final void initNodeStats() { + nodes = qNodes = 0; + nodesPlyVec = new int[20]; + nodesDepthVec = new int[20]; + for (int i = 0; i < 20; i++) { + nodesPlyVec[i] = 0; + nodesDepthVec[i] = 0; + } + } +} diff --git a/CuckooChessEngine/src/chess/TextIO.java b/CuckooChessEngine/src/chess/TextIO.java new file mode 100644 index 0000000..402856d --- /dev/null +++ b/CuckooChessEngine/src/chess/TextIO.java @@ -0,0 +1,612 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +/** + * + * @author petero + */ +public class TextIO { + static public final String startPosFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + + /** Parse a FEN string and return a chess Position object. */ + public static final Position readFEN(String fen) throws ChessParseError { + Position pos = new Position(); + String[] words = fen.split(" "); + if (words.length < 2) { + throw new ChessParseError("Too few spaces"); + } + + // Piece placement + int row = 7; + int col = 0; + for (int i = 0; i < words[0].length(); i++) { + char c = words[0].charAt(i); + switch (c) { + case '1': col += 1; break; + case '2': col += 2; break; + case '3': col += 3; break; + case '4': col += 4; break; + case '5': col += 5; break; + case '6': col += 6; break; + case '7': col += 7; break; + case '8': col += 8; break; + case '/': row--; col = 0; break; + case 'P': safeSetPiece(pos, col, row, Piece.WPAWN); col++; break; + case 'N': safeSetPiece(pos, col, row, Piece.WKNIGHT); col++; break; + case 'B': safeSetPiece(pos, col, row, Piece.WBISHOP); col++; break; + case 'R': safeSetPiece(pos, col, row, Piece.WROOK); col++; break; + case 'Q': safeSetPiece(pos, col, row, Piece.WQUEEN); col++; break; + case 'K': safeSetPiece(pos, col, row, Piece.WKING); col++; break; + case 'p': safeSetPiece(pos, col, row, Piece.BPAWN); col++; break; + case 'n': safeSetPiece(pos, col, row, Piece.BKNIGHT); col++; break; + case 'b': safeSetPiece(pos, col, row, Piece.BBISHOP); col++; break; + case 'r': safeSetPiece(pos, col, row, Piece.BROOK); col++; break; + case 'q': safeSetPiece(pos, col, row, Piece.BQUEEN); col++; break; + case 'k': safeSetPiece(pos, col, row, Piece.BKING); col++; break; + default: throw new ChessParseError("Invalid piece"); + } + } + if (words[1].length() == 0) { + throw new ChessParseError("Invalid side"); + } + pos.setWhiteMove(words[1].charAt(0) == 'w'); + + // Castling rights + int castleMask = 0; + if (words.length > 2) { + for (int i = 0; i < words[2].length(); i++) { + char c = words[2].charAt(i); + switch (c) { + case 'K': + castleMask |= (1 << Position.H1_CASTLE); + break; + case 'Q': + castleMask |= (1 << Position.A1_CASTLE); + break; + case 'k': + castleMask |= (1 << Position.H8_CASTLE); + break; + case 'q': + castleMask |= (1 << Position.A8_CASTLE); + break; + case '-': + break; + default: + throw new ChessParseError("Invalid castling flags"); + } + } + } + pos.setCastleMask(castleMask); + + if (words.length > 3) { + // En passant target square + String epString = words[3]; + if (!epString.equals("-")) { + if (epString.length() < 2) { + throw new ChessParseError("Invalid en passant square"); + } + pos.setEpSquare(getSquare(epString)); + } + } + + try { + if (words.length > 4) { + pos.halfMoveClock = Integer.parseInt(words[4]); + } + if (words.length > 5) { + pos.fullMoveCounter = Integer.parseInt(words[5]); + } + } catch (NumberFormatException nfe) { + // Ignore errors here, since the fields are optional + } + + // Each side must have exactly one king + int wKings = 0; + int bKings = 0; + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + int p = pos.getPiece(Position.getSquare(x, y)); + if (p == Piece.WKING) { + wKings++; + } else if (p == Piece.BKING) { + bKings++; + } + } + } + if (wKings != 1) { + throw new ChessParseError("White must have exactly one king"); + } + if (bKings != 1) { + throw new ChessParseError("Black must have exactly one king"); + } + + // Make sure king can not be captured + Position pos2 = new Position(pos); + pos2.setWhiteMove(!pos.whiteMove); + if (MoveGen.inCheck(pos2)) { + throw new ChessParseError("King capture possible"); + } + + fixupEPSquare(pos); + + return pos; + } + + /** Remove pseudo-legal EP square if it is not legal, ie would leave king in check. */ + public static final void fixupEPSquare(Position pos) { + int epSquare = pos.getEpSquare(); + if (epSquare >= 0) { + MoveGen.MoveList moves = MoveGen.instance.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + boolean epValid = false; + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + if (m.to == epSquare) { + if (pos.getPiece(m.from) == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) { + epValid = true; + break; + } + } + } + if (!epValid) { + pos.setEpSquare(-1); + } + } + } + + private static final void safeSetPiece(Position pos, int col, int row, int p) throws ChessParseError { + if (row < 0) throw new ChessParseError("Too many rows"); + if (col > 7) throw new ChessParseError("Too many columns"); + if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) { + if ((row == 0) || (row == 7)) + throw new ChessParseError("Pawn on first/last rank"); + } + pos.setPiece(Position.getSquare(col, row), p); + } + + /** Return a FEN string corresponding to a chess Position object. */ + public static final String toFEN(Position pos) { + StringBuilder ret = new StringBuilder(); + // Piece placement + for (int r = 7; r >=0; r--) { + int numEmpty = 0; + for (int c = 0; c < 8; c++) { + int p = pos.getPiece(Position.getSquare(c, r)); + if (p == Piece.EMPTY) { + numEmpty++; + } else { + if (numEmpty > 0) { + ret.append(numEmpty); + numEmpty = 0; + } + switch (p) { + case Piece.WKING: ret.append('K'); break; + case Piece.WQUEEN: ret.append('Q'); break; + case Piece.WROOK: ret.append('R'); break; + case Piece.WBISHOP: ret.append('B'); break; + case Piece.WKNIGHT: ret.append('N'); break; + case Piece.WPAWN: ret.append('P'); break; + case Piece.BKING: ret.append('k'); break; + case Piece.BQUEEN: ret.append('q'); break; + case Piece.BROOK: ret.append('r'); break; + case Piece.BBISHOP: ret.append('b'); break; + case Piece.BKNIGHT: ret.append('n'); break; + case Piece.BPAWN: ret.append('p'); break; + default: throw new RuntimeException(); + } + } + } + if (numEmpty > 0) { + ret.append(numEmpty); + } + if (r > 0) { + ret.append('/'); + } + } + ret.append(pos.whiteMove ? " w " : " b "); + + // Castling rights + boolean anyCastle = false; + if (pos.h1Castle()) { + ret.append('K'); + anyCastle = true; + } + if (pos.a1Castle()) { + ret.append('Q'); + anyCastle = true; + } + if (pos.h8Castle()) { + ret.append('k'); + anyCastle = true; + } + if (pos.a8Castle()) { + ret.append('q'); + anyCastle = true; + } + if (!anyCastle) { + ret.append('-'); + } + + // En passant target square + { + ret.append(' '); + if (pos.getEpSquare() >= 0) { + int x = Position.getX(pos.getEpSquare()); + int y = Position.getY(pos.getEpSquare()); + ret.append((char)(x + 'a')); + ret.append((char)(y + '1')); + } else { + ret.append('-'); + } + } + + // Move counters + ret.append(' '); + ret.append(pos.halfMoveClock); + ret.append(' '); + ret.append(pos.fullMoveCounter); + + return ret.toString(); + } + + /** + * Convert a chess move to human readable form. + * @param pos The chess position. + * @param move The executed move. + * @param longForm If true, use long notation, eg Ng1-f3. + * Otherwise, use short notation, eg Nf3 + */ + public static final String moveToString(Position pos, Move move, boolean longForm) { + MoveGen.MoveList moves = MoveGen.instance.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + return moveToString(pos, move, longForm, moves); + } + private static final String moveToString(Position pos, Move move, boolean longForm, MoveGen.MoveList moves) { + StringBuilder ret = new StringBuilder(); + int wKingOrigPos = Position.getSquare(4, 0); + int bKingOrigPos = Position.getSquare(4, 7); + if (move.from == wKingOrigPos && pos.getPiece(wKingOrigPos) == Piece.WKING) { + // Check white castle + if (move.to == Position.getSquare(6, 0)) { + ret.append("O-O"); + } else if (move.to == Position.getSquare(2, 0)) { + ret.append("O-O-O"); + } + } else if (move.from == bKingOrigPos && pos.getPiece(bKingOrigPos) == Piece.BKING) { + // Check white castle + if (move.to == Position.getSquare(6, 7)) { + ret.append("O-O"); + } else if (move.to == Position.getSquare(2, 7)) { + ret.append("O-O-O"); + } + } + if (ret.length() == 0) { + int p = pos.getPiece(move.from); + ret.append(pieceToChar(p)); + int x1 = Position.getX(move.from); + int y1 = Position.getY(move.from); + int x2 = Position.getX(move.to); + int y2 = Position.getY(move.to); + if (longForm) { + ret.append((char)(x1 + 'a')); + ret.append((char) (y1 + '1')); + ret.append(isCapture(pos, move) ? 'x' : '-'); + } else { + if (p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) { + if (isCapture(pos, move)) { + ret.append((char) (x1 + 'a')); + } + } else { + int numSameTarget = 0; + int numSameFile = 0; + int numSameRow = 0; + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + if (m == null) + break; + if ((pos.getPiece(m.from) == p) && (m.to == move.to)) { + numSameTarget++; + if (Position.getX(m.from) == x1) + numSameFile++; + if (Position.getY(m.from) == y1) + numSameRow++; + } + } + if (numSameTarget < 2) { + // No file/row info needed + } else if (numSameFile < 2) { + ret.append((char) (x1 + 'a')); // Only file info needed + } else if (numSameRow < 2) { + ret.append((char) (y1 + '1')); // Only row info needed + } else { + ret.append((char) (x1 + 'a')); // File and row info needed + ret.append((char) (y1 + '1')); + } + } + if (isCapture(pos, move)) { + ret.append('x'); + } + } + ret.append((char) (x2 + 'a')); + ret.append((char) (y2 + '1')); + if (move.promoteTo != Piece.EMPTY) { + ret.append(pieceToChar(move.promoteTo)); + } + } + UndoInfo ui = new UndoInfo(); + if (MoveGen.givesCheck(pos, move)) { + pos.makeMove(move, ui); + MoveGen.MoveList nextMoves = MoveGen.instance.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, nextMoves); + if (nextMoves.size == 0) { + ret.append('#'); + } else { + ret.append('+'); + } + pos.unMakeMove(move, ui); + } + + return ret.toString(); + } + + /** Convert a move object to UCI string format. */ + public static final String moveToUCIString(Move m) { + String ret = squareToString(m.from); + ret += squareToString(m.to); + switch (m.promoteTo) { + case Piece.WQUEEN: + case Piece.BQUEEN: + ret += "q"; + break; + case Piece.WROOK: + case Piece.BROOK: + ret += "r"; + break; + case Piece.WBISHOP: + case Piece.BBISHOP: + ret += "b"; + break; + case Piece.WKNIGHT: + case Piece.BKNIGHT: + ret += "n"; + break; + default: + break; + } + return ret; + } + + /** + * Convert a string to a Move object. + * @return A move object, or null if move has invalid syntax + */ + public static final Move uciStringToMove(String move) { + Move m = null; + if ((move.length() < 4) || (move.length() > 5)) + return m; + int fromSq = TextIO.getSquare(move.substring(0, 2)); + int toSq = TextIO.getSquare(move.substring(2, 4)); + if ((fromSq < 0) || (toSq < 0)) { + return m; + } + char prom = ' '; + boolean white = true; + if (move.length() == 5) { + prom = move.charAt(4); + if (Position.getY(toSq) == 7) { + white = true; + } else if (Position.getY(toSq) == 0) { + white = false; + } else { + return m; + } + } + int promoteTo; + switch (prom) { + case ' ': + promoteTo = Piece.EMPTY; + break; + case 'q': + promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN; + break; + case 'r': + promoteTo = white ? Piece.WROOK : Piece.BROOK; + break; + case 'b': + promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP; + break; + case 'n': + promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT; + break; + default: + return m; + } + m = new Move(fromSq, toSq, promoteTo); + return m; + } + + private static final boolean isCapture(Position pos, Move move) { + if (pos.getPiece(move.to) == Piece.EMPTY) { + int p = pos.getPiece(move.from); + if ((p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) && (move.to == pos.getEpSquare())) { + return true; + } else { + return false; + } + } else { + return true; + } + } + + /** + * Convert a chess move string to a Move object. + * Any prefix of the string representation of a valid move counts as a legal move string, + * as long as the string only matches one valid move. + */ + public static final Move stringToMove(Position pos, String strMove) { + strMove = strMove.replaceAll("=", ""); + Move move = null; + if (strMove.length() == 0) + return move; + MoveGen.MoveList moves = MoveGen.instance.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + { + char lastChar = strMove.charAt(strMove.length() - 1); + if ((lastChar == '#') || (lastChar == '+')) { + MoveGen.MoveList subMoves = new MoveGen.MoveList(); + int len = 0; + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + String str1 = TextIO.moveToString(pos, m, true, moves); + if (str1.charAt(str1.length() - 1) == lastChar) { + subMoves.m[len++] = m; + } + } + subMoves.size = len; + moves = subMoves; + strMove = normalizeMoveString(strMove); + } + } + + for (int i = 0; i < 2; i++) { + // Search for full match + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + String str1 = normalizeMoveString(TextIO.moveToString(pos, m, true, moves)); + String str2 = normalizeMoveString(TextIO.moveToString(pos, m, false, moves)); + if (i == 0) { + if (strMove.equals(str1) || strMove.equals(str2)) { + return m; + } + } else { + if (strMove.toLowerCase().equals(str1.toLowerCase()) || + strMove.toLowerCase().equals(str2.toLowerCase())) { + return m; + } + } + } + } + + for (int i = 0; i < 2; i++) { + // Search for unique substring match + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + String str1 = normalizeMoveString(TextIO.moveToString(pos, m, true)); + String str2 = normalizeMoveString(TextIO.moveToString(pos, m, false)); + boolean match; + if (i == 0) { + match = (str1.startsWith(strMove) || str2.startsWith(strMove)); + } else { + match = (str1.toLowerCase().startsWith(strMove.toLowerCase()) || + str2.toLowerCase().startsWith(strMove.toLowerCase())); + } + if (match) { + if (move != null) { + return null; // More than one match, not ok + } else { + move = m; + } + } + } + if (move != null) + return move; + } + return move; + } + + /** + * Convert a string, such as "e4" to a square number. + * @return The square number, or -1 if not a legal square. + */ + public static final int getSquare(String s) { + int x = s.charAt(0) - 'a'; + int y = s.charAt(1) - '1'; + if ((x < 0) || (x > 7) || (y < 0) || (y > 7)) + return -1; + return Position.getSquare(x, y); + } + + /** + * Convert a square number to a string, such as "e4". + */ + public static final String squareToString(int square) { + StringBuilder ret = new StringBuilder(); + int x = Position.getX(square); + int y = Position.getY(square); + ret.append((char) (x + 'a')); + ret.append((char) (y + '1')); + return ret.toString(); + } + + /** + * Create an ascii representation of a position. + */ + public static final String asciiBoard(Position pos) { + StringBuilder ret = new StringBuilder(400); + String nl = String.format("%n"); + ret.append(" +----+----+----+----+----+----+----+----+"); ret.append(nl); + for (int y = 7; y >= 0; y--) { + ret.append(" |"); + for (int x = 0; x < 8; x++) { + ret.append(' '); + int p = pos.getPiece(Position.getSquare(x, y)); + if (p == Piece.EMPTY) { + boolean dark = Position.darkSquare(x, y); + ret.append(dark ? ".. |" : " |"); + } else { + ret.append(Piece.isWhite(p) ? ' ' : '*'); + String pieceName = pieceToChar(p); + if (pieceName.length() == 0) + pieceName = "P"; + ret.append(pieceName); + ret.append(" |"); + } + } + ret.append(nl); + ret.append(" +----+----+----+----+----+----+----+----+"); + ret.append(nl); + } + return ret.toString(); + } + + /** + * Convert move string to lower case and remove special check/mate symbols. + */ + private static final String normalizeMoveString(String str) { + if (str.length() > 0) { + char lastChar = str.charAt(str.length() - 1); + if ((lastChar == '#') || (lastChar == '+')) { + str = str.substring(0, str.length() - 1); + } + } + return str; + } + + private final static String pieceToChar(int p) { + switch (p) { + case Piece.WQUEEN: case Piece.BQUEEN: return "Q"; + case Piece.WROOK: case Piece.BROOK: return "R"; + case Piece.WBISHOP: case Piece.BBISHOP: return "B"; + case Piece.WKNIGHT: case Piece.BKNIGHT: return "N"; + case Piece.WKING: case Piece.BKING: return "K"; + } + return ""; + } +} diff --git a/CuckooChessEngine/src/chess/TranspositionTable.java b/CuckooChessEngine/src/chess/TranspositionTable.java new file mode 100644 index 0000000..a2f384c --- /dev/null +++ b/CuckooChessEngine/src/chess/TranspositionTable.java @@ -0,0 +1,343 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author petero + */ +public class TranspositionTable { + static final public class TTEntry { + long key; // Zobrist hash key + private short move; // from + (to<<6) + (promote<<12) + private short score; // Score from search + private short depthSlot; // Search depth (bit 0-14) and hash slot (bit 15). + byte generation; // Increase when OTB position changes + public byte type; // exact score, lower bound, upper bound + short evalScore; // Score from static evaluation + // FIXME!!! Test storing both upper and lower bound in each hash entry. + + static public final int T_EXACT = 0; // Exact score + static public final int T_GE = 1; // True score >= this.score + static public final int T_LE = 2; // True score <= this.score + static public final int T_EMPTY = 3; // Empty hash slot + + /** Return true if this object is more valuable than the other, false otherwise. */ + public final boolean betterThan(TTEntry other, int currGen) { + if ((generation == currGen) != (other.generation == currGen)) { + return generation == currGen; // Old entries are less valuable + } + if ((type == T_EXACT) != (other.type == T_EXACT)) { + return type == T_EXACT; // Exact score more valuable than lower/upper bound + } + if (getDepth() != other.getDepth()) { + return getDepth() > other.getDepth(); // Larger depth is more valuable + } + return false; // Otherwise, pretty much equally valuable + } + + /** Return true if entry is good enough to spend extra time trying to avoid overwriting it. */ + public final boolean valuable(int currGen) { + if (generation != currGen) + return false; + return (type == T_EXACT) || (getDepth() > 3 * Search.plyScale); + } + + public final void getMove(Move m) { + m.from = move & 63; + m.to = (move >> 6) & 63; + m.promoteTo = (move >> 12) & 15; + } + public final void setMove(Move move) { + this.move = (short)(move.from + (move.to << 6) + (move.promoteTo << 12)); + } + + /** Get the score from the hash entry, and convert from "mate in x" to "mate at ply". */ + public final int getScore(int ply) { + int sc = score; + if (sc > Search.MATE0 - 1000) { + sc -= ply; + } else if (sc < -(Search.MATE0 - 1000)) { + sc += ply; + } + return sc; + } + + /** Convert score from "mate at ply" to "mate in x", and store in hash entry. */ + public final void setScore(int score, int ply) { + if (score > Search.MATE0 - 1000) { + score += ply; + } else if (score < -(Search.MATE0 - 1000)) { + score -= ply; + } + this.score = (short)score; + } + + /** Get depth from the hash entry. */ + public final int getDepth() { + return depthSlot & 0x7fff; + } + + /** Set depth. */ + public final void setDepth(int d) { + depthSlot &= 0x8000; + depthSlot |= ((short)d) & 0x7fff; + } + + final int getHashSlot() { + return depthSlot >>> 15; + } + + public final void setHashSlot(int s) { + depthSlot &= 0x7fff; + depthSlot |= (s << 15); + } + } + TTEntry[] table; + TTEntry emptySlot; + byte generation; + + /** Constructor. Creates an empty transposition table with numEntries slots. */ + public TranspositionTable(int log2Size) { + final int numEntries = (1 << log2Size); + table = new TTEntry[numEntries]; + for (int i = 0; i < numEntries; i++) { + TTEntry ent = new TTEntry(); + ent.key = 0; + ent.depthSlot = 0; + ent.type = TTEntry.T_EMPTY; + table[i] = ent; + } + emptySlot = new TTEntry(); + emptySlot.type = TTEntry.T_EMPTY; + generation = 0; + } + + public final void insert(long key, Move sm, int type, int ply, int depth, int evalScore) { + if (depth < 0) depth = 0; + int idx0 = h0(key); + int idx1 = h1(key); + TTEntry ent = table[idx0]; + byte hashSlot = 0; + if (ent.key != key) { + ent = table[idx1]; + hashSlot = 1; + } + if (ent.key != key) { + if (table[idx1].betterThan(table[idx0], generation)) { + ent = table[idx0]; + hashSlot = 0; + } + if (ent.valuable(generation)) { + int altEntIdx = (ent.getHashSlot() == 0) ? h1(ent.key) : h0(ent.key); + if (ent.betterThan(table[altEntIdx], generation)) { + TTEntry altEnt = table[altEntIdx]; + altEnt.key = ent.key; + altEnt.move = ent.move; + altEnt.score = ent.score; + altEnt.depthSlot = ent.depthSlot; + altEnt.generation = (byte)ent.generation; + altEnt.type = ent.type; + altEnt.setHashSlot(1 - ent.getHashSlot()); + altEnt.evalScore = ent.evalScore; + } + } + } + boolean doStore = true; + if ((ent.key == key) && (ent.getDepth() > depth) && (ent.type == type)) { + if (type == TTEntry.T_EXACT) { + doStore = false; + } else if ((type == TTEntry.T_GE) && (sm.score <= ent.score)) { + doStore = false; + } else if ((type == TTEntry.T_LE) && (sm.score >= ent.score)) { + doStore = false; + } + } + if (doStore) { + if ((ent.key != key) || (sm.from != sm.to)) + ent.setMove(sm); + ent.key = key; + ent.setScore(sm.score, ply); + ent.setDepth(depth); + ent.generation = (byte)generation; + ent.type = (byte)type; + ent.setHashSlot(hashSlot); + ent.evalScore = (short)evalScore; + } + } + + /** Retrieve an entry from the hash table corresponding to "pos". */ + public final TTEntry probe(long key) { + int idx0 = h0(key); + TTEntry ent = table[idx0]; + if (ent.key == key) { + ent.generation = (byte)generation; + return ent; + } + int idx1 = h1(key); + ent = table[idx1]; + if (ent.key == key) { + ent.generation = (byte)generation; + return ent; + } + return emptySlot; + } + + /** + * Increase hash table generation. This means that subsequent inserts will be considered + * more valuable than the entries currently present in the hash table. + */ + public final void nextGeneration() { + generation++; + } + + /** Clear the transposition table. */ + public final void clear() { + for (TTEntry ent : table) { + ent.type = TTEntry.T_EMPTY; + } + } + + /** + * Extract a list of PV moves, starting from "rootPos" and first move "m". + */ + public final ArrayList extractPVMoves(Position rootPos, Move m) { + Position pos = new Position(rootPos); + m = new Move(m); + ArrayList ret = new ArrayList(); + UndoInfo ui = new UndoInfo(); + List hashHistory = new ArrayList(); + MoveGen moveGen = new MoveGen(); + while (true) { + ret.add(m); + pos.makeMove(m, ui); + if (hashHistory.contains(pos.zobristHash())) { + break; + } + hashHistory.add(pos.zobristHash()); + TTEntry ent = probe(pos.historyHash()); + if (ent.type == TTEntry.T_EMPTY) { + break; + } + m = new Move(0,0,0); + ent.getMove(m); + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + boolean contains = false; + for (int mi = 0; mi < moves.size; mi++) + if (moves.m[mi].equals(m)) { + contains = true; + break; + } + if (!contains) + break; + } + return ret; + } + + /** Extract the PV starting from pos, using hash entries, both exact scores and bounds. */ + public final String extractPV(Position pos) { + StringBuilder ret = new StringBuilder(100); + pos = new Position(pos); // To avoid modifying the input parameter + boolean first = true; + TTEntry ent = probe(pos.historyHash()); + UndoInfo ui = new UndoInfo(); + ArrayList hashHistory = new ArrayList(); + boolean repetition = false; + MoveGen moveGen = MoveGen.instance; + while (ent.type != TTEntry.T_EMPTY) { + String type = ""; + if (ent.type == TTEntry.T_LE) { + type = "<"; + } else if (ent.type == TTEntry.T_GE) { + type = ">"; + } + Move m = new Move(0,0,0); + ent.getMove(m); + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + boolean contains = false; + for (int mi = 0; mi < moves.size; mi++) + if (moves.m[mi].equals(m)) { + contains = true; + break; + } + if (!contains) + break; + String moveStr = TextIO.moveToString(pos, m, false); + if (repetition) + break; + if (!first) { + ret.append(" "); + } + ret.append(type); + ret.append(moveStr); + pos.makeMove(m, ui); + if (hashHistory.contains(pos.zobristHash())) { + repetition = true; + } + hashHistory.add(pos.zobristHash()); + ent = probe(pos.historyHash()); + first = false; + } + return ret.toString(); + } + + /** Print hash table statistics. */ + public final void printStats() { + int unused = 0; + int thisGen = 0; + List depHist = new ArrayList(); + final int maxDepth = 20*8; + for (int i = 0; i < maxDepth; i++) { + depHist.add(0); + } + for (TTEntry ent : table) { + if (ent.type == TTEntry.T_EMPTY) { + unused++; + } else { + if (ent.generation == generation) { + thisGen++; + } + if (ent.getDepth() < maxDepth) { + depHist.set(ent.getDepth(), depHist.get(ent.getDepth()) + 1); + } + } + } + double w = 100.0 / table.length; + System.out.printf("Hash stats: size:%d unused:%d (%.2f%%) thisGen:%d (%.2f%%)\n", + table.length, unused, unused*w, thisGen, thisGen*w); + for (int i = 0; i < maxDepth; i++) { + int c = depHist.get(i); + if (c > 0) + System.out.printf("%3d %8d (%6.2f%%)\n", i, c, c*w); + } + } + + private final int h0(long key) { + return (int)(key & (table.length - 1)); + } + + private final int h1(long key) { + return (int)((key >> 32) & (table.length - 1)); + } +} diff --git a/CuckooChessEngine/src/chess/TreeLogger.java b/CuckooChessEngine/src/chess/TreeLogger.java new file mode 100644 index 0000000..c36cb89 --- /dev/null +++ b/CuckooChessEngine/src/chess/TreeLogger.java @@ -0,0 +1,617 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.util.ArrayList; +import java.util.Collections; + +import chess.TranspositionTable.TTEntry; + +public final class TreeLogger { + private byte[] entryBuffer = new byte[16]; + private ByteBuffer bb = ByteBuffer.wrap(entryBuffer); + + // Used in write mode + private FileOutputStream os = null; + private BufferedOutputStream bos = null; + private long nextIndex = 0; + + // Used in analyze mode + private MappedByteBuffer mapBuf = null; + private FileChannel fc = null; + private int numEntries = 0; + + private TreeLogger() { + } + + /** Get a logger object set up for writing to a log file. */ + public static final TreeLogger getWriter(String filename, Position pos) { + try { + TreeLogger log = new TreeLogger(); + log.os = new FileOutputStream(filename); + log.bos = new BufferedOutputStream(log.os, 65536); + log.writeHeader(pos); + log.nextIndex = 0; + return log; + } catch (FileNotFoundException e) { + throw new RuntimeException(); + } + } + + private final void writeHeader(Position pos) { + try { + byte[] fen = TextIO.toFEN(pos).getBytes(); + bos.write((byte)(fen.length)); + bos.write(fen); + byte[] pad = new byte[128-1-fen.length]; + for (int i = 0; i < pad.length; i++) + pad[i] = 0; + bos.write(pad); + } catch (IOException e) { + throw new RuntimeException(); + } + } + + /** Get a logger object set up for analyzing a log file. */ + public static final TreeLogger getAnalyzer(String filename) { + try { + TreeLogger log = new TreeLogger(); + RandomAccessFile raf; + raf = new RandomAccessFile(filename, "rw"); + log.fc = raf.getChannel(); + long len = raf.length(); + log.numEntries = (int) ((len - 128) / 16); + log.mapBuf = log.fc.map(MapMode.READ_WRITE, 0, len); + log.computeForwardPointers(); + return log; + } catch (FileNotFoundException e) { + throw new RuntimeException(); + } catch (IOException e) { + throw new RuntimeException(); + } + } + + public final void close() { + try { + if (bos != null) bos.close(); + if (fc != null) fc.close(); + } catch (IOException e) { + } + } + + /* This is the on-disk format. Big-endian byte-order is used. + * First there is one header entry. Then there is a set of start/end entries. + * A StartEntry can be identified by its first 4 bytes (endIndex/startIndex) + * being either -1 (endIndex not computed), or > the entry index. + * + * private static final class Header { + * byte fenLen; // Used length of fen array + * byte[] fen; // 126 bytes, 0-padded + * byte flags; // bit 0: 1 if endIndex has been computed for all StartEntries. + * } + * + * private static final class StartEntry { + * int endIndex; + * int parentIndex; // -1 for root node + * short move; + * short alpha; + * short beta; + * byte ply; + * byte depth; + * } + * + * private static final class EndEntry { + * int startIndex; + * short score; + * short scoreType; + * short evalScore; + * byte[] hashKey; // lower 6 bytes of position hash key + * } + */ + + // ---------------------------------------------------------------------------- + // Functions used for tree logging + + /** + * Log information when entering a search node. + * @param parentId Index of parent node. + * @param m Move made to go from parent node to this node + * @param alpha Search parameter + * @param beta Search parameter + * @param ply Search parameter + * @param depth Search parameter + * @return node index + */ + final long logNodeStart(long parentIndex, Move m, int alpha, int beta, int ply, int depth) { + bb.putInt ( 0, (int)-1); + bb.putInt ( 4, (int)parentIndex); + bb.putShort( 8, (short)(m.from + (m.to << 6) + (m.promoteTo << 12))); + bb.putShort(10, (short)alpha); + bb.putShort(12, (short)beta); + bb.put (14, (byte)ply); + bb.put (15, (byte)depth); + try { + bos.write(bb.array()); + } catch (IOException e) { + throw new RuntimeException(); + } + return nextIndex++; + } + + /** + * @param startIndex Pointer to corresponding start node entry. + * @param score Computed score for this node. + * @param scoreType See TranspositionTable, T_EXACT, T_GE, T_LE. + * @param evalScore Score returned by evaluation function at this node, if known. + * @return node index + */ + final long logNodeEnd(long startIndex, int score, int scoreType, int evalScore, long hashKey) { + bb.putInt ( 0, (int)startIndex); + bb.putShort( 4, (short)score); + bb.putShort( 6, (short)scoreType); + bb.putLong( 8, hashKey); + bb.putShort( 8, (short)evalScore); // Overwrites first two byte of hashKey + try { + bos.write(bb.array()); + } catch (IOException e) { + throw new RuntimeException(); + } + return nextIndex++; + } + + // ---------------------------------------------------------------------------- + // Functions used for tree analyzing + + private static final int indexToFileOffs(int index) { + return 128 + index * 16; + } + + /** Compute endIndex for all StartNode entries. */ + private final void computeForwardPointers() { + if ((mapBuf.get(127) & (1<<7)) != 0) + return; + System.out.printf("Computing forward pointers...\n"); + StartEntry se = new StartEntry(); + EndEntry ee = new EndEntry(); + for (int i = 0; i < numEntries; i++) { + boolean isStart = readEntry(i, se, ee); + if (!isStart) { + int offs = indexToFileOffs(ee.startIndex); + mapBuf.putInt(offs, i); + } + } + mapBuf.put(127, (byte)(1 << 7)); + mapBuf.force(); + System.out.printf("Computing forward pointers... done\n"); + } + + /** Get FEN string for root node position. */ + private final String getRootNodeFEN() { + int len = mapBuf.get(0); + byte[] fenB = new byte[len]; + for (int i = 0; i < len; i++) + fenB[i] = mapBuf.get(1+i); + String ret = new String(fenB); + return ret; + } + + static final class StartEntry { + int endIndex; + int parentIndex; // -1 for root node + Move move; + short alpha; + short beta; + byte ply; + byte depth; + } + static final class EndEntry { + int startIndex; + short score; + short scoreType; + short evalScore; + long hashKey; // Note! Upper 2 bytes are not valid (ie 0) + } + + /** Read a start/end entry. + * @return True if entry was a start entry, false if it was an end entry. */ + private final boolean readEntry(int index, StartEntry se, EndEntry ee) { + int offs = indexToFileOffs(index); + for (int i = 0; i < 16; i++) + bb.put(i, mapBuf.get(offs + i)); + int otherIndex = bb.getInt(0); + boolean isStartEntry = (otherIndex == -1) || (otherIndex > index); + if (isStartEntry) { + se.endIndex = otherIndex; + se.parentIndex = bb.getInt(4); + int m = bb.getShort(8); + se.move = new Move(m & 63, (m >> 6) & 63, (m >> 12) & 15); + se.alpha = bb.getShort(10); + se.beta = bb.getShort(12); + se.ply = bb.get(14); + se.depth = bb.get(15); + } else { + ee.startIndex = otherIndex; + ee.score = bb.getShort(4); + ee.scoreType = bb.getShort(6); + ee.evalScore = bb.getShort(8); + ee.hashKey = bb.getLong(8) & 0x0000ffffffffffffL; + } + return isStartEntry; + } + + // ---------------------------------------------------------------------------- + // Functions used for the interactive tree browser + + public static final void main(String[] args) throws IOException { + if (args.length != 1) { + System.out.printf("Usage: progname filename\n"); + System.exit(1); + } + TreeLogger an = getAnalyzer(args[0]); + try { + Position rootPos = TextIO.readFEN(an.getRootNodeFEN()); + an.mainLoop(rootPos); + } catch (ChessParseError e) { + throw new RuntimeException(); + } + an.close(); + } + + private final void mainLoop(Position rootPos) throws IOException { + int currIndex = -1; + BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); + String prevStr = ""; + + boolean doPrint = true; + while (true) { + if (doPrint) { + ArrayList moves = getMoveSequence(currIndex); + for (Move m : moves) + System.out.printf(" %s", TextIO.moveToUCIString(m)); + System.out.printf("\n"); + printNodeInfo(rootPos, currIndex); + Position pos = getPosition(rootPos, currIndex); + System.out.print(TextIO.asciiBoard(pos)); + System.out.printf("%s\n", TextIO.toFEN(pos)); + System.out.printf("%16x\n", pos.historyHash()); + if (currIndex >= 0) { + ArrayList children = findChildren(currIndex); + for (Integer c : children) + printNodeInfo(rootPos, c); + } + } + doPrint = true; + System.out.printf("Command:"); + String cmdStr = in.readLine(); + if (cmdStr == null) + return; + if (cmdStr.length() == 0) + cmdStr = prevStr; + if (cmdStr.startsWith("q")) { + return; + } else if (cmdStr.startsWith("?")) { + printHelp(); + doPrint = false; + } else if (isMove(cmdStr)) { + ArrayList children = findChildren(currIndex); + String m = cmdStr; + StartEntry se = new StartEntry(); + EndEntry ee = new EndEntry(); + ArrayList found = new ArrayList(); + for (Integer c : children) { + readEntries(c, se, ee); + if (TextIO.moveToUCIString(se.move).equals(m)) + found.add(c); + } + if (found.size() == 0) { + System.out.printf("No such move\n"); + doPrint = false; + } else if (found.size() > 1) { + System.out.printf("Ambiguous move\n"); + for (Integer c : found) + printNodeInfo(rootPos, c); + doPrint = false; + } else { + currIndex = found.get(0); + } + } else if (cmdStr.startsWith("u")) { + int n = getArg(cmdStr, 1); + for (int i = 0; i < n; i++) + currIndex = findParent(currIndex); + } else if (cmdStr.startsWith("l")) { + ArrayList children = findChildren(currIndex); + String m = getArgStr(cmdStr, ""); + for (Integer c : children) + printNodeInfo(rootPos, c, m); + doPrint = false; + } else if (cmdStr.startsWith("n")) { + ArrayList nodes = getNodeSequence(currIndex); + for (int node : nodes) + printNodeInfo(rootPos, node); + doPrint = false; + } else if (cmdStr.startsWith("d")) { + ArrayList nVec = getArgs(cmdStr, 0); + for (int n : nVec) { + ArrayList children = findChildren(currIndex); + if ((n >= 0) && (n < children.size())) { + currIndex = children.get(n); + } else + break; + } + } else if (cmdStr.startsWith("p")) { + ArrayList moves = getMoveSequence(currIndex); + for (Move m : moves) + System.out.printf(" %s", TextIO.moveToUCIString(m)); + System.out.printf("\n"); + doPrint = false; + } else if (cmdStr.startsWith("h")) { + long hashKey = getPosition(rootPos, currIndex).historyHash(); + hashKey = getHashKey(cmdStr, hashKey); + ArrayList nodes = getNodeForHashKey(hashKey); + for (int node : nodes) + printNodeInfo(rootPos, node); + doPrint = false; + } else { + try { + int i = Integer.parseInt(cmdStr); + if ((i >= -1) && (i < numEntries)) + currIndex = i; + } catch (NumberFormatException e) { + } + } + prevStr = cmdStr; + } + } + + private final boolean isMove(String cmdStr) { + if (cmdStr.length() != 4) + return false; + cmdStr = cmdStr.toLowerCase(); + for (int i = 0; i < 4; i++) { + int c = cmdStr.charAt(i); + if ((i == 0) || (i == 2)) { + if ((c < 'a') || (c > 'h')) + return false; + } else { + if ((c < '1') || (c > '8')) + return false; + } + } + return true; + } + + /** Return all nodes with a given hash key. */ + private final ArrayList getNodeForHashKey(long hashKey) { + hashKey &= 0x0000ffffffffffffL; + ArrayList ret = new ArrayList(); + StartEntry se = new StartEntry(); + EndEntry ee = new EndEntry(); + for (int index = 0; index < numEntries; index++) { + boolean isStart = readEntry(index, se, ee); + if (!isStart) { + if (ee.hashKey == hashKey) { + int sIdx = ee.startIndex; + ret.add(sIdx); + } + } + } + Collections.sort(ret); + return ret; + } + + /** Get hash key from an input string. */ + private final long getHashKey(String s, long defKey) { + long key = defKey; + int idx = s.indexOf(' '); + if (idx > 0) { + s = s.substring(idx + 1); + if (s.startsWith("0x")) + s = s.substring(2); + try { + key = Long.parseLong(s, 16); + } catch (NumberFormatException e) { + } + } + return key; + } + + /** Get integer parameter from an input string. */ + private static final int getArg(String s, int defVal) { + try { + int idx = s.indexOf(' '); + if (idx > 0) { + return Integer.parseInt(s.substring(idx+1)); + } + } catch (NumberFormatException e) { + } + return defVal; + } + + /** Get a list of integer parameters from an input string. */ + final ArrayList getArgs(String s, int defVal) { + ArrayList ret = new ArrayList(); + String[] split = s.split(" "); + try { + for (int i = 1; i < split.length; i++) + ret.add(Integer.parseInt(split[i])); + } catch (NumberFormatException e) { + ret.clear(); + } + if (ret.size() == 0) + ret.add(defVal); + return ret; + } + + /** Get a string parameter from an input string. */ + private static final String getArgStr(String s, String defVal) { + int idx = s.indexOf(' '); + if (idx > 0) + return s.substring(idx+1); + return defVal; + } + + private final void printHelp() { + System.out.printf(" p - Print move sequence\n"); + System.out.printf(" n - Print node info corresponding to move sequence\n"); + System.out.printf(" l [move] - List child nodes, optionally only for one move\n"); + System.out.printf(" d [n1 [n2...]] - Go to child \"n\"\n"); + System.out.printf(" move - Go to child \"move\", if unique\n"); + System.out.printf(" u [levels] - Move up\n"); + System.out.printf(" h [key] - Find nodes with current (or given) hash key\n"); + System.out.printf(" num - Go to node \"num\"\n"); + System.out.printf(" q - Quit\n"); + System.out.printf(" ? - Print this help\n"); + } + + /** Read start/end entries for a tree node. Return true if the end entry exists. */ + private final boolean readEntries(int index, StartEntry se, EndEntry ee) { + boolean isStart = readEntry(index, se, ee); + if (isStart) { + int eIdx = se.endIndex; + if (eIdx >= 0) { + readEntry(eIdx, null, ee); + } else { + return false; + } + } else { + int sIdx = ee.startIndex; + readEntry(sIdx, se, null); + } + return true; + } + + /** Find the parent node to a node. */ + private final int findParent(int index) { + if (index >= 0) { + StartEntry se = new StartEntry(); + EndEntry ee = new EndEntry(); + readEntries(index, se, ee); + index = se.parentIndex; + } + return index; + } + + /** Find all children of a node. */ + private final ArrayList findChildren(int index) { + ArrayList ret = new ArrayList(); + StartEntry se = new StartEntry(); + EndEntry ee = new EndEntry(); + int child = index + 1; + while ((child >= 0) && (child < numEntries)) { + boolean haveEE = readEntries(child, se, ee); + if (se.parentIndex == index) + ret.add(child); + if (!haveEE) + break; + if (child != ee.startIndex) + break; // two end entries in a row, no more children +// if (se.parentIndex != index) +// break; + child = se.endIndex + 1; + } + return ret; + } + + /** Get node position in parents children list. */ + private final int getChildNo(int index) { + ArrayList childs = findChildren(findParent(index)); + for (int i = 0; i < childs.size(); i++) + if (childs.get(i) == index) + return i; + return -1; + } + + /** Get list of nodes from root position to a node. */ + private final ArrayList getNodeSequence(int index) { + ArrayList nodes = new ArrayList(); + nodes.add(index); + while (index >= 0) { + index = findParent(index); + nodes.add(index); + } + Collections.reverse(nodes); + return nodes; + } + + /** Find list of moves from root node to a node. */ + private final ArrayList getMoveSequence(int index) { + ArrayList moves = new ArrayList(); + StartEntry se = new StartEntry(); + EndEntry ee = new EndEntry(); + while (index >= 0) { + readEntries(index, se, ee); + moves.add(se.move); + index = findParent(index); + } + Collections.reverse(moves); + return moves; + } + + /** Find the position corresponding to a node. */ + private final Position getPosition(Position rootPos, int index) { + ArrayList moves = getMoveSequence(index); + Position ret = new Position(rootPos); + UndoInfo ui = new UndoInfo(); + for (Move m : moves) + ret.makeMove(m, ui); + return ret; + } + + private final void printNodeInfo(Position rootPos, int index) { + printNodeInfo(rootPos, index, ""); + } + private final void printNodeInfo(Position rootPos, int index, String filterMove) { + if (index < 0) { // Root node + System.out.printf("%8d entries:%d\n", index, numEntries); + } else { + StartEntry se = new StartEntry(); + EndEntry ee = new EndEntry(); + boolean haveEE = readEntries(index, se, ee); + String m = TextIO.moveToUCIString(se.move); + if ((filterMove.length() > 0) && !m.equals(filterMove)) + return; + System.out.printf("%3d %8d %s a:%6d b:%6d p:%2d d:%2d", getChildNo(index), index, + m, se.alpha, se.beta, se.ply, se.depth); + if (haveEE) { + int subTreeNodes = (se.endIndex - ee.startIndex - 1) / 2; + String type; + switch (ee.scoreType) { + case TTEntry.T_EXACT: type = "= "; break; + case TTEntry.T_GE : type = ">="; break; + case TTEntry.T_LE : type = "<="; break; + default : type = " "; break; + } + System.out.printf(" s:%s%6d e:%6d sub:%d", type, ee.score, ee.evalScore, + subTreeNodes); + } + System.out.printf("\n"); + } + } +} diff --git a/CuckooChessEngine/src/chess/TwoReturnValues.java b/CuckooChessEngine/src/chess/TwoReturnValues.java new file mode 100644 index 0000000..cb76a0b --- /dev/null +++ b/CuckooChessEngine/src/chess/TwoReturnValues.java @@ -0,0 +1,33 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +/** + * A small helper class that makes it possible to return two values from a function. + * @author petero + */ +public final class TwoReturnValues { + public final T1 first; + public final T2 second; + + TwoReturnValues(T1 first, T2 second) { + this.first = first; + this.second = second; + } +} diff --git a/CuckooChessEngine/src/chess/UndoInfo.java b/CuckooChessEngine/src/chess/UndoInfo.java new file mode 100644 index 0000000..f44744d --- /dev/null +++ b/CuckooChessEngine/src/chess/UndoInfo.java @@ -0,0 +1,31 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +/** + * Contains enough information to undo a previous move. + * Set by makeMove(). Used by unMakeMove(). + * @author petero + */ +public class UndoInfo { + int capturedPiece; + int castleMask; + int epSquare; + int halfMoveClock; +} diff --git a/CuckooChessEngine/src/guibase/ChessController.java b/CuckooChessEngine/src/guibase/ChessController.java new file mode 100644 index 0000000..5748560 --- /dev/null +++ b/CuckooChessEngine/src/guibase/ChessController.java @@ -0,0 +1,549 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package guibase; + +import chess.ChessParseError; +import chess.ComputerPlayer; +import chess.Game; +import chess.HumanPlayer; +import chess.Move; +import chess.MoveGen; +import chess.Piece; +import chess.Player; +import chess.Position; +import chess.Search; +import chess.TextIO; +import chess.UndoInfo; +import chess.Game.GameState; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Scanner; + +/** + * The glue between the chess engine and the GUI. + * @author petero + */ +public class ChessController { + Player humanPlayer; + ComputerPlayer computerPlayer; + Game game; + GUIInterface gui; + boolean humanIsWhite; + Thread computerThread; + int threadStack; // Thread stack size, or zero to use OS default + + // Search statistics + String thinkingPV; + + class SearchListener implements Search.Listener { + int currDepth = 0; + int currMoveNr = 0; + String currMove = ""; + int currNodes = 0; + int currNps = 0; + int currTime = 0; + + int pvDepth = 0; + int pvScore = 0; + boolean pvIsMate = false; + boolean pvUpperBound = false; + boolean pvLowerBound = false; + String pvStr = ""; + + private void setSearchInfo() { + StringBuilder buf = new StringBuilder(); + buf.append(String.format("%n[%d] ", pvDepth)); + if (pvUpperBound) { + buf.append("<="); + } else if (pvLowerBound) { + buf.append(">="); + } + if (pvIsMate) { + buf.append(String.format("m%d", pvScore)); + } else { + buf.append(String.format("%.2f", pvScore / 100.0)); + } + buf.append(pvStr); + buf.append(String.format("%n")); + buf.append(String.format("d:%d %d:%s t:%.2f n:%d nps:%d", currDepth, + currMoveNr, currMove, currTime / 1000.0, currNodes, currNps)); + final String newPV = buf.toString(); + gui.runOnUIThread(new Runnable() { + public void run() { + thinkingPV = newPV; + setThinkingPV(); + } + }); + } + + public void notifyDepth(int depth) { + currDepth = depth; + setSearchInfo(); + } + + public void notifyCurrMove(Move m, int moveNr) { + currMove = TextIO.moveToString(new Position(game.pos), m, false); + currMoveNr = moveNr; + setSearchInfo(); + } + + public void notifyPV(int depth, int score, int time, int nodes, int nps, boolean isMate, + boolean upperBound, boolean lowerBound, ArrayList pv) { + pvDepth = depth; + pvScore = score; + currTime = time; + currNodes = nodes; + currNps = nps; + pvIsMate = isMate; + pvUpperBound = upperBound; + pvLowerBound = lowerBound; + + StringBuilder buf = new StringBuilder(); + Position pos = new Position(game.pos); + UndoInfo ui = new UndoInfo(); + for (Move m : pv) { + buf.append(String.format(" %s", TextIO.moveToString(pos, m, false))); + pos.makeMove(m, ui); + } + pvStr = buf.toString(); + setSearchInfo(); + } + + public void notifyStats(int nodes, int nps, int time) { + currNodes = nodes; + currNps = nps; + currTime = time; + setSearchInfo(); + } + } + SearchListener listener; + + public ChessController(GUIInterface gui) { + this.gui = gui; + listener = new SearchListener(); + thinkingPV = ""; + threadStack = 0; + } + + public void setThreadStackSize(int size) { + threadStack = size; + } + + public final void newGame(boolean humanIsWhite, int ttLogSize, boolean verbose) { + stopComputerThinking(); + this.humanIsWhite = humanIsWhite; + humanPlayer = new HumanPlayer(); + computerPlayer = new ComputerPlayer(); + computerPlayer.verbose = verbose; + computerPlayer.setTTLogSize(ttLogSize); + computerPlayer.setListener(listener); + if (humanIsWhite) { + game = new Game(humanPlayer, computerPlayer); + } else { + game = new Game(computerPlayer, humanPlayer); + } + } + public final void startGame() { + gui.setSelection(-1); + updateGUI(); + startComputerThinking(); + } + + public final void setPosHistory(List posHistStr) { + try { + String fen = posHistStr.get(0); + Position pos = TextIO.readFEN(fen); + game.processString("new"); + game.pos = pos; + String[] strArr = posHistStr.get(1).split(" "); + final int arrLen = strArr.length; + for (int i = 0; i < arrLen; i++) { + game.processString(strArr[i]); + } + int numUndo = Integer.parseInt(posHistStr.get(2)); + for (int i = 0; i < numUndo; i++) { + game.processString("undo"); + } + } catch (ChessParseError e) { + // Just ignore invalid positions + } + } + + public final List getPosHistory() { + return game.getPosHistory(); + } + + public String getFEN() { + return TextIO.toFEN(game.pos); + } + + /** Convert current game to PGN format. */ + public String getPGN() { + StringBuilder pgn = new StringBuilder(); + List posHist = getPosHistory(); + String fen = posHist.get(0); + String moves = game.getMoveListString(true); + if (game.getGameState() == GameState.ALIVE) + moves += " *"; + int year, month, day; + { + Calendar now = GregorianCalendar.getInstance(); + year = now.get(Calendar.YEAR); + month = now.get(Calendar.MONTH) + 1; + day = now.get(Calendar.DAY_OF_MONTH); + } + pgn.append(String.format("[Date \"%04d.%02d.%02d\"]%n", year, month, day)); + String white = "Player"; + String black = ComputerPlayer.engineName; + if (!humanIsWhite) { + String tmp = white; white = black; black = tmp; + } + pgn.append(String.format("[White \"%s\"]%n", white)); + pgn.append(String.format("[Black \"%s\"]%n", black)); + pgn.append(String.format("[Result \"%s\"]%n", game.getPGNResultString())); + if (!fen.equals(TextIO.startPosFEN)) { + pgn.append(String.format("[FEN \"%s\"]%n", fen)); + pgn.append("[SetUp \"1\"]\n"); + } + pgn.append("\n"); + String[] strArr = moves.split(" "); + int currLineLength = 0; + final int arrLen = strArr.length; + for (int i = 0; i < arrLen; i++) { + String move = strArr[i].trim(); + int moveLen = move.length(); + if (moveLen > 0) { + if (currLineLength + 1 + moveLen >= 80) { + pgn.append("\n"); + pgn.append(move); + currLineLength = moveLen; + } else { + if (currLineLength > 0) { + pgn.append(" "); + currLineLength++; + } + pgn.append(move); + currLineLength += moveLen; + } + } + } + pgn.append("\n\n"); + return pgn.toString(); + } + + public void setPGN(String pgn) throws ChessParseError { + // First pass, remove comments + { + StringBuilder out = new StringBuilder(); + Scanner sc = new Scanner(pgn); + sc.useDelimiter(""); + while (sc.hasNext()) { + String c = sc.next(); + if (c.equals("{")) { + sc.skip("[^}]*}"); + } else if (c.equals(";")) { + sc.skip("[^\n]*\n"); + } else { + out.append(c); + } + } + pgn = out.toString(); + } + + // Parse tag section + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Scanner sc = new Scanner(pgn); + sc.useDelimiter("\\s+"); + while (sc.hasNext("\\[.*")) { + String tagName = sc.next(); + if (tagName.length() > 1) { + tagName = tagName.substring(1); + } else { + tagName = sc.next(); + } + String tagValue = sc.findWithinHorizon(".*\\]", 0); + tagValue = tagValue.trim(); + if (tagValue.charAt(0) == '"') + tagValue = tagValue.substring(1); + if (tagValue.charAt(tagValue.length()-1) == ']') + tagValue = tagValue.substring(0, tagValue.length() - 1); + if (tagValue.charAt(tagValue.length()-1) == '"') + tagValue = tagValue.substring(0, tagValue.length() - 1); + if (tagName.equals("FEN")) { + pos = TextIO.readFEN(tagValue); + } + } + game.processString("new"); + game.pos = pos; + + // Handle (ignore) recursive annotation variations + { + StringBuilder out = new StringBuilder(); + sc.useDelimiter(""); + int level = 0; + while (sc.hasNext()) { + String c = sc.next(); + if (c.equals("(")) { + level++; + } else if (c.equals(")")) { + level--; + } else if (level == 0) { + out.append(c); + } + } + pgn = out.toString(); + } + + // Parse move text section + sc = new Scanner(pgn); + sc.useDelimiter("\\s+"); + while (sc.hasNext()) { + String strMove = sc.next(); + strMove = strMove.replaceFirst("\\$?[0-9]*\\.*([^?!]*)[?!]*", "$1"); + if (strMove.length() == 0) continue; + Move m = TextIO.stringToMove(game.pos, strMove); + if (m == null) + break; + game.processString(strMove); + } + } + + public void setFENOrPGN(String fenPgn) throws ChessParseError { + try { + Position pos = TextIO.readFEN(fenPgn); + game.processString("new"); + game.pos = pos; + } catch (ChessParseError e) { + // Try read as PGN instead + setPGN(fenPgn); + } + gui.setSelection(-1); + updateGUI(); + startComputerThinking(); + } + + /** Set color for human player. Doesn't work when computer is thinking. */ + public final void setHumanWhite(final boolean humanIsWhite) { + if (computerThread != null) + return; + if (this.humanIsWhite != humanIsWhite) { + this.humanIsWhite = humanIsWhite; + game.processString("swap"); + startComputerThinking(); + } + } + + public final boolean humansTurn() { + return game.pos.whiteMove == humanIsWhite; + } + public final boolean computerThinking() { + return computerThread != null; + } + + public final void takeBackMove() { + if (humansTurn()) { + if (game.getLastMove() != null) { + game.processString("undo"); + if (game.getLastMove() != null) { + game.processString("undo"); + } else { + game.processString("redo"); + } + updateGUI(); + setSelection(); + } + } else if (game.getGameState() != Game.GameState.ALIVE) { + if (game.getLastMove() != null) { + game.processString("undo"); + if (!humansTurn()) { + if (game.getLastMove() != null) { + game.processString("undo"); + } else { + game.processString("redo"); + } + } + updateGUI(); + setSelection(); + } + } + } + + public final void redoMove() { + if (humansTurn()) { + game.processString("redo"); + game.processString("redo"); + updateGUI(); + setSelection(); + } + } + + public final void humanMove(Move m) { + if (humansTurn()) { + if (doMove(m)) { + updateGUI(); + startComputerThinking(); + } else { + gui.setSelection(-1); + } + } + } + + Move promoteMove; + public final void reportPromotePiece(int choice) { + final boolean white = game.pos.whiteMove; + int promoteTo; + switch (choice) { + case 1: + promoteTo = white ? Piece.WROOK : Piece.BROOK; + break; + case 2: + promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP; + break; + case 3: + promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT; + break; + default: + promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN; + break; + } + promoteMove.promoteTo = promoteTo; + Move m = promoteMove; + promoteMove = null; + humanMove(m); + } + + /** + * Move a piece from one square to another. + * @return True if the move was legal, false otherwise. + */ + final private boolean doMove(Move move) { + Position pos = game.pos; + MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moves); + int promoteTo = move.promoteTo; + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + if ((m.from == move.from) && (m.to == move.to)) { + if ((m.promoteTo != Piece.EMPTY) && (promoteTo == Piece.EMPTY)) { + promoteMove = m; + gui.requestPromotePiece(); + return false; + } + if (m.promoteTo == promoteTo) { + String strMove = TextIO.moveToString(pos, m, false); + game.processString(strMove); + return true; + } + } + } + gui.reportInvalidMove(move); + return false; + } + + + final private void updateGUI() { + setStatusString(); + setMoveList(); + setThinkingPV(); + gui.setPosition(game.pos); + } + + final private void setStatusString() { + String str = game.pos.whiteMove ? "White's move" : "Black's move"; + if (computerThread != null) str += " (thinking)"; + if (game.getGameState() != Game.GameState.ALIVE) { + str = game.getGameStateString(); + } + gui.setStatusString(str); + } + + public final void setMoveList() { + String str = game.getMoveListString(true); + gui.setMoveListString(str); + } + + public final void setThinkingPV() { + String str = ""; + if (gui.showThinking()) { + str = thinkingPV; + } + gui.setThinkingString(str); + } + + final private void setSelection() { + Move m = game.getLastMove(); + int sq = (m != null) ? m.to : -1; + gui.setSelection(sq); + } + + + private void startComputerThinking() { + if (game.pos.whiteMove != humanIsWhite) { + if (computerThread == null) { + Runnable run = new Runnable() { + public void run() { + computerPlayer.timeLimit(gui.timeLimit(), gui.timeLimit(), gui.randomMode()); + final String cmd = computerPlayer.getCommand(new Position(game.pos), + game.haveDrawOffer(), game.getHistory()); + gui.runOnUIThread(new Runnable() { + public void run() { + game.processString(cmd); + thinkingPV = ""; + updateGUI(); + setSelection(); + stopComputerThinking(); + } + }); + } + }; + if (threadStack > 0) { + ThreadGroup tg = new ThreadGroup("searcher"); + computerThread = new Thread(tg, run, "searcher", threadStack); + } else { + computerThread = new Thread(run); + } + thinkingPV = ""; + updateGUI(); + computerThread.start(); + } + } + } + + public synchronized void stopComputerThinking() { + if (computerThread != null) { + computerPlayer.timeLimit(0, 0, false); + try { + computerThread.join(); + } catch (InterruptedException ex) { + System.out.printf("Could not stop thread%n"); + } + computerThread = null; + updateGUI(); + } + } + + public synchronized void setTimeLimit() { + if (computerThread != null) { + computerPlayer.timeLimit(gui.timeLimit(), gui.timeLimit(), gui.randomMode()); + } + } +} diff --git a/CuckooChessEngine/src/guibase/GUIInterface.java b/CuckooChessEngine/src/guibase/GUIInterface.java new file mode 100644 index 0000000..54062bf --- /dev/null +++ b/CuckooChessEngine/src/guibase/GUIInterface.java @@ -0,0 +1,58 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package guibase; + +import chess.Move; +import chess.Position; + +public interface GUIInterface { + + /** Update the displayed board position. */ + public void setPosition(Position pos); + + /** Mark square i as selected. Set to -1 to clear selection. */ + public void setSelection(int sq); + + /** Set the status text. */ + public void setStatusString(String str); + + /** Update the list of moves. */ + public void setMoveListString(String str); + + /** Update the computer thinking information. */ + public void setThinkingString(String str); + + /** Get the current time limit. */ + public int timeLimit(); + + /** Get "random move" setting. */ + public boolean randomMode(); + + /** Return true if "show thinking" is enabled. */ + public boolean showThinking(); + + /** Ask what to promote a pawn to. Should call reportPromotePiece() when done. */ + public void requestPromotePiece(); + + /** Run code on the GUI thread. */ + public void runOnUIThread(Runnable runnable); + + /** Report that user attempted to make an invalid move. */ + public void reportInvalidMove(Move m); +} diff --git a/CuckooChessEngine/src/kpk.bitbase b/CuckooChessEngine/src/kpk.bitbase new file mode 100644 index 0000000000000000000000000000000000000000..6b8d6b685adf7b71d0b7cc0428748edba3667d1c GIT binary patch literal 24576 zcmc(nL5v&cfu-3^g*9lkSO{RN0k@m~A(u&viH-uSq${@od&vL;tPg{@eH_fmCmE1E zB&}Y#f$?Vgk{uuR;Nu(y8y#~nGNdHf4J252E?FN2GtH>sRp7-;Q?@M9O;*)@@BJj3 z-AzfYEP^GBCx-p%@8VZK->a(rnMy^E^4yQY&fGe(4y=dPeQU?svfl0LpIdF|ihs}L zWj+6XP5OFWdOMYF1!?zg4{hJlW|b9fw)}!Nt9?&e|Ibx4y`7+8OPh7O_xt$UANTNA z{J3jD*Oz~9ds_s z4|Dv7{b%!s&x}9*XYv=%8Grm!{!R1WH2+QW-!%Ws{Ifa!djID;3eU^G7;EIOe=m*m z|AjO2Kk{#7@{Es|&Sn2|Ch}zdSI*4;$UpP^9}mA|gMROL{#VY-|Hwb{{2ved%>RDx zc>Xi@PR;+wA3n@~{P4#=oB#Mv&Hu<>JemLa;g7$cNB&LoZ<>G8{9ESVI>TS@|5-=j z596N?HS%Boxzsqz?_JRIm_KLv55Lx@;En&(MkD{_KdtaQ=FeIF!~W-N@WzjnKau}( zumPhgL-XQ{Obw%kDvU(zc~N#lmGb1fBeaQ1Ap3) zKeS?#o?j#Rcoe=7u<~o`zgz#+`YY?-Sab8w{R?sz_KPO}XJ-CW>#u^WKedis81wym z!TeXvf7Sdq{DIjp|Cm2x^MA+uFPQ(T`LCM)hCeVH<{$H?J=eb%%zxGVSIvKe4@_Ea zn19TlvH8Da{x6&V%jW;G`QwK_e)!8jEadO!^7lqRF#jKz{}0UnhxpUZ56wTG!<=8^ zoR8$=QTR&m%Ku~i7wbpXx2;F!KQjN3f6d4zOs|;yUj~^zvc8?^`H>4_=)#zu|L9}0 zV*W3g|GN2Kb7360Fpiu*H_ZQr^MA$sUo!u7^S_p7-*EoFYW}a9|26Z+4}bZGh5Y?o{@zIbJA)hMf5ZIWHvhNrhgLj$ zJio@YiE}=Zk4NEw;FX_Qe`$To`q=!x_Qz&m{saHA!J&TqT9E!SlfGqrZ2pg27;pad zKlA#ue^nTl&Ht+TUp4>7=Ksiras7>7lmFMv|GLjVFPr~W^S^5TkInxPh0!nHe4{(d ze^QA2xorMd&Ht+Tu+?)pX=s--RGaz&HpX)f6M&k9~ScWbNPEC`R@$m z-#(N-*_S`<$RApz`J7*4p2t)fdE0^Y(7JE!SX5f|0~w5c>ZtQY5=$_ z=L%zaeOi><9du|Ko?hli~atn>6NmOqGF0IJE9tJJyyplAjy}$JW}n zz*b(lB;B%hHvHULo+*sPg^{{2QWr*WVFVXO;lha5U(MLR7sS85YW}O@A6yv0g;99^ z7h~~nz9ati3*uj2HUCxdC(6a2!hpZ$f9zkKPk!;RaY6hWtK#2S6@Q%KPXWN+^PlnI z&-n1)d|CWAUl#w(m&HHBpLS63cP5-)3;Sb}#ypRya_HChtsQI2TFe#3)+<`3*x^b{*9N+--VI6Fbcw)`A=cM z|AzQCZ|MA|u_FGBm&~8SNXv}EfIorG`H%A_^5;w*{+lb}zxk5*XB5WmfM5LabN){O z!2gE$Gd}#AuZn;3Rq=087-^@4KeS4x@%go|Kexsvjd_0PSN5$PYs*@=Fmhi7mnJ_s z3NDPTH8Jm8mA0&f3uF9*!U!&m%!QG;Fz~~F?80ci`2~d$&;OgX@9{ZnTo(V#g^{^1 z3JL@M6bAgS_r(9Y_&2ZX{B!fN_-7Ob{3#6h6X@`#Fuoo(-|R8}bw2;wgQokpd0G5h z6vpk)qA=i(AN~{o{IB=K|GN2K*ZF7jy7^N8@TUOafBxvVv#{pYk#%4_wC-Cw)|PdW z=T4Q2RaL@TBE4eWO7;A+@6s2^-U%Pov`Je0@235trwIjdy$Cy5^7WU`Xk#%4_ zwC-Cw)=9OTDU7v5zw1gW-LiIqpIe#Bygoe&DvZR1k-9Ka7e;VlgslyJ%Kq!HmB&{Y zN%A86Qx`_+!U!&m;KC>QEL?(rhBI`mt+E_x_s^|` zow;>n9as;o`__)NWi6gi7*|sL-mM_*SX-H%6P;*Tiv08_D7I1;M%Y^CdFV(p7Y2Uw zKYfLfx-fzZBe*a!7l!=sr!a;kd9M6vF8};q*{H9;KZJGjS7Br>4E$rw|0DB1>K#37 z66=G5&BhA+v#<_-6#n?L7W|LKn*W@Iihr*^V*K{Opt-pM|5mmRe-!>L{8_6!fArf~ zSaa*hI?WAlGB%oXeAf4w_6 zXg04`5BFNx8vI)nMkk<|_&+xPN5f&x_~u{f!k=3|dxx#on)p)~@TV}uKgRTVl{d?O zGReuaO;j>iAs&lC8g`__)NWraVl=VO~)7=uL5;g7-`g+I!N z9c9l^wy+*wVZfhPU@moG@L}gU{#jwnjsk@Nf37fs3j_Y^^o0vUe%=H7=7Sf10l5gt zS9bBMFwl>b|0)do9q8iU?koSBHwOohyr}%AFv$O~uKdrgDSsGW4_Y4nM@MM$Xu!J* z>$}#lujk5t6$X2v@IAM(?{UIM>}dQC{yf>YcC0Nc{COSz=xAuO3*#`=bNHj3ObUOL zJx9k+C=B@X)69jDxiIik7~{FZm>mTQ1O96wufl*oDnF6ky?XtXW6T%|FKOIaPBfo`c3S zq1eZT=0C6RTRYa4RaZy&+tQ2NkuSb-W!>PhR zDU84d4H+lDpvXD@zx%~kg?uq){gv*md5ud@tmsn8qfaYIcPi+%FRD7_x;n3wPh85{6pyo{<-<*=AWB?uKeM3 z@y|>49c|yz?^wFHjf#I>CRbDy;-4#jq-@|lKUEmwkJE)A{y5n{dR!RCN8uZh|KMXW zzw$FFg#nn(hIDI#_rPAo1MFXxkHqu055(|)d@B8^@}I)cTd^wNHYbE9KzX?$Yz+^SuP*In`KKc0h*%ztG5qq1X9%PRi(htknQ>45n^GXIhJ z;}3d;{6WQkR1RGjl@yZRfel7w#s*x0^YH$kDh%<*33I6mgZ#;&e_9yF zN8zhd{NKl^!aymE%!Q%x7Zrv`{^O?^*1!Bv`cc47VaTtbVk^%7i{c?^(<(=U7N+-0 z>H5C4)53qPRq`NG&#mGguXy8iS3LV4nE$~12j)-W*(v_`;g7#B9UMpx$)ADw;}3c+ z{sZ$LnE#;cAE_`ZFX(q@{twKbuIywy&)9%_A3WIV356m4I9(XxAN`N<9~Z{)QFu@S zlEMHCg@IBS8ef6FxG+TWwGUfZf63@7jH$mU9>s0B{%RHnItr};=l@x@#&Z>hRge_B z=T=+eCqB-*ctfsG{+Go*QWhO+%j)$%h1Z`HUjIY~X@@G$aWv5X7q*4k)$09JM9_$%~f&cCoU*g>P?0=)l?N4x~TYo3L z-M}BVHtqEIW#RlUlvjI?mH#{c!G|VI|IPYO@`tAVPPQ1P&(gT-lPJ{2@m6SV{`tON z=~!FVqapv~=AWB?ZvMIX=jNZAe{TLcHyiP{Z?Rc!{<-<*=AWB?&dn}_BGt7 zV{KWF#DB!iUex?Y=07t3k@=6ze`Nk6^B-}u7k_0%g<<|9^B%U7o>-f3CnDd{Y+R8%M9ZAE#SU)0!C=5X&M$fJ0AOGYv@GqGEbM9c@<8-VotN9Pi zfAB?*mj+({Gcf;w`47y0VEzO1ADI83$K9E7PjtyZ$Wb zP%tItj*hM`?%qFAUhNI!-#L^s-M4nULCfPmhdeKTtqwf7{`2YlwL0+R`p>8H*Xls? zXEXBWN&Lf2a2cgA0ZahAW{NjVb8&mM^3?N6n?|HB4)C=&IUKMIk* zoV$lo?!W2Lv9@e~4*A3QYjxmk{in|+o;3d>f2|JWU3mT5dH8E}p!ri63+I0@|BRKQ z=Fb`h3IqPX9--6uANhweabctmEa%CBH!YC2KNZ8R52e2ULtxGMPaFmLcMs(Z`_>K_ zL}2kYpX;$;SG@h9b>G^twybgfNB&x!GhP3A9{yeaIs#9BV4jpeasEgCtj^)Ek^dCN zsr(6x__IcV!eFlceBk_<=bthE!Rd2&o&BpYwD;wS&=tw84+XL#negX^sW-6y#8EK$ zeOo)$*8C^Me;zN{c^-S#U2Cvr)_MN%ym;C3xwfny*S)Hz?)5w=nX6PO z`c;%wPbf=cP!>g^_fsDL zjef7>@Ad8P*?&jlFKui5YNGM!0QrN*|0DW&%RICXk!D#w@GIxxAE&}u{tfdFK?AY> z=y~{ag%bTe``L_LYR7$B#9vo5&%=?`yjLf7x?BYE6^3tVeb7*RAp9e7xdkLp;k4 zELL6eoK-j4%GSCIgS_gA_A!5$uo*L7%Bm;%6Y?KFzfJy=`7eLle&#=Z_-oax{fqL) z{yQ3<`7eKBf2#2p<^R+DTB+FgHwAkAx<6kipD(^O!1(&sz{&ArlFaMp5Gl)#tv|DF zSbu1ZpA?4V@r_^kmYGMrWab-DgMNJS-nm|>nEwUyUv*)xn*W9i<1Byurm|#}KFTV6 z__InM{u}zli_S{a*P$^veJK%jS>5 zpKlE?zP>e3P4umS>G<*1Mof}<{f$tS?_1Zc*R1hgcqFeqsZ`=w(?v6nx*lY`W*xdP z&fYLuc44fT|4R*Ryl(z6f3)Myoc3Juz@Jt6@Mo1i{8@C#k&pS~H>#v!Gwsv#{73$( z{fYf)^e@PN@W99ZMB^u^#!o}cACG^cFP`N0%KxEP{`a-=zuS9N`*TOSw5@LqR1* znhWEy`Lldde%^4YmHDUm>vv4gKUt*@e->TBUw*ITUzq>u>#A1ulRxsO(LdDqi}K(8 z#QxO&;PIcs@0I`lra-T+mH*uy3V-R+w!Q^WP5do@^eyv`_cr26{;ymE2i867t~FRQ zYs~YQD)afD_{=|z8qfc&;s!Y%wPkM*HF04?C2Aobl?xu8$3-a2v9|g`fkjhL7%0r? z3pP+dV^rR{FZ@kj=0A4jzfj80rGi%JbN-J%ExYnh=D&>Hp8a?1C;xToZ@<>)@CDtN z=QSDoHT%^R?PvJ)O|e=2+fVYtU*8%yIsW{;>Udl8fy-dex@!&A%zEv6Q74+=zbf+h*`TdAs+EMZ`41)W zC(xI)s$cwBrSDb!Y-az7{IUOz{oD2@_NRt<3)&PEeYoMCw zTLVN-9N(cI?}*0}%0s{Msdd*HteG{gwmJ33RZQV@R^hF0cJ=YLc0car};-{4yS;?Jsn@h8yvGno7*_-n6b z=O^+f_NVrT=#Tt8es98)Sq;x~_%}U1-@nGcX}`WTkffRRC&atdQRAPo;vMUKm%)eD zV9l&!S4Gq_%Q?%iCh}3Ut9m|mRYX0z{y(@XqOuw$`iEut&YQnqSQw4k3ZJ&dW%0)! zG(Ics@%Lu)A4<%BQC%watxx!~O5dyc;cx%Y{u6y_|F->!{V6MTH2*ceSN`|};$07|53L8*J?pMDSh*J^KGr|+O8zBl z)tY!ce;QTGGt1OE$#bVlWj_C17%ZZ+Q&mCWqT-;YRbi0)H?1T;PG){oMYH`V4KOPV zlHB>fysZ2uYTXyQ^P{SWs+IZup^Ux0{p3IYi#)F_SC#+VF1C~KXRE*;nDI~Y-<#E* z?$^qHzA2#b>)uSs&*#hI>stdS#~-@@hSrDH1M8l3*BY#ub>9DNEzIPexiqn+Ue6z* zhQ)QDuRMy1sJd&Rt1;rLllb7N&d8w99%73~rY7~a}Uw}WW`Z-C`R$A4m z!iasUmHD$iRjtDR0{rot4}Na{(Ee}{E8P_m}h5cEoB9Y3Kk5?4iPWlaqJ zsMFOpB0oI}Dh!+^FU1+A%4(ZO1lH;0oRD9>ApR@xmtXu<7+#q_oaDLxnOvVz7~;Po zKCJ2&AN=8jACTQ?{@c&D1{V9Z^1t6?{9bRfA^w{UjjwMFBuVD+QO5VeO!zC@A6gHr zd)8fRu<|EVJY!eI(E89C{XY)+X=)ADEQ@{>#_$P+k-92^l{GOQUxhKAD~#z;c)(9_ zhN{~G+|4;pzm9O8%1JLU-`%};&}ecGs1#h+FBDSq*{ zzp!7%ZVwet`#0^+=KU1LkOf#8pOL%0p2qLqX|h3eySb_H^{s(4ZFzj&EcwX{Liytn zdSKnN?pk@vyw00N$8G7KRHREwQcf^X3JztmNwqw4qS0TeZ1W0xfG#gfnFcduXLuf! zU&8a4DwWE7{;Mk33dKoPM4cWT6^8oau9VH>OeQL)SM~ck8y@Px*s>%TxRn{L25Eq%6;wWoU3=@b^%jLNaW-M!+aGJ2 zGjgrES8eEuNUFjhRZy}5Wv*5y3%*he8Tiif!>!S|INF< zbN=5W`S}8v^~Ct%pPPSf{<-<*=AWB?ZvMIEKTdsi@k2f@zikzN*5|{YRrm164}UaB z+ieww_)Aq7OI(q%svrJr#T`kl)t55&&{O^$`>6`?x1YigAN#|i!dSw3%lyB^SuufY zzgP9o3gh@Fc>RBJYft3AwWE=BZZ2hi78Zt@`j(H^BERt;Y32ENsBhkNAE5ZT*~$1J zjyyNM__N;Ls`)=M|B?BR%zsn@lGih<_~YlZJ5rvb-+@2t^Wo3>eE8#sKYljDIaFa( zDk==#wN-@yBvky_Dh;Uk+fP;Cw@&<&1b!4hs&2F&B}x_+28WBP5dVu>Nkw5GsQ63^ z#YB1EdKpME6!GrG#NcXI})?m%7?Kb}uxMXr@(&n*nc2@`<`@p+y2D<#D1^pKXpq= z{=4_F6VnXO7qldtp=)x-D}V+b9=sE(-uZvu`F~f&|1E|m`pth*0D9Bq z*asmV1+D*2c0M-wTP8m#41a9m6^Uz!GSh?K{xpA#HuKllvxsc-ADREi{71pBXI5_Z z;xYLXYW^ehXN9Uf=08&M$gjdMe^iBG{`j%thd=&+DtYAOu*u)HQW)khKZSum*pJFz z*e{*>ckNH@PwfvHpW48_urS0wec${qGCa}mSXo#8$AT%pnL7$z|DV2Z{ufQ2YXxm3 zJ72oigKL^&wR3^r{0HVgFn@hLi({KYrSs%1M|nP z!l+c(3}rKv{27=(e)!`LsQJrJVc?`N^scQGhWX1M?9ZZqZ2#DP>%<@I5B3L*&lQIF zj~_Ao!om>$ux9>ivS(K(RpA=oT~Qd?AHQ>!3Yz((FmNyOb4|lzq6_@~vHAP_)7M|T zX8!KqvJCcQR_70$rT=kO{VCvFML+dX(BJ#)Y+JQi+8q| zL;1icRdmNm9~d76p9@9*^zUEt$G?AV+n?B<*q_=z`THY3rDndgpZwQlgT_yGG=7q3 ze6RlWlwhY~o-04wUHOT1<`1!AJAd5Q?6w4rHaR-+`eHu+ca%lUp5uqFzkL16ukrl% z`Op7X{!9jgM4!PwME+h$PHoG7N4hl6KRxCjf{*VB{{#E?j`^qQj6c&AEHnOou9T~8 zw>{bi9`U(|@9asFon!DET0fjWKL0!)eZ36nT4AoZE+@+C}9GO#-ZH$*myAuaS#WDMX?EIet-kaB8woh)3Y$(S*QUg zYS@%-fcYbGIEyTd5wz1nNEzWD5P`tbLYN}i8v!)TZomv1WV36YbM8xtq$!I2NG4MY zJ-k(QPgkAleot5Ry{f%3F>$h1tBt?%^2?w$d9E(h247XxW>r;#FZf4%9K0W#&zzim zZDL}Aev{|xpf)pewc-8)^`_roJy-^dU1k5s61XBFH~AUAgJ$tM1rt-%Jwd_p|@~ewq9C zi(m@czN+8P{=;n<2<<<#04&u37Qqy>0P8Iv%70M;q6EY;SOim00@^MCGrztC+(y7G zgGDd}5%6{dGPi@9!S!GnEP|;T3IETWm5a67)Kud&@#54up?2e)d9$~akH2@9<7DZz zGDk+T|5H=fWPh#I`lz7`&}!Xu$8Pt_q1N|$r91R`MKA?*_UQFEc+MZnK5wJjm+{ckFah(DqfGj_niu{)e(p?R6Ol%V-zD6tn=_EgLu@lzrN6|3&#PNcEQ3YR{*Ufn1M$qcaW|*0MKChKLVs50n+ccy`Fxvx^V4w+y3|a{q2uA1jxag_Mdi{b#j2vPSLji zwC%qr|3&#PN=pzRVc5ik)j5irYO5k$c25y({imoKXSqUyh>`Y)EjBB=U5 z;{2_RjM(?xe=b}(<3De_bHnT{sX$Wi?gm?#bo2ZE?ActvTCJ7?q}96N3)t;0yYBQ9 zAnG3h(u)9@nwsMMue@m8s&fdvUT-_inwtX-f%f{4(g(}jKL-fy6m9!Y+y0C4UzGo% z{1?k$5lle|XuAYV1WW`>1k5s61QGCh1Tt0s<%{aSsQNFe{)=U>2qx;k3ZLqK{uqpm z?)x~@=H7Y7{x5&FY<3p%{+(tp0%SA-{o_W}*G1AGH6p?Y}7hMfoqvf3XY}!4#B$woAZ7 zz(l}Az$}AB5CN}8AXD{UzNr3-s{f+uzgPx~V5%lw$=^4j|EQmvlwGyzD;KP)`Xcx^ zct1Fw>7DtfP7u5X8#B|gy;dDFKpF%##~m1$C!N>PLXE zK>07qf3XY}!4&ibWV+SH>_4pP?zdAlPOATQb>8jGYUsjj|5?x3yI^znW(wMWw%dPE{)_Tolz>$ zK-(o?B48q5B4C!mB8dM{_$Q+N*Uz2}|F4+yQU9wY7qF_j8*F7xM1at5Uw|Y6Kmx<= zGF1Z~pA#UBsi`aWp8!CM{m`ZUi>m){WzaX5I9nCmyTMklhyQi$zf(<&h}-%cz|HDS z(|4b=OLc%nFa_;D>+QcN|3&#P%73v87QqyhfVNA(M8HJAM8GVAMG%2i7bcE8*Zq3b z|Jh5IWLs@M>VH4#f3=YJubROKka`42g8*>H_-9A}=+8j}NcEfmNzLZFdJv+*I6?h) zfuxd&{R`$|pDRgwfwNZOZ3c6Iu-{HqpV0o>)jzu5X7#bTUVUJe6}64+MehGM5g_%` zlP>u6^OKVbfX3CCT)#J6u!xRe87zV+vrgf6KNWHUAf@*FJUZ1e1B6|@Bn3dCj^NQi z_5Zj5k_#)2`gj^5g&>LFAV6||-oDn+`(Ju`|BHJ6i)F9~rl3DSdVL9q5)dUIN^O=pSIRMhl8@Yae+0FI4bOEcXBA7Dk^)v3+XiPZ( zQff_2+4o*g9jj`^0@ZT@gnn0a2SUHr)Sk-a0;#%R^;Wu>OB7g^gLfdjPBnq=I-Zjw znN>Y=p#HrT-h1Lw?tfT-NLK-pY5!^4e`x<<8QY6s3fh0R+ka64q69<# z!vaLQ3Xn|uPuu>(GS(Nt6tw?(JzYRi0-^*&35aE|2qye@VN!IfKz)-(7v8_on4Os+ z08+X|0e^|}+-AK#|HliE&e+uO;JH8I|SHEg8xR77>;2lWSS?MT;RcQ4@FE6=m z|2vBvzr&9T5O!DpsuA36>vPoUEUF2=&j3N$1(4`4i4K$KFo_Pc3>Lu@{H_7Q1++d1 z6jXq4n#T+fc8vX>k^oWvV+F{b>-4n$Uo}AR--QqVP5d|U-^72j3>Lu@#D8xmz%!5k z>^tvY4TC#D1qkjrK*Xa1gdJl4uaN*z{}Qyvi@9GMF-{+sx3;=hUi zW*IDkDTx2xj{lj*fA*dC-@OB=cA~u+bX=Gg zo*y8x&;9pahuzm9K=Ss5U~~8N@{j1c1KI6=I2U94La?dt|G9wTzY8G#oA__yzgY&0 zUcHG zEP^R0|FvBLCITh`CIV&|EP@DlJp!43(*eTS9xFihU59*LoaYe%BL7w3ECidu-5=4p1MzmwXWw<8 zo!2i07lO@>>{B7O|DyaC<-aKZ#WGj~Q_uozw}2=CQ39d_#4=a}Q&0lhE&&q(69E$e zvkVr&6hy$=5y(9L=*zsn?m&2bjv&Y#h}%5EGWiX}EQ3Wb1><#hkB7pO_va48{rC|* z^KtNgaDHI>D8Si&X#b)8hxQ+qQh-G;1uejO3y2aBB_K*bEQ3Wb1tp;E5-<@k5ik)j z%U}^qK?J-Vfz0ENzRdgU4usd|2!h;!xXmLhlRFTz3>Lu@jMv>go@b2u&mD;S^CNoZ zy1czuo_$Q_8=+<}IsWr3R-~m77!&MNLu@f^b_7fW zOax2>YBdHMu#As;_^tnT`%M1x%IM+JDH|@2G-v_S>r9_8;1RXaQ&eXaQ&e5dl|n`LDJFLNOax2>Oax2>Mo#4S|A~F0r)srJm-sPj&vIcdH=e%#-^%ULn13;PBKv>wVy-`z znm_Re;jOpw2Wa{BBX?MyyT`A%jN@G3;8`wTzQ*|5S8JoAuiF1+^VhCFozB|LN$uBp z_=WagUjFp4xVrkwKlGQi-u9{D+kYtg)VBZ7{zJ}wT@~E^L)oYOEC4MaN)|0kOoe>jK$8AN~#B0!i&kR-sUqie76a|=H{f2bKa zJWATwX8aK3?1%boLH1E^bAUkm5A8q1zM>GZFNyMBl>edxMENgDK$L)3?fjuIw|0_= zfQf*KfQf*KfO&}jyVqj;*!92W{62SXT=vzT)&DD&>OXiA_5TqrP%nReFY{j8WYm!h z7^8rkzthuKd;#0-_kJURe*eL`#vk@0K>86N%p*t=px-qG2&0bdKaXBEKID@J*EV@& z=(C5ikGh)!1loUS|HZAH#)K&UMfoqve^COW{1+u4%71g~n)^$@Z3IjNOax2>Oau;; z9ecL-pBLBv-Ql-cX=>{1r21dDuv`D%v0V4|16)-7zl#gh&ATmgL=(tZ&!{68FouC% zRe-cw@Av|CyAND{dc99djX&%~fG`X+Nr3qSy#GCa;KTDA0*_oaKAJVxzTyyQKm3wc zhCX{J`>4M;K%nc7DE~$5tBcs@-zMb0+VWqNfGGb(37GhAZfw`hwN3Ze1#}w$69H5H z=ki}WSpVhK-sNlce=ctw9Xa9otD_+>65~jl%^VVFd#RR1q} z{>x|t$mPo;4iFxh+|&nQb@j9NwEuDh$m;4_f9Ni&{^tN;KhC~hXa2y4=lKRkd*dqS z1lKlsC%}haTrlrE=9Ou4_T4U&ebnI`AR_h0?TzhJR~!FlMeG}h@?Vtyq69?wZz5oB zd{j5rHr*cq_m_azBVfw^F`XUXLdWtSU#tH)K5G}=&fDj1Xa06BI_B<-Uyu0%Ka^{X zhj{OUd9nIaJp1_cF@=)l7xy#o&zqVzpbN;nftWw=<7xLBJo(Dw&j)`xGC(x!be5^J z3?FS@Yw&uE+EM+7PdxtoMFBz~-WfLPD#(;?&mZ9ZpVU_U5%vBT<-aKZMPEQz?abEI zuC{KuKOdmvwgj|Z0-^*&35fDvC(9BagJ<_&e0H_G===XkYVF>oekS`r`}!5H9zJ-| z*B{mIA5kb-ets|WUfbNAKj3))984o7o~8DCH{4GFfyypJZ z);0I9HvTEM*Z!P;+mL|vlYl4zaj*YzA$H%@PxJSJT%ex6o%!_p=Ui@|Wc~;Q0d^ z+YRfiZRYs{l#hY7+ka^Pq5X&W@BY=+-?@Laan=2+wSUQcNDa__5)dUI%KroPe=OR& zZ|kS|J0}-vwS)dojGWN0&DuoN|9aGa@612*bMN@ie$@Z|-0fwps1};~=3F%{U%aIH zQ;z_l?e9}6koqJISO4h!2N58{)wh19`p+=X93cGYI%M1@^9O#VJ8=4~{D^Z~P|x8~|f~&q=fJ{Kp@KL+X#$|8_2KpE&i3 z+x1st9>5!U{#SZG>Oa@sh5pXt%<238N3?BLVV6JUl>l=MQ)ufaZ_wnK#h&^&>#=g7t?6i2HpU^9OeF{DCe3 zt^&;+AdmJOAeEROVeP*IasOKyKinrku1%O5Te+?Juk}X+$anWY7ykOIo(52Byb<#U z-jDeMpKH`O_oJUi{l9*f!pBmy%zFK-1saVj`zDZi*r)wpvOraRvebQ`>##pSm_IP3 z0Ac>X4b2~LVQq|wD?QbHCY~*4ezJrYv=7{YP>6>UFRNd7C*}|E{Zs7~Zs5cv|Ni9w z;lK|Ikkj~%&elsKCf`5JwXgE=sQzpFAp!E;{XgMx|M>5^-FU;zT6#a`4}8A7oc;gI z?En0>SrttRpvLcByrckWG%i^nrFT7lKz+3EqwDH291eeHf#LA2-!0$P`Unt)eXdp$ zCzwC*E6wAekgqf|(H~cPG3E~}e5U$eb$+^_`oBLwIFe3hRrMdPY{&cozW=MexSr<^ zER?!_4iNAE^#Gy%v%RzN1HFy{#Qi%DpX9dczt-;wkmLBT0)N=|AFZD_b=tvEf0YSj zw9~IYns@!??w^l;P4^xy^gO;auNRp`GV4q{Q-Y-Q;r#rJ{MlgmrnnaM|L6eWCA?q= z{nAPJl_rou0>tB=j}8zQ>gqcCKO41!OKKA!=FxW`6t}^!WJd>xNr0Hg2$19WuM<4X zf33%Vp@#nsfW{kn0vR`tdH&Z!emrG6-@Pb=9M$zAvq&cX`+uBhG~V<)0{O21z<*C5 zY*7O0+XN`FS;zLg$!~dn+-X9>$A86g6a_!C|h|3^9 zm^Wbmmo$Nl_9sg{zXSFMhzfHz&mUkO0rf+L+Sh>qvH!iL6<PcxO=@4uAQ(+>QXj=hcq=TV2WX20V{|u3qrr>AV3= zAfx@$g^ud~z5t=ge}%D<=MStfe?V=HkNT|uvHzWgMPEM!h?aeKAZmA3?Z56o>TdIY zAeoEypF0lKfA-@J#3VqD^q;!C@B1h1x!ar;-2ML9xHj9ERQ>+&R+^c_Cmx=-e)HYe z1)lF-CfRS2J!X+i=52iJemp#h`mgc-_@n?4Ul1TX{Td8rzj^8Fgj`W{{+vh)R`w#c}uR8$wKg}QVUv~g{c<8!~|1R|S zZ{oj+|04d+svqzFTYheXKtI=iO(3)Xna>Fj{BP5d)8Fd%$5jV5*k8UYKxCi-L_9n| z*uehRIR!5?6(E8F#3Vq>BLc+R(aC%kAh~@+fP5eRtA3{suO(6bx6Wq&dp98dyKdvZ z3qJmv_%F(T%^Q>dE%isBr~X_(O(53$UqOIr^+xP}uXpw|?KKLr z{nrFC+IN;R_Xh|Ym_Swsnn%DByl2;ec>`KqEfEAF0U{i@1EK%!`2TLN)OMer_;lDO zk4V%$BtX8a|N8CcKbe86_rL2l{$E!8=KbHf1o2;A;PvdubsPU(`0-y<{ZYZkK20El z8{3^+|2N$qfsXnkpdio}sLTiu{9k3l7^kvS$n!iSyCvi(;O(7wKq zxi>&q&3}bK;TKmr9iC+8MeW7-KZ+Iz0<{Sc;jjRyYdyn28>Rw|g#fYt1c>|Z4Uql* zkJa`+e;AAUGj`zn=UCJq?lbxA!G(NlClmkO*0T>4di*!>-^70t|4j*KKmGb)B48df zK=3~%kZlnJI=_vWK;{6UeVhQXFA5O$-xnZi9~B^buhX{)kp2D>t1WW`>1WW`>1WW`nw;23$7MlnV>2d^_1c(bX0pj&v50IEZMu51$Zv+Vtw;x6j z5FlpApzq5B7y+vEYl`H^t!uuARNJ}+X@h^SAduVh)IB$ zcNQ}V5VsEt5cOAp?7I%puOdLaeNTYcf9{`7yrdpuW3E5spT~cWj*YqgKtF%rM`C~f z(0f3jZosgT=$QT~haUzGo%{1@fFDE~zZi1_ce1k{#*C;?Fdq69<ymu*9eH1_%(hxdSnIgk}2qCx7u9 zh}--IV)7fve*X{Df3A!DhuQxF-~U;k{ii<4J_>U7AKHIt|DpYdvQPWje`oOax2>Oax2>Oax2>G8G``aRiwP5VaK`qJRH_3J|sR2uplA zZ-4-Cn>!Gb05J&=)6YNoi{C)p<~I=g&;6_4|M1ZKWxf4}e*Z(i|M|pZ|EY_zkAj^2 zhxQ-Ze`x=q?9+bsA6h_^fGGb(35XI9B_K*blz=D!69E$e69E$e69E$e69E%}Oa%yf z96_c6L~R9#r~nZaAfg^&iPcVw|0h7)CO}LA#3VpW0>mUhOnw8g|J;Ate`x>V)A8r8 z=SpV(S#STL{fF6q>Y?nTAZP!f{fG7++J7kfw4eQl77!&M%70M;q69<. +*/ + +package chess; + +import static org.junit.Assert.*; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class BitBoardTest { + public BitBoardTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** Test of kingAttacks, of class BitBoard. */ + @Test + public void testKingAttacks() throws ChessParseError { + System.out.println("kingAttacks"); + assertEquals(5, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("g1")])); + assertEquals(3, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("h1")])); + assertEquals(3, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("a1")])); + assertEquals(5, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("a2")])); + assertEquals(3, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("h8")])); + assertEquals(5, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("a6")])); + assertEquals(8, Long.bitCount(BitBoard.kingAttacks[TextIO.getSquare("b2")])); + } + + /** Test of knightAttacks, of class BitBoard. */ + @Test + public void testKnightAttacks() throws ChessParseError { + System.out.println("knightAttacks"); + assertEquals(3, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("g1")])); + assertEquals(2, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("a1")])); + assertEquals(2, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("h1")])); + assertEquals(4, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("h6")])); + assertEquals(4, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("b7")])); + assertEquals(8, Long.bitCount(BitBoard.knightAttacks[TextIO.getSquare("c6")])); + assertEquals((1L< 1) || (Math.abs(dy) > 1)) { + assertTrue(BitBoard.squaresBetween[sq1][sq2] != 0); + } else { + assertEquals(0, BitBoard.squaresBetween[sq1][sq2]); + } + } + } + } + } + + assertEquals(0x0040201008040200L, BitBoard.squaresBetween[0][63]); + assertEquals(0x000000001C000000L, BitBoard.squaresBetween[TextIO.getSquare("b4")][TextIO.getSquare("f4")]); + } + + /** + * If there is a piece type that can move from "from" to "to", return the + * corresponding direction, 8*dy+dx. + */ + private static final int computeDirection(int from, int to) { + int dx = Position.getX(to) - Position.getX(from); + int dy = Position.getY(to) - Position.getY(from); + if (dx == 0) { // Vertical rook direction + if (dy == 0) return 0; + return (dy > 0) ? 8 : -8; + } + if (dy == 0) // Horizontal rook direction + return (dx > 0) ? 1 : -1; + if (Math.abs(dx) == Math.abs(dy)) // Bishop direction + return ((dy > 0) ? 8 : -8) + (dx > 0 ? 1 : -1); + if (Math.abs(dx * dy) == 2) // Knight direction + return dy * 8 + dx; + return 0; + } + + @Test + public void testGetDirection() { + System.out.println("getDirection"); + for (int from = 0; from < 64; from++) { + for (int to = 0; to < 64; to++) { + assertEquals(computeDirection(from, to), BitBoard.getDirection(from, to)); + } + } + } + + @Test + public void testTrailingZeros() { + System.out.println("trailingZeros"); + for (int i = 0; i < 64; i++) { + long mask = 1L << i; + assertEquals(i, BitBoard.numberOfTrailingZeros(mask)); + } + } +} diff --git a/CuckooChessEngine/test/chess/BookTest.java b/CuckooChessEngine/test/chess/BookTest.java new file mode 100644 index 0000000..418c47a --- /dev/null +++ b/CuckooChessEngine/test/chess/BookTest.java @@ -0,0 +1,85 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class BookTest { + + public BookTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** + * Test of getBookMove method, of class Book. + */ + @Test + public void testGetBookMove() throws ChessParseError { + System.out.println("getBookMove"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Book book = new Book(true); + Move move = book.getBookMove(pos); + checkValid(pos, move); + } + + /** + * Test of getAllBookMoves method, of class Book. + */ + @Test + public void testGetAllBookMoves() throws ChessParseError { + System.out.println("getAllBookMoves"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Book book = new Book(true); + String moveListString = book.getAllBookMoves(pos); + String[] strMoves = moveListString.split("\\([0-9]*\\) "); + assertTrue(strMoves.length > 1); + for (String strMove : strMoves) { + Move m = TextIO.stringToMove(pos, strMove); + checkValid(pos, m); + } + } + + /** Check that move is a legal move in position pos. */ + private void checkValid(Position pos, Move move) { + assertTrue(move != null); + MoveGen.MoveList moveList = new MoveGen().pseudoLegalMoves(pos); + MoveGen.removeIllegal(pos, moveList); + boolean contains = false; + for (int mi = 0; mi < moveList.size; mi++) + if (moveList.m[mi].equals(move)) { + contains = true; + break; + } + assertTrue(contains); + } +} diff --git a/CuckooChessEngine/test/chess/ComputerPlayerTest.java b/CuckooChessEngine/test/chess/ComputerPlayerTest.java new file mode 100644 index 0000000..0db030a --- /dev/null +++ b/CuckooChessEngine/test/chess/ComputerPlayerTest.java @@ -0,0 +1,114 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.util.ArrayList; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class ComputerPlayerTest { + + public ComputerPlayerTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** + * Test of getCommand method, of class ComputerPlayer. + */ + @Test + public void testGetCommand() throws ChessParseError { + System.out.println("getCommand"); + ArrayList nullHist = new ArrayList(); + + Position pos = TextIO.readFEN("7k/5Q2/p5K1/8/8/8/8/8 b - - 99 80"); + ComputerPlayer cp = new ComputerPlayer(); + cp.maxDepth = 1; + cp.maxTimeMillis = -1; + cp.verbose = false; + String result = cp.getCommand(pos, false, nullHist); + assertEquals("a5", result); // Only one legal move + + pos = TextIO.readFEN("7k/5Q2/p5K1/8/8/8/8/8 b - - 100 80"); + result = cp.getCommand(pos, false, nullHist); + assertEquals("draw 50", result); // Should claim draw without making a move + + pos = TextIO.readFEN("3k4/1R6/R7/8/8/8/8/1K6 w - - 100 80"); + result = cp.getCommand(pos, false, nullHist); + assertEquals("Ra8#", result); // Can claim draw, but should not + + pos = TextIO.readFEN("8/1R5k/R7/8/8/8/B7/1K6 b - - 99 80"); + result = cp.getCommand(pos, false, nullHist); + assertEquals("draw 50 Kh8", result); // Should claim draw by 50-move rule + + // Only one possible move. Should realize that draw claim is possible, but very bad + pos = TextIO.readFEN("6Nk/8/5K1R/q7/q7/q7/8/8 b - - 100 80"); + result = cp.getCommand(pos, false, nullHist); + assertEquals("Kxg8", result); + } + + /** + * Test of draw by repetition, of class ComputerPlayer. + */ + @Test + public void testDrawRep() throws ChessParseError { + System.out.println("drawRep"); + Game game = new Game(new HumanPlayer(), new HumanPlayer()); + ComputerPlayer cp = new ComputerPlayer(); + cp.maxDepth = 3; + cp.maxTimeMillis = -1; + cp.verbose = false; + game.processString("setpos 7k/5RR1/8/8/8/8/q3q3/2K5 w - - 0 1"); + game.processString("Rh7"); + game.processString("Kg8"); + game.processString("Rhg7"); + String result = cp.getCommand(new Position(game.pos), false, game.getHistory()); + assertEquals("Kh8", result); // Not valid to claim draw here + game.processString("Kh8"); + game.processString("Rh7"); + game.processString("Kg8"); + game.processString("Rhg7"); + result = cp.getCommand(new Position(game.pos), false, game.getHistory()); + assertEquals("draw rep Kh8", result); // Can't win, but can claim draw. + + game.processString("setpos 7k/R7/1R6/8/8/8/8/K7 w - - 0 1"); + game.processString("Ra8"); + game.processString("Kh7"); + result = cp.getCommand(new Position(game.pos), false, game.getHistory()); + assertEquals("Ra7+", result); // Ra7 is mate-in-two + game.processString("Ra7"); + game.processString("Kh8"); + game.processString("Ra8"); + game.processString("Kh7"); + result = cp.getCommand(new Position(game.pos), false, game.getHistory()); + assertTrue(!result.equals("Ra7+")); // Ra7 now leads to a draw by repetition + } +} diff --git a/CuckooChessEngine/test/chess/EvaluateTest.java b/CuckooChessEngine/test/chess/EvaluateTest.java new file mode 100644 index 0000000..aff7f8b --- /dev/null +++ b/CuckooChessEngine/test/chess/EvaluateTest.java @@ -0,0 +1,492 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class EvaluateTest { + + public EvaluateTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** + * Test of evalPos method, of class Evaluate. + */ + @Test + public void testEvalPos() throws ChessParseError { + System.out.println("evalPos"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.stringToMove(pos, "e4"), ui); + pos.makeMove(TextIO.stringToMove(pos, "e5"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nf3"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nc6"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Bb5"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nge7"), ui); + assertTrue(moveScore(pos, "O-O") > 0); // Castling is good + assertTrue(moveScore(pos, "Ke2") < 0); // Losing right to castle is bad + assertTrue(moveScore(pos, "Kf1") < 0); + assertTrue(moveScore(pos, "Rg1") < 0); + assertTrue(moveScore(pos, "Rf1") < 0); + + pos = TextIO.readFEN("8/8/8/1r3k2/4pP2/4P3/8/4K2R w K - 0 1"); + assertEquals(true, pos.h1Castle()); + int cs1 = evalWhite(pos); + pos.setCastleMask(pos.getCastleMask() & ~(1 << Position.H1_CASTLE)); + assertEquals(false, pos.h1Castle()); + int cs2 = evalWhite(pos); + assertTrue(cs2 >= cs1); // No bonus for useless castle right + + // Test rook open file bonus + pos = TextIO.readFEN("r4rk1/1pp1qppp/3b1n2/4p3/2B1P1b1/1QN2N2/PP3PPP/R3R1K1 w - - 0 1"); + int ms1 = moveScore(pos, "Red1"); + int ms2 = moveScore(pos, "Rec1"); + int ms3 = moveScore(pos, "Rac1"); + int ms4 = moveScore(pos, "Rad1"); + assertTrue(ms1 > 0); // Good to have rook on open file + assertTrue(ms2 > 0); // Good to have rook on half-open file + assertTrue(ms1 > ms2); // Open file better than half-open file + assertTrue(ms3 > 0); + assertTrue(ms4 > 0); + assertTrue(ms4 > ms1); + assertTrue(ms3 > ms2); + + pos = TextIO.readFEN("r3kb1r/p3pp1p/bpPq1np1/4N3/2pP4/2N1PQ2/P1PB1PPP/R3K2R b KQkq - 0 12"); + assertTrue(moveScore(pos, "O-O-O") > 0); // Black long castle is bad for black + pos.makeMove(TextIO.stringToMove(pos, "O-O-O"), ui); + assertTrue(moveScore(pos, "O-O") > 0); // White short castle is good for white + + pos = TextIO.readFEN("8/3k4/2p5/1pp5/1P1P4/3K4/8/8 w - - 0 1"); + int sc1 = moveScore(pos, "bxc5"); + int sc2 = moveScore(pos, "dxc5"); + assertTrue(sc1 < sc2); // Don't give opponent a passed pawn. + + pos = TextIO.readFEN("8/pp1bk3/8/8/8/8/PPPBK3/8 w - - 0 1"); + sc1 = evalWhite(pos); + pos.setPiece(Position.getSquare(3, 1), Piece.EMPTY); + pos.setPiece(Position.getSquare(3, 0), Piece.WBISHOP); + sc2 = evalWhite(pos); + assertTrue(sc2 > sc1); // Easier to win if bishops on same color + + // Test bishop mobility + pos = TextIO.readFEN("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3"); + sc1 = moveScore(pos, "Bd3"); + sc2 = moveScore(pos, "Bc4"); + assertTrue(sc2 > sc1); + } + + /** + * Test of pieceSquareEval method, of class Evaluate. + */ + @Test + public void testPieceSquareEval() throws ChessParseError { + System.out.println("pieceSquareEval"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + int score = evalWhite(pos); + assertEquals(0, score); // Should be zero, by symmetry + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.stringToMove(pos, "e4"), ui); + score = evalWhite(pos); + assertTrue(score > 0); // Centralizing a pawn is a good thing + pos.makeMove(TextIO.stringToMove(pos, "e5"), ui); + score = evalWhite(pos); + assertEquals(0, score); // Should be zero, by symmetry + assertTrue(moveScore(pos, "Nf3") > 0); // Developing knight is good + pos.makeMove(TextIO.stringToMove(pos, "Nf3"), ui); + assertTrue(moveScore(pos, "Nc6") < 0); // Developing knight is good + pos.makeMove(TextIO.stringToMove(pos, "Nc6"), ui); + assertTrue(moveScore(pos, "Bb5") > 0); // Developing bishop is good + pos.makeMove(TextIO.stringToMove(pos, "Bb5"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nge7"), ui); + assertTrue(moveScore(pos, "Qe2") > 0); // Queen away from edge is good + score = evalWhite(pos); + pos.makeMove(TextIO.stringToMove(pos, "Bxc6"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nxc6"), ui); + int score2 = evalWhite(pos); + assertTrue(score2 < score); // Bishop worth more than knight in this case + + pos = TextIO.readFEN("5k2/4nppp/p1n5/1pp1p3/4P3/2P1BN2/PP3PPP/3R2K1 w - - 0 1"); + assertTrue(moveScore(pos, "Rd7") > 0); // Rook on 7:th rank is good + assertTrue(moveScore(pos, "Rd8") <= 0); // Rook on 8:th rank not particularly good + pos.setPiece(TextIO.getSquare("a1"), Piece.WROOK); + assertTrue(moveScore(pos, "Rac1") > 0); // Rook on c-f files considered good + + pos = TextIO.readFEN("r4rk1/pppRRppp/1q4b1/n7/8/2N3B1/PPP1QPPP/6K1 w - - 0 1"); + score = evalWhite(pos); + assertTrue(score > 100); // Two rooks on 7:th rank is very good + } + + /** + * Test of tradeBonus method, of class Evaluate. + */ + @Test + public void testTradeBonus() throws ChessParseError { + System.out.println("tradeBonus"); + String fen = "8/5k2/6r1/2p1p3/3p4/2P2N2/3PPP2/4K1R1 w - - 0 1"; + Position pos = TextIO.readFEN(fen); + int score1 = evalWhite(pos); + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.stringToMove(pos, "Rxg6"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Kxg6"), ui); + int score2 = evalWhite(pos); + assertTrue(score2 > score1); // White ahead, trading pieces is good + + pos = TextIO.readFEN(fen); + pos.makeMove(TextIO.stringToMove(pos, "cxd4"), ui); + pos.makeMove(TextIO.stringToMove(pos, "cxd4"), ui); + score2 = evalWhite(pos); + assertTrue(score2 < score1); // White ahead, trading pawns is bad + + pos = TextIO.readFEN("8/8/1b2b3/4kp2/5N2/4NKP1/6B1/8 w - - 0 62"); + score1 = evalWhite(pos); + pos.makeMove(TextIO.stringToMove(pos, "Nxe6"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Kxe6"), ui); + score2 = evalWhite(pos); + assertTrue(score2 > score1); // White ahead, trading pieces is good + } + + /** + * Test of material method, of class Evaluate. + */ + @Test + public void testMaterial() throws ChessParseError { + System.out.println("material"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + assertEquals(0, Evaluate.material(pos)); + + final int pV = Evaluate.pV; + final int qV = Evaluate.qV; + assertTrue(pV != 0); + assertTrue(qV != 0); + assertTrue(qV > pV); + + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.stringToMove(pos, "e4"), ui); + assertEquals(0, Evaluate.material(pos)); + pos.makeMove(TextIO.stringToMove(pos, "d5"), ui); + assertEquals(0, Evaluate.material(pos)); + pos.makeMove(TextIO.stringToMove(pos, "exd5"), ui); + assertEquals(pV, Evaluate.material(pos)); + pos.makeMove(TextIO.stringToMove(pos, "Qxd5"), ui); + assertEquals(0, Evaluate.material(pos)); + pos.makeMove(TextIO.stringToMove(pos, "Nc3"), ui); + assertEquals(0, Evaluate.material(pos)); + pos.makeMove(TextIO.stringToMove(pos, "Qxd2"), ui); + assertEquals(-pV, Evaluate.material(pos)); + pos.makeMove(TextIO.stringToMove(pos, "Qxd2"), ui); + assertEquals(-pV+qV, Evaluate.material(pos)); + } + + /** + * Test of kingSafety method, of class Evaluate. + */ + @Test + public void testKingSafety() throws ChessParseError { + System.out.println("kingSafety"); + Position pos = TextIO.readFEN("r3kb1r/p1p1pppp/b2q1n2/4N3/3P4/2N1PQ2/P2B1PPP/R3R1K1 w kq - 0 1"); + int s1 = evalWhite(pos); + pos.setPiece(TextIO.getSquare("g7"), Piece.EMPTY); + pos.setPiece(TextIO.getSquare("b7"), Piece.BPAWN); + int s2 = evalWhite(pos); + assertTrue(s2 < s1); // Half-open g-file is bad for white + + // Trapping rook with own king is bad + pos = TextIO.readFEN("rnbqk1nr/pppp1ppp/8/8/1bBpP3/8/PPP2PPP/RNBQK1NR w KQkq - 2 4"); + s1 = evalWhite(pos); + pos.setPiece(TextIO.getSquare("e1"), Piece.EMPTY); + pos.setPiece(TextIO.getSquare("f1"), Piece.WKING); + s2 = evalWhite(pos); + assertTrue(s2 < s1); + + pos = TextIO.readFEN("rnbqk1nr/pppp1ppp/8/8/1bBpPB2/8/PPP1QPPP/RN1K2NR w kq - 0 1"); + s1 = evalWhite(pos); + pos.setPiece(TextIO.getSquare("d1"), Piece.EMPTY); + pos.setPiece(TextIO.getSquare("c1"), Piece.WKING); + s2 = evalWhite(pos); + assertTrue(s2 < s1); + } + + /** + * Test of endGameEval method, of class Evaluate. + */ + @Test + public void testEndGameEval() throws ChessParseError { + System.out.println("endGameEval"); + Position pos = new Position(); + pos.setPiece(Position.getSquare(4, 1), Piece.WKING); + pos.setPiece(Position.getSquare(4, 6), Piece.BKING); + int score = evalWhite(pos); + assertEquals(0, score); + + pos.setPiece(Position.getSquare(3, 1), Piece.WBISHOP); + score = evalWhite(pos); + assertTrue(Math.abs(score) < 50); // Insufficient material to mate + + pos.setPiece(Position.getSquare(3, 1), Piece.WKNIGHT); + score = evalWhite(pos); + assertTrue(Math.abs(score) < 50); // Insufficient material to mate + + pos.setPiece(Position.getSquare(3, 1), Piece.WROOK); + score = evalWhite(pos); + final int rV = Evaluate.rV; + assertTrue(Math.abs(score) > rV + 100); // Enough material to force mate + + pos.setPiece(Position.getSquare(3, 6), Piece.BBISHOP); + score = evalWhite(pos); + final int bV = Evaluate.bV; + assertTrue(score >= 0); + assertTrue(score < rV - bV); // Insufficient excess material to mate + + pos.setPiece(Position.getSquare(5, 6), Piece.BROOK); + score = evalWhite(pos); + assertTrue(score <= 0); + assertTrue(-score < bV); + + pos.setPiece(Position.getSquare(2, 6), Piece.BBISHOP); + score = evalWhite(pos); + assertTrue(-score > bV * 2 + 100); + + // KrpKn is win for white + pos = TextIO.readFEN("8/3bk3/8/8/8/3P4/3RK3/8 w - - 0 1"); + score = evalWhite(pos); + final int pV = Evaluate.pV; + assertTrue(score > rV + pV - bV - 100); + + // KNNK is a draw + pos = TextIO.readFEN("8/8/4k3/8/8/3NK3/3N4/8 w - - 0 1"); + score = evalWhite(pos); + assertTrue(Math.abs(score) < 50); + + pos = TextIO.readFEN("8/8/3k4/8/8/3NK3/2B5/8 b - - 0 1"); + score = evalWhite(pos); + final int nV = Evaluate.nV; + assertTrue(score > bV + nV + 150); // KBNK is won, should have a bonus + score = moveScore(pos, "Kc6"); + assertTrue(score > 0); // Black king going into wrong corner, good for white + score = moveScore(pos, "Ke6"); + assertTrue(score < 0); // Black king going away from wrong corner, good for black + + // KRN vs KR is generally drawn + pos = TextIO.readFEN("rk/p/8/8/8/8/NKR/8 w - - 0 1"); + score = evalWhite(pos); + assertTrue(score < nV - 2 * pV); + } + + /** + * Test of endGameEval method, of class Evaluate. + */ + @Test + public void testPassedPawns() throws ChessParseError { + System.out.println("passedPawns"); + Position pos = TextIO.readFEN("8/8/8/P3k/8/8/p/K w"); + int score = evalWhite(pos); + assertTrue(score > 300); // Unstoppable passed pawn + pos.whiteMove = false; + score = evalWhite(pos); + assertTrue(score <= 0); // Not unstoppable + + pos = TextIO.readFEN("4R3/8/8/3K4/8/4pk2/8/8 w - - 0 1"); + score = evalWhite(pos); + pos.setPiece(TextIO.getSquare("d5"), Piece.EMPTY); + pos.setPiece(TextIO.getSquare("d4"), Piece.WKING); + int score2 = evalWhite(pos); + assertTrue(score2 > score); // King closer to passed pawn promotion square + + // Connected passed pawn test. Disabled because it didn't help in tests +// pos = TextIO.readFEN("4k3/8/8/4P3/3P1K2/8/8/8 w - - 0 1"); +// score = evalWhite(pos); +// pos.setPiece(TextIO.getSquare("d4"), Piece.EMPTY); +// pos.setPiece(TextIO.getSquare("d5"), Piece.WPAWN); +// score2 = evalWhite(pos); +// assertTrue(score2 > score); // Advancing passed pawn is good + } + + /** + * Test of endGameEval method, of class Evaluate. + */ + @Test + public void testBishAndRookPawns() throws ChessParseError { + System.out.println("bishAndRookPawns"); + final int pV = Evaluate.pV; + final int bV = Evaluate.bV; + final int winScore = pV + bV; + final int drawish = (pV + bV) / 20; + Position pos = TextIO.readFEN("k7/8/8/8/2B5/2K5/P7/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + + pos = TextIO.readFEN("k7/8/8/8/3B4/2K5/P7/8 w - - 0 1"); + assertTrue(evalWhite(pos) < drawish); + + pos = TextIO.readFEN("8/2k5/8/8/3B4/2K5/P7/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + + pos = TextIO.readFEN("8/2k5/8/8/3B4/2K4P/8/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + + pos = TextIO.readFEN("8/2k5/8/8/4B3/2K4P/8/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + + pos = TextIO.readFEN("8/6k1/8/8/4B3/2K4P/8/8 w - - 0 1"); + assertTrue(evalWhite(pos) < drawish); + + pos = TextIO.readFEN("8/6k1/8/8/4B3/2K4P/7P/8 w - - 0 1"); + assertTrue(evalWhite(pos) < drawish); + + pos = TextIO.readFEN("8/6k1/8/8/2B1B3/2K4P/7P/8 w - - 0 1"); + assertTrue(evalWhite(pos) < drawish); + + pos = TextIO.readFEN("8/6k1/8/2B5/4B3/2K4P/7P/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + + pos = TextIO.readFEN("8/6k1/8/8/4B3/2K4P/P7/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + + pos = TextIO.readFEN("8/6k1/8/8/4B3/2K3PP/8/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + } + + @Test + public void testTrappedBishop() throws ChessParseError { + Position pos = TextIO.readFEN("r2q1rk1/ppp2ppp/3p1n2/8/3P4/1P1Q1NP1/b1P2PBP/2KR3R w - - 0 1"); + assertTrue(evalWhite(pos) > 0); // Black has trapped bishop + + pos = TextIO.readFEN("r2q2k1/pp1b1p1p/2p2np1/3p4/3P4/1BNQ2P1/PPPB1P1b/2KR4 w - - 0 1"); + assertTrue(evalWhite(pos) > 0); // Black has trapped bishop + } + + /** + * Test of endGameEval method, of class Evaluate. + */ + @Test + public void testKQKP() throws ChessParseError { + System.out.println("KQKP"); + final int pV = Evaluate.pV; + final int qV = Evaluate.qV; + final int winScore = qV - pV - 200; + final int drawish = (pV + qV) / 20; + + // Pawn on a2 + Position pos = TextIO.readFEN("8/8/1K6/8/8/Q7/p7/1k6 w - - 0 1"); + assertTrue(evalWhite(pos) < drawish); + pos = TextIO.readFEN("8/8/8/1K6/8/Q7/p7/1k6 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + pos = TextIO.readFEN("3Q4/8/8/8/K7/8/1kp5/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + + // Pawn on c2 + pos = TextIO.readFEN("3Q4/8/8/8/3K4/8/1kp5/8 w - - 0 1"); + assertTrue(evalWhite(pos) < drawish); + pos = TextIO.readFEN("3Q4/8/8/8/8/4K3/1kp5/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + } + + @Test + public void testKRKP() throws ChessParseError { + System.out.println("KRKP"); + final int pV = Evaluate.pV; + final int rV = Evaluate.rV; + final int winScore = rV - pV; + final int drawish = (pV + rV) / 20; + Position pos = TextIO.readFEN("6R1/8/8/8/5K2/2kp4/8/8 w - - 0 1"); + assertTrue(evalWhite(pos) > winScore); + pos.whiteMove = !pos.whiteMove; + assertTrue(evalWhite(pos) < drawish); + } + + @Test + public void testCantWin() throws ChessParseError { + Position pos = TextIO.readFEN("8/8/8/3k4/3p4/3K4/4N3/8 w - - 0 1"); + int score1 = evalWhite(pos); + assertTrue(score1 < 0); + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.stringToMove(pos, "Nxd4"), ui); + int score2 = evalWhite(pos); + assertTrue(score2 <= 0); + assertTrue(score2 > score1); + } + + /** Return static evaluation score for white, regardless of whose turn it is to move. */ + final static int evalWhite(Position pos) { + Evaluate eval = new Evaluate(); + int ret = eval.evalPos(pos); + Position symPos = swapColors(pos); + int symScore = eval.evalPos(symPos); + assertEquals(ret, symScore); + if (!pos.whiteMove) { + ret = -ret; + } + return ret; + } + + final static Position swapColors(Position pos) { + Position sym = new Position(); + sym.whiteMove = !pos.whiteMove; + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + int p = pos.getPiece(Position.getSquare(x, y)); + p = Piece.isWhite(p) ? Piece.makeBlack(p) : Piece.makeWhite(p); + sym.setPiece(Position.getSquare(x, 7-y), p); + } + } + + int castleMask = 0; + if (pos.a1Castle()) castleMask |= 1 << Position.A8_CASTLE; + if (pos.h1Castle()) castleMask |= 1 << Position.H8_CASTLE; + if (pos.a8Castle()) castleMask |= 1 << Position.A1_CASTLE; + if (pos.h8Castle()) castleMask |= 1 << Position.H1_CASTLE; + sym.setCastleMask(castleMask); + + if (pos.getEpSquare() >= 0) { + int x = Position.getX(pos.getEpSquare()); + int y = Position.getY(pos.getEpSquare()); + sym.setEpSquare(Position.getSquare(x, 7-y)); + } + + sym.halfMoveClock = pos.halfMoveClock; + sym.fullMoveCounter = pos.fullMoveCounter; + + return sym; + } + + /** Compute change in eval score for white after making "moveStr" in position "pos". */ + private final int moveScore(Position pos, String moveStr) { + int score1 = evalWhite(pos); + Position tmpPos = new Position(pos); + UndoInfo ui = new UndoInfo(); + tmpPos.makeMove(TextIO.stringToMove(tmpPos, moveStr), ui); + int score2 = evalWhite(tmpPos); +// System.out.printf("move:%s s1:%d s2:%d\n", moveStr, score1, score2); + return score2 - score1; + } +} diff --git a/CuckooChessEngine/test/chess/GameTest.java b/CuckooChessEngine/test/chess/GameTest.java new file mode 100644 index 0000000..ae090da --- /dev/null +++ b/CuckooChessEngine/test/chess/GameTest.java @@ -0,0 +1,441 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class GameTest { + + public GameTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** + * Test of haveDrawOffer method, of class Game. + */ + @Test + public void testHaveDrawOffer() { + System.out.println("haveDrawOffer"); + Game game = new Game(new HumanPlayer(), new HumanPlayer()); + assertEquals(false, game.haveDrawOffer()); + + boolean res = game.processString("e4"); + assertEquals(true, res); + assertEquals(false, game.haveDrawOffer()); + + res = game.processString("draw offer e5"); + assertEquals(true, res); + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw offer does not imply draw + assertEquals(Piece.BPAWN, game.pos.getPiece(Position.getSquare(4, 4))); // e5 move made + + res = game.processString("draw offer Nf3"); + assertEquals(true, res); + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw offer does not imply draw + assertEquals(Piece.WKNIGHT, game.pos.getPiece(Position.getSquare(5, 2))); // Nf3 move made + + res = game.processString("Nc6"); + assertEquals(true, res); + assertEquals(false, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + assertEquals(Piece.BKNIGHT, game.pos.getPiece(Position.getSquare(2, 5))); // Nc6 move made + + res = game.processString("draw offer Bb5"); + assertEquals(true, res); + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + assertEquals(Piece.WBISHOP, game.pos.getPiece(Position.getSquare(1, 4))); // Bb5 move made + + res = game.processString("draw accept"); + assertEquals(true, res); + assertEquals(Game.GameState.DRAW_AGREE, game.getGameState()); // Draw by agreement + + res = game.processString("undo"); + assertEquals(true, res); + assertEquals(Piece.EMPTY, game.pos.getPiece(Position.getSquare(1, 4))); // Bb5 move undone + assertEquals(false, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + res = game.processString("undo"); + assertEquals(true, res); + assertEquals(Piece.EMPTY, game.pos.getPiece(Position.getSquare(2, 5))); // Nc6 move undone + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + + res = game.processString("redo"); + assertEquals(true, res); + assertEquals(Piece.BKNIGHT, game.pos.getPiece(Position.getSquare(2, 5))); // Nc6 move redone + assertEquals(false, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + res = game.processString("redo"); + assertEquals(true, res); + assertEquals(Piece.WBISHOP, game.pos.getPiece(Position.getSquare(1, 4))); // Bb5 move redone + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + res = game.processString("redo"); + assertEquals(true, res); + assertEquals(true, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Can't redo draw accept + + // Test draw offer in connection with invalid move + res = game.processString("new"); + assertEquals(true, res); + assertEquals(false, game.haveDrawOffer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + + res = game.processString("draw offer e5"); + assertEquals(true, res); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos)); // Move invalid, not executed + res = game.processString("e4"); + assertEquals(true, res); + assertEquals(true, game.haveDrawOffer()); // Previous draw offer still valid + assertEquals(Piece.WPAWN, game.pos.getPiece(Position.getSquare(4, 3))); // e4 move made + + // Undo/redo shall clear "pendingDrawOffer". + game.processString("new"); + game.processString("e4"); + game.processString("draw offer e4"); // Invalid black move + assertEquals(true, game.pendingDrawOffer); + game.processString("undo"); + game.processString("redo"); + game.processString("e5"); + assertEquals(true,game.pos.whiteMove); + assertEquals(false, game.haveDrawOffer()); + } + + /** + * Test of draw by 50 move rule, of class Game. + */ + @Test + public void testDraw50() { + System.out.println("draw50"); + Game game = new Game(new HumanPlayer(), new HumanPlayer()); + assertEquals(false, game.haveDrawOffer()); + boolean res = game.processString("draw 50"); + assertEquals(true, res); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw claim invalid + res = game.processString("e4"); + assertEquals(true, game.haveDrawOffer()); // Invalid claim converted to draw offer + + String cmd = "setpos 8/4k3/8/P7/8/8/8/1N2K2R w K - 99 83"; + res = game.processString(cmd); + assertEquals(true, res); + res = game.processString("draw 50"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw claim invalid + + game.processString(cmd); + game.processString("draw 50 Nc3"); + assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Draw claim valid + assertEquals("Game over, draw by 50 move rule! [Nc3]", game.getGameStateString()); + + game.processString(cmd); + game.processString("draw 50 a6"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Pawn move resets counter + assertEquals(Piece.WPAWN, game.pos.getPiece(Position.getSquare(0, 5))); // Move a6 made + + game.processString(cmd); + game.processString("draw 50 O-O"); + assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Castling doesn't reset counter + + game.processString(cmd); + game.processString("draw 50 Kf2"); + assertEquals(Game.GameState.DRAW_50, game.getGameState()); // Loss of castling right doesn't reset counter + + game.processString(cmd); + game.processString("draw 50 Ke3"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Ke3 is invalid + assertEquals(true,game.pos.whiteMove); + game.processString("a6"); + assertEquals(true, game.haveDrawOffer()); // Previous invalid claim converted to offer + game.processString("draw 50"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // 50 move counter reset. + res = game.processString("draw accept"); + assertEquals(true, res); + assertEquals(Game.GameState.DRAW_AGREE, game.getGameState()); // Can accept previous implicit offer + + cmd = "setpos 3k4/R7/3K4/8/8/8/8/8 w - - 99 78"; + game.processString(cmd); + game.processString("Ra8"); + assertEquals(Game.GameState.WHITE_MATE, game.getGameState()); + game.processString("draw 50"); + assertEquals(Game.GameState.WHITE_MATE, game.getGameState()); // Can't claim draw when game over + assertEquals(Game.GameState.ALIVE, game.drawState); + } + + /** + * Test of draw by repetition, of class Game. + */ + @Test + public void testDrawRep() { + System.out.println("drawRep"); + Game game = new Game(new HumanPlayer(), new HumanPlayer()); + assertEquals(false, game.haveDrawOffer()); + game.processString("Nc3"); + game.processString("Nc6"); + game.processString("Nb1"); + game.processString("Nb8"); + game.processString("Nf3"); + game.processString("Nf6"); + game.processString("Ng1"); + assertEquals(false, game.haveDrawOffer()); + game.processString("draw rep"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Claim not valid, one more move needed + game.processString("draw rep Nc6"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); // Claim not valid, wrong move claimed + assertEquals(Piece.BKNIGHT, game.pos.getPiece(Position.getSquare(2, 5))); // Move Nc6 made + assertEquals(true, game.haveDrawOffer()); + game.processString("undo"); + assertEquals(false, game.haveDrawOffer()); + assertEquals(Piece.EMPTY, game.pos.getPiece(Position.getSquare(2, 5))); + game.processString("draw rep Ng8"); + assertEquals(Game.GameState.DRAW_REP, game.getGameState()); + assertEquals(Piece.EMPTY, game.pos.getPiece(Position.getSquare(6, 7))); // Ng8 not played + + // Test draw by repetition when a "potential ep square but not real ep square" position is present. + game.processString("new"); + game.processString("e4"); // e3 is not a real epSquare here + game.processString("Nf6"); + game.processString("Nf3"); + game.processString("Ng8"); + game.processString("Ng1"); + game.processString("Nf6"); + game.processString("Nf3"); + game.processString("Ng8"); + game.processString("draw rep Ng1"); + assertEquals(Game.GameState.DRAW_REP, game.getGameState()); + + // Now check the case when e3 *is* an epSquare + game.processString("new"); + game.processString("Nf3"); + game.processString("d5"); + game.processString("Ng1"); + game.processString("d4"); + game.processString("e4"); // Here e3 is a real epSquare + game.processString("Nf6"); + game.processString("Nf3"); + game.processString("Ng8"); + game.processString("Ng1"); + game.processString("Nf6"); + game.processString("Nf3"); + game.processString("Ng8"); + game.processString("draw rep Ng1"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + + // EP capture not valid because it would leave the king in check. Therefore + // the position has been repeated three times at the end of the move sequence. + game.processString("setpos 4k2n/8/8/8/4p3/8/3P4/3KR2N w - - 0 1"); + game.processString("d4"); + game.processString("Ng6"); + game.processString("Ng3"); + game.processString("Nh8"); + game.processString("Nh1"); + game.processString("Ng6"); + game.processString("Ng3"); + game.processString("Nh8"); + game.processString("draw rep Nh1"); + assertEquals(Game.GameState.DRAW_REP, game.getGameState()); + } + + /** + * Test of resign command, of class Game. + */ + @Test + public void testResign() { + System.out.println("resign"); + Game game = new Game(new HumanPlayer(), new HumanPlayer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("f3"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("resign"); + assertEquals(Game.GameState.RESIGN_BLACK, game.getGameState()); + game.processString("undo"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("f3"); + game.processString("e5"); + game.processString("resign"); + assertEquals(Game.GameState.RESIGN_WHITE, game.getGameState()); + game.processString("undo"); + game.processString("e5"); + game.processString("g4"); + game.processString("Qh4"); + assertEquals(Game.GameState.BLACK_MATE, game.getGameState()); + game.processString("resign"); + assertEquals(Game.GameState.BLACK_MATE, game.getGameState()); // Can't resign after game over + } + + /** + * Test of processString method, of class Game. + */ + @Test + public void testProcessString() throws ChessParseError { + System.out.println("processString"); + Game game = new Game(new HumanPlayer(), new HumanPlayer()); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos)); + boolean res = game.processString("Nf3"); + assertEquals(true, res); + assertEquals(1, game.pos.halfMoveClock); + assertEquals(1, game.pos.fullMoveCounter); + res = game.processString("d5"); + assertEquals(true, res); + assertEquals(0, game.pos.halfMoveClock); + assertEquals(2, game.pos.fullMoveCounter); + + res = game.processString("undo"); + assertEquals(true, res); + assertEquals(1, game.pos.halfMoveClock); + assertEquals(1, game.pos.fullMoveCounter); + res = game.processString("undo"); + assertEquals(true, res); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos)); + res = game.processString("undo"); + assertEquals(true, res); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos)); + + res = game.processString("redo"); + assertEquals(true, res); + assertEquals(1, game.pos.halfMoveClock); + assertEquals(1, game.pos.fullMoveCounter); + res = game.processString("redo"); + assertEquals(true, res); + assertEquals(0, game.pos.halfMoveClock); + assertEquals(2, game.pos.fullMoveCounter); + res = game.processString("redo"); + assertEquals(true, res); + assertEquals(0, game.pos.halfMoveClock); + assertEquals(2, game.pos.fullMoveCounter); + + res = game.processString("new"); + assertEquals(true, res); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.pos)); + + String fen = "8/8/8/4k3/8/8/2p5/5K2 b - - 47 68"; + Position pos = TextIO.readFEN(fen); + res = game.processString("setpos " + fen); + assertEquals(true, res); + assertEquals(pos, game.pos); + + res = game.processString("junk"); + assertEquals(false, res); + } + + /** + * Test of getGameState method, of class Game. + */ + @Test + public void testGetGameState() { + System.out.println("getGameState"); + Game game = new Game(new HumanPlayer(), new HumanPlayer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("f3"); + game.processString("e5"); + game.processString("g4"); + game.processString("Qh4"); + assertEquals(Game.GameState.BLACK_MATE, game.getGameState()); + + game.processString("setpos 5k2/5P2/5K2/8/8/8/8/8 b - - 0 1"); + assertEquals(Game.GameState.BLACK_STALEMATE, game.getGameState()); + } + + /** + * Test of insufficientMaterial method, of class Game. + */ + @Test + public void testInsufficientMaterial() { + System.out.println("insufficientMaterial"); + Game game = new Game(new HumanPlayer(), new HumanPlayer()); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.processString("setpos 4k3/8/8/8/8/8/8/4K3 w - - 0 1"); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + final int a1 = Position.getSquare(0, 0); + game.pos.setPiece(a1, Piece.WROOK); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.pos.setPiece(a1, Piece.BQUEEN); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.pos.setPiece(a1, Piece.WPAWN); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.pos.setPiece(a1, Piece.BKNIGHT); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + game.pos.setPiece(a1, Piece.WBISHOP); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + + final int c1 = Position.getSquare(2, 0); + game.pos.setPiece(c1, Piece.WKNIGHT); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + game.pos.setPiece(c1, Piece.BBISHOP); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + game.pos.setPiece(c1, Piece.WBISHOP); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + + final int b2 = Position.getSquare(1, 1); + game.pos.setPiece(b2, Piece.WBISHOP); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + game.pos.setPiece(b2, Piece.BBISHOP); + assertEquals(Game.GameState.DRAW_NO_MATE, game.getGameState()); + + final int b3 = Position.getSquare(1, 2); + game.pos.setPiece(b3, Piece.WBISHOP); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + + // Can't force mate with KNNK, but still not an automatic draw. + game.processString("setpos 8/8/8/8/8/8/8/K3nnk1 w - - 0 1"); + assertEquals(Game.GameState.ALIVE, game.getGameState()); + } + + /** + * Test of perfT method, of class Game. + */ + @Test + public void testPerfT() { + System.out.println("perfT"); + Game game = new Game(new HumanPlayer(), new HumanPlayer()); + game.processString("new"); + doTestPerfT(game.pos, 5, new long[]{20,400,8902,197281,4865609,119060324,3195901860L,84998978956L}); + + game.processString("setpos 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -"); + doTestPerfT(game.pos, 5, new long[]{14, 191, 2812, 43238, 674624, 11030083, 178633661}); + + game.processString("setpos r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -"); + doTestPerfT(game.pos, 4, new long[]{48,2039,97862,4085603,193690690}); + } + + private void doTestPerfT(Position pos, int maxDepth, long[] expectedNodeCounts) { + for (int d = 1; d <= maxDepth; d++) { + MoveGen moveGen = new MoveGen(); + long t0 = System.nanoTime(); + long nodes = Game.perfT(moveGen, pos, d); + long t1 = System.nanoTime(); + System.out.printf("perft(%d) = %d, t=%.6fs\n", d, nodes, (t1 - t0)*1e-9); + assertEquals(expectedNodeCounts[d-1], nodes); + } + } +} diff --git a/CuckooChessEngine/test/chess/HistoryTest.java b/CuckooChessEngine/test/chess/HistoryTest.java new file mode 100644 index 0000000..684a181 --- /dev/null +++ b/CuckooChessEngine/test/chess/HistoryTest.java @@ -0,0 +1,85 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class HistoryTest { + + public HistoryTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of getHistScore method, of class History. + */ + @Test + public void testGetHistScore() throws ChessParseError { + System.out.println("getHistScore"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + History hs = new History(); + Move m1 = TextIO.stringToMove(pos, "e4"); + Move m2 = TextIO.stringToMove(pos, "d4"); + assertEquals(0, hs.getHistScore(pos, m1)); + + hs.addSuccess(pos, m1, 1); + assertEquals(1 * 49 / 1, hs.getHistScore(pos, m1)); + assertEquals(0, hs.getHistScore(pos, m2)); + + hs.addSuccess(pos, m1, 1); + assertEquals(1 * 49 / 1, hs.getHistScore(pos, m1)); + assertEquals(0, hs.getHistScore(pos, m2)); + + hs.addFail(pos, m1, 1); + assertEquals(2 * 49 / 3, hs.getHistScore(pos, m1)); + assertEquals(0, hs.getHistScore(pos, m2)); + + hs.addFail(pos, m1, 1); + assertEquals(2 * 49 / 4, hs.getHistScore(pos, m1)); + assertEquals(0, hs.getHistScore(pos, m2)); + + hs.addSuccess(pos, m2, 1); + assertEquals(2 * 49 / 4, hs.getHistScore(pos, m1)); + assertEquals(1 * 49 / 1, hs.getHistScore(pos, m2)); + } +} diff --git a/CuckooChessEngine/test/chess/KillerTableTest.java b/CuckooChessEngine/test/chess/KillerTableTest.java new file mode 100644 index 0000000..4b38b4e --- /dev/null +++ b/CuckooChessEngine/test/chess/KillerTableTest.java @@ -0,0 +1,95 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class KillerTableTest { + + public KillerTableTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** + * Test of addKiller method, of class KillerTable. + */ + @Test + public void testAddKiller() { + System.out.println("addKiller"); + KillerTable kt = new KillerTable(); + Move m = new Move(TextIO.getSquare("b1"), TextIO.getSquare("b5"), Piece.EMPTY); + kt.addKiller(3, m); + kt.addKiller(7, m); + kt.addKiller(3, m); + kt.addKiller(3, m); + } + + /** + * Test of getKillerScore method, of class KillerTable. + */ + @Test + public void testGetKillerScore() { + System.out.println("getKillerScore"); + KillerTable kt = new KillerTable(); + Move m1 = new Move(TextIO.getSquare("b1"), TextIO.getSquare("b5"), Piece.EMPTY); + Move m2 = new Move(TextIO.getSquare("c1"), TextIO.getSquare("d2"), Piece.EMPTY); + Move m3 = new Move(TextIO.getSquare("e1"), TextIO.getSquare("g1"), Piece.EMPTY); + kt.addKiller(0, m1); + assertEquals(4, kt.getKillerScore(0, m1)); + assertEquals(0, kt.getKillerScore(0, m2)); + assertEquals(0, kt.getKillerScore(0, new Move(m2))); + kt.addKiller(0, m1); + assertEquals(4, kt.getKillerScore(0, m1)); + kt.addKiller(0, m2); + assertEquals(4, kt.getKillerScore(0, m2)); + assertEquals(4, kt.getKillerScore(0, new Move(m2))); // Must compare by value + assertEquals(3, kt.getKillerScore(0, m1)); + kt.addKiller(0, new Move(m2)); + assertEquals(4, kt.getKillerScore(0, m2)); + assertEquals(3, kt.getKillerScore(0, m1)); + assertEquals(0, kt.getKillerScore(0, m3)); + kt.addKiller(0, m3); + assertEquals(0, kt.getKillerScore(0, m1)); + assertEquals(3, kt.getKillerScore(0, m2)); + assertEquals(4, kt.getKillerScore(0, m3)); + + assertEquals(0, kt.getKillerScore(1, m3)); + assertEquals(2, kt.getKillerScore(2, m3)); + assertEquals(0, kt.getKillerScore(3, m3)); + assertEquals(0, kt.getKillerScore(4, m3)); + + kt.addKiller(2, m2); + assertEquals(4, kt.getKillerScore(2, m2)); + assertEquals(3, kt.getKillerScore(0, m2)); + } +} diff --git a/CuckooChessEngine/test/chess/MoveGenTest.java b/CuckooChessEngine/test/chess/MoveGenTest.java new file mode 100644 index 0000000..b7023e4 --- /dev/null +++ b/CuckooChessEngine/test/chess/MoveGenTest.java @@ -0,0 +1,543 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.util.ArrayList; +import java.util.List; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class MoveGenTest { + + public MoveGenTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** + * Test of pseudoLegalMoves method, of class MoveGen. + */ + @Test + public void testPseudoLegalMoves() throws ChessParseError { + System.out.println("pseudoLegalMoves"); + String fen = "8/3k4/8/2n2pP1/1P6/1NB5/2QP4/R3K2R w KQ f6 0 2"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + List strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("a1d1")); + assertTrue(!strMoves.contains("a1e1")); + assertTrue(!strMoves.contains("a1f1")); + assertTrue(strMoves.contains("a1a7")); + assertTrue(strMoves.contains("e1f2")); + assertTrue(!strMoves.contains("e1g3")); + assertTrue(strMoves.contains("c3f6")); + assertTrue(!strMoves.contains("b3d2")); + + // Test castling + assertTrue(strMoves.contains("e1g1")); + assertTrue(strMoves.contains("e1c1")); + assertEquals(49, strMoves.size()); + + pos.setPiece(Position.getSquare(4,3), Piece.BROOK); + strMoves = getMoveList(pos, false); + assertTrue(!strMoves.contains("e1g1")); // In check, no castling possible + assertTrue(!strMoves.contains("e1c1")); + + pos.setPiece(Position.getSquare(4, 3), Piece.EMPTY); + pos.setPiece(Position.getSquare(5, 3), Piece.BROOK); + strMoves = getMoveList(pos, false); + assertTrue(!strMoves.contains("e1g1")); // f1 attacked, short castle not possible + assertTrue(strMoves.contains("e1c1")); + + pos.setPiece(Position.getSquare(5, 3), Piece.EMPTY); + pos.setPiece(Position.getSquare(6, 3), Piece.BBISHOP); + strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("e1g1")); // d1 attacked, long castle not possible + assertTrue(!strMoves.contains("e1c1")); + + pos.setPiece(Position.getSquare(6, 3), Piece.EMPTY); + pos.setCastleMask(1 << Position.A1_CASTLE); + strMoves = getMoveList(pos, false); + assertTrue(!strMoves.contains("e1g1")); // short castle right has been lost + assertTrue(strMoves.contains("e1c1")); + } + + /** + * Test of pseudoLegalMoves method, of class MoveGen. Pawn moves. + */ + @Test + public void testPawnMoves() throws ChessParseError { + System.out.println("pawnMoves"); + String fen = "1r2k3/P1pppp1p/8/1pP3p1/1nPp2P1/n4p1P/1P2PP2/4KBNR w K b6 0 1"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + List strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("c5b6")); // En passant capture + assertTrue(strMoves.contains("a7a8q")); // promotion + assertTrue(strMoves.contains("a7a8n")); // under promotion + assertTrue(strMoves.contains("a7b8r")); // capture promotion + assertTrue(strMoves.contains("b2b3")); // pawn single move + assertTrue(strMoves.contains("b2a3")); // pawn capture to the left + assertTrue(strMoves.contains("e2e4")); // pawn double move + assertTrue(strMoves.contains("e2f3")); // pawn capture to the right + assertEquals(22, strMoves.size()); + + pos.setEpSquare(-1); + strMoves = getMoveList(pos, false); + assertEquals(21, strMoves.size()); // No ep, one less move possible + + // Check black pawn moves + pos.setWhiteMove(false); + strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("f3e2")); + assertTrue(strMoves.contains("d4d3")); + assertTrue(strMoves.contains("e7e6")); + assertTrue(strMoves.contains("e7e5")); + assertEquals(28, strMoves.size()); + + // Check black pawn promotion + pos.setPiece(Position.getSquare(0,1), Piece.BPAWN); + strMoves = getMoveList(pos, false); + assertTrue(strMoves.contains("a2a1q")); + assertTrue(strMoves.contains("a2a1r")); + assertTrue(strMoves.contains("a2a1n")); + assertTrue(strMoves.contains("a2a1b")); + } + + /** + * Test of inCheck method, of class MoveGen. + */ + @Test + public void testInCheck() { + System.out.println("inCheck"); + Position pos = new Position(); + pos.setPiece(Position.getSquare(4,2), Piece.WKING); + pos.setPiece(Position.getSquare(4,7), Piece.BKING); + assertEquals(false, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(3,3), Piece.BQUEEN); + assertEquals(true, MoveGen.inCheck(pos)); + pos.setPiece(Position.getSquare(3,3), Piece.BROOK); + assertEquals(false, MoveGen.inCheck(pos)); + pos.setPiece(Position.getSquare(3,3), Piece.BPAWN); + assertEquals(true, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(3,3), Piece.EMPTY); + pos.setPiece(Position.getSquare(5,3), Piece.WQUEEN); + assertEquals(false, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(4, 6), Piece.BROOK); + assertEquals(true, MoveGen.inCheck(pos)); + pos.setPiece(Position.getSquare(4, 4), Piece.WPAWN); + assertEquals(false, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(2, 3), Piece.BKNIGHT); + assertEquals(true, MoveGen.inCheck(pos)); + + pos.setPiece(Position.getSquare(2, 3), Piece.EMPTY); + pos.setPiece(Position.getSquare(0, 4), Piece.BKNIGHT); + assertEquals(false, MoveGen.inCheck(pos)); + } + + /** + * Test of givesCheck method, of class MoveGen. + */ + @Test + public void testGivesCheck() throws ChessParseError { + System.out.println("givesCheck"); + Position pos = new Position(); + UndoInfo ui = new UndoInfo(); + pos.setPiece(TextIO.getSquare("e3"), Piece.WKING); + pos.setPiece(TextIO.getSquare("e8"), Piece.BKING); + pos.setPiece(TextIO.getSquare("c2"), Piece.WROOK); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Rc8"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Rc6"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Rc7"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Re2"))); + + pos.setPiece(TextIO.getSquare("c2"), Piece.EMPTY); + pos.setPiece(TextIO.getSquare("e2"), Piece.WROOK); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kd3"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kd4"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Ke4"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kf2"))); + + pos.setPiece(TextIO.getSquare("e4"), Piece.WBISHOP); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Bd5"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Bc6"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kd3"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Re1"))); + + pos = TextIO.readFEN("4k3/3p4/8/8/4B3/2K5/4R3/8 w - - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Bc6"))); + pos = TextIO.readFEN("4k3/8/5K2/8/6N1/8/8/8 w - - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Ke6"))); + assertTrue(!MoveGen.givesCheck(pos, new Move(TextIO.getSquare("f6"), + TextIO.getSquare("e7"), + Piece.EMPTY))); + + pos = TextIO.readFEN("8/2k5/8/4N3/8/2K3B1/8/8 w - - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Kc4"))); + pos.setPiece(TextIO.getSquare("g3"), Piece.WROOK); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7"))); + pos.setPiece(TextIO.getSquare("g3"), Piece.WQUEEN); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7"))); + pos.setPiece(TextIO.getSquare("g3"), Piece.WKNIGHT); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7"))); + pos.setPiece(TextIO.getSquare("g3"), Piece.WPAWN); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7"))); + pos.setPiece(TextIO.getSquare("c3"), Piece.EMPTY); + pos.setPiece(TextIO.getSquare("g3"), Piece.WKING); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7"))); + + pos = TextIO.readFEN("8/2k5/3p4/4N3/8/2K3B1/8/8 w - - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7"))); + + pos = TextIO.readFEN("8/2k5/8/4N3/8/6q1/2K5/8 w - - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7"))); + pos = TextIO.readFEN("8/2k5/8/4N3/8/8/2K5/8 w - - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "Nf7"))); + pos = TextIO.readFEN("2nk4/3P4/8/8/3R4/8/2K5/8 w - - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc8N"))); + + pos = TextIO.readFEN("8/2k5/2p5/1P1P4/8/2K5/8/8 w - - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "d6"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "bxc6"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b6"))); + + pos = TextIO.readFEN("8/8/R1PkP2R/8/8/2K5/8/8 w - - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c7"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e7"))); + + // Test pawn promotion + pos = TextIO.readFEN("8/1P6/2kP4/8/8/2K5/8/8 w - - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "d7"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b8Q"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b8N"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b8R"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "b8B"))); + + pos = TextIO.readFEN("8/2P1P3/2k5/8/8/2K5/8/8 w - - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e8Q"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e8N"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e8R"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "e8B"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c8Q"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c8N"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c8R"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c8B"))); + + // Test castling + pos = TextIO.readFEN("8/8/8/8/5k2/8/8/R3K2R w KQ - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O"))); + pos = TextIO.readFEN("8/8/8/8/6k1/8/8/R3K2R w KQ - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O"))); + pos = TextIO.readFEN("8/8/8/8/3k4/8/8/R3K2R w KQ - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O"))); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O"))); + pos = TextIO.readFEN("8/8/8/8/5k2/8/5P2/R3K2R w KQ - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O"))); + pos = TextIO.readFEN("8/8/8/8/8/8/8/R3K2k w Q - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O"))); + pos = TextIO.readFEN("8/8/8/8/8/8/8/2k1K2R w K - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O"))); + pos.setPiece(TextIO.getSquare("d1"), Piece.WKNIGHT); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O"))); + + // Test en passant + pos = TextIO.readFEN("8/1kp5/8/3P4/8/8/8/4K3 b - - 0 1"); + pos.makeMove(TextIO.stringToMove(pos, "c5"), ui); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6"))); + + pos = TextIO.readFEN("3k4/2p5/8/3P4/8/8/3R4/4K3 b - - 0 1"); + pos.makeMove(TextIO.stringToMove(pos, "c5"), ui); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6"))); + + pos = TextIO.readFEN("5k2/2p5/8/3P4/8/B7/8/4K3 b - - 0 1"); + pos.makeMove(TextIO.stringToMove(pos, "c5"), ui); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6"))); + + pos = TextIO.readFEN("5k2/2p5/8/3P4/1P6/B7/8/4K3 b - - 0 1"); + pos.makeMove(TextIO.stringToMove(pos, "c5"), ui); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6"))); + + pos = TextIO.readFEN("8/2p5/8/R2P1k2/8/8/8/4K3 b - - 0 1"); + pos.makeMove(TextIO.stringToMove(pos, "c5"), ui); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "dxc6"))); + + // Black pawn moves + pos = TextIO.readFEN("8/2p5/8/R4k2/1K6/8/8/8 b - - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c5"))); + pos = TextIO.readFEN("8/2p5/8/R4k2/2K5/8/8/8 b - - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c5"))); + pos = TextIO.readFEN("8/2p5/8/R4k2/3K4/8/8/8 b - - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "c5"))); + + // Black castling + pos = TextIO.readFEN("r3k2r/8/8/5K2/8/8/8/8 b kq - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O"))); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O"))); + pos = TextIO.readFEN("r3k2r/8/8/6K1/8/8/8/8 b kq - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O"))); + pos = TextIO.readFEN("r3k2r/8/8/2K5/8/8/8/8 b kq - 0 1"); + assertTrue(!MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O"))); + pos = TextIO.readFEN("r3k2r/8/8/3K4/8/8/8/8 b kq - 0 1"); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "O-O-O"))); + + // Black en passant + pos = TextIO.readFEN("8/8/4k3/8/4p3/8/5PK1/8 w - - 0 1"); + pos.makeMove(TextIO.stringToMove(pos, "f4"), ui); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "exf3"))); + + pos = TextIO.readFEN("8/8/4k3/8/K3p1r1/8/5P2/8 w - - 0 1"); + pos.makeMove(TextIO.stringToMove(pos, "f4"), ui); + assertTrue(MoveGen.givesCheck(pos, TextIO.stringToMove(pos, "exf3"))); + } + + /** + * Test of removeIllegal method, of class MoveGen. + */ + @Test + public void testRemoveIllegal() throws ChessParseError { + System.out.println("removeIllegal"); + Position pos = TextIO.readFEN("8/3k4/8/2n1rpP1/1P6/1NB5/2QP4/R3K2R w KQ f6 0 1"); + List strMoves = getMoveList(pos, true); + assertTrue(strMoves.contains("c2e4")); + assertTrue(strMoves.contains("c3e5")); + assertTrue(strMoves.contains("e1d1")); + assertTrue(strMoves.contains("e1f1")); + assertTrue(strMoves.contains("e1f2")); + assertEquals(5, strMoves.size()); + + pos = TextIO.readFEN("4k3/8/8/2KPp1r1/8/8/8/8 w - e6 0 2"); + strMoves = getMoveList(pos, true); + assertTrue(!strMoves.contains("d5e6")); + assertEquals(7, strMoves.size()); + + pos = TextIO.readFEN("8/6p1/4p3/2k1Pp1B/4KP1p/6rP/8/8 w - f6 0 55"); + strMoves = getMoveList(pos, true); + assertTrue(strMoves.contains("e5f6")); + assertEquals(1, strMoves.size()); + } + + /** + * Test that if king capture is possible, only a king capture move is returned in the move list. + */ + @Test + public void testKingCapture() throws ChessParseError { + System.out.println("kingCapture"); + Position pos = TextIO.readFEN("8/4k3/8/8/8/8/8/4RK2 b - - 0 1"); + pos.setWhiteMove(true); + List strMoves = getMoveList(pos, false); + assertEquals(1, strMoves.size()); + assertEquals("e1e7", strMoves.get(0)); + + pos.setPiece(Position.getSquare(0, 2), Piece.WBISHOP); + pos.setPiece(Position.getSquare(4, 1), Piece.WPAWN); + strMoves = getMoveList(pos, false); + assertEquals(1, strMoves.size()); + assertEquals("a3e7", strMoves.get(0)); + + pos.setPiece(Position.getSquare(1, 3), Piece.WPAWN); + pos.setPiece(Position.getSquare(5, 5), Piece.WPAWN); + strMoves = getMoveList(pos, false); + assertEquals(1, strMoves.size()); + assertEquals("f6e7", strMoves.get(0)); + } + + /** Test that captureList and captureAndcheckList are generated correctly. */ + @Test + public void testCaptureList() throws ChessParseError { + System.out.println("captureList"); + Position pos = TextIO.readFEN("rnbqkbnr/ppp2ppp/3p1p2/R7/4N3/8/PPPPQPPP/2B1KB1R w Kkq - 0 1"); + getMoveList(pos, false); + + pos = TextIO.readFEN("rnb1kbn1/ppp1qppp/5p2/4p3/3N3r/3P4/PPP2PPP/R1BQKB1R b KQq - 0 1"); + getMoveList(pos, false); + + pos = TextIO.readFEN("rnb1k1n1/ppp1qppp/5p2/b3p3/1r1N4/3P4/PPP2PPP/R1BQKB1R b KQq - 0 1"); + getMoveList(pos, false); + + pos = TextIO.readFEN("8/8/8/8/3k4/8/4P3/4K3 w - - 0 1"); + getMoveList(pos, false); + + pos = TextIO.readFEN("8/8/8/3k4/8/8/4P3/4K3 w - - 0 1"); + getMoveList(pos, false); + + pos = TextIO.readFEN("8/8/8/3k4/4p3/8/3KP3/8 b - - 0 1"); + getMoveList(pos, false); + + pos = TextIO.readFEN("3k4/r2p2K1/8/8/8/8/8/8 b - - 0 1"); + getMoveList(pos, false); + } + + @Test + public void testCheckEvasions() throws ChessParseError { + System.out.println("checkEvasions"); + Position pos = TextIO.readFEN("n7/8/8/7k/5pP1/5K2/8/8 b - g3 0 1"); + getMoveList(pos, false); + + pos = TextIO.readFEN("rn1qkbnr/pppB1ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 0 1"); + getMoveList(pos, false); + + // King captures must be included in check evasions + pos = TextIO.readFEN("r1bq2r1/pp3pbk/2p1p1P1/8/3P4/2PB1N2/PP3PPR/2KR4 b - - 0 1"); + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.uciStringToMove("g7h6"), ui); + getMoveList(pos, false); + List evList = getCheckEvasions(pos, false); + assertTrue(evList.contains("g6h7")); + + pos = TextIO.readFEN("1R6/1brk2p1/2P1p2p/p3Pp2/P7/6P1/1P4P1/2R3K1 b - - 0 1"); + getMoveList(pos, false); + evList = getCheckEvasions(pos, false); + assertTrue(evList.contains("b7c6")); + } + + private List getMoveList(Position pos, boolean onlyLegal) { + Position swap = EvaluateTest.swapColors(pos); + List swapList = getMoveList0(swap, onlyLegal); + List ret = getMoveList0(pos, onlyLegal); + assertEquals(swapList.size(), ret.size()); + // FIXME! Test that swapList contains swapped moves compared to ret + return ret; + } + + private List getMoveList0(Position pos, boolean onlyLegal) { + MoveGen moveGen = new MoveGen(); + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + if (onlyLegal) + MoveGen.removeIllegal(pos, moves); + ArrayList strMoves = new ArrayList(); + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + String mStr = TextIO.moveToUCIString(m); + strMoves.add(mStr); + } + + List capList1 = getCaptureList(pos, false, onlyLegal); + assertTrue(strMoves.containsAll(capList1)); + + List capList2 = getCaptureList(pos, true, onlyLegal); + assertTrue(strMoves.containsAll(capList2)); + + List evList = getCheckEvasions(pos, onlyLegal); + if (evList != null) + assertTrue(strMoves.containsAll(evList)); + UndoInfo ui = new UndoInfo(); + for (String sm : strMoves) { + Move m = TextIO.uciStringToMove(sm); + if (m != null) { + pos.makeMove(m, ui); + boolean invalid = MoveGen.canTakeKing(pos); + pos.unMakeMove(m, ui); + if (invalid) m = null; + } + if (m == null) // Move was illegal (but pseudo-legal) + continue; + boolean qProm = false; // Promotion types considered in qsearch + switch (m.promoteTo) { + case Piece.WQUEEN: case Piece.BQUEEN: + case Piece.WKNIGHT: case Piece.BKNIGHT: + case Piece.EMPTY: + qProm = true; + break; + default: + break; + } + if (!MoveGen.canTakeKing(pos) && MoveGen.givesCheck(pos, m)) { + if (qProm) + assertTrue(capList2.contains(sm)); + } else { + switch (m.promoteTo) { + case Piece.WQUEEN: case Piece.BQUEEN: + case Piece.WKNIGHT: case Piece.BKNIGHT: + assertTrue(capList1.contains(sm)); // All queen/knight promotions + assertTrue(capList2.contains(sm)); // All queen/knight promotions + break; + case Piece.EMPTY: + break; + default: + assertTrue(!capList1.contains(sm)); // No rook/bishop promotions + assertTrue(!capList2.contains(sm)); // No rook/bishop promotions + break; + } + } + if (pos.getPiece(m.to) != Piece.EMPTY) { + if (qProm) { + assertTrue(capList1.contains(sm)); + assertTrue(capList2.contains(sm)); + } + } + if (evList != null) { + assertTrue(evList.contains(sm)); + } + } + + return strMoves; + } + + private List getCaptureList(Position pos, boolean includeChecks, boolean onlyLegal) { + MoveGen.MoveList moves; + if (includeChecks) { + moves = new MoveGen().pseudoLegalCapturesAndChecks(pos); + } else { + moves = new MoveGen().pseudoLegalCaptures(pos); + } + if (onlyLegal) + MoveGen.removeIllegal(pos, moves); + ArrayList strMoves = new ArrayList(); + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + String mStr = TextIO.moveToUCIString(m); + strMoves.add(mStr); + } + return strMoves; + } + + private List getCheckEvasions(Position pos, boolean onlyLegal) { + if (!MoveGen.inCheck(pos)) + return null; + MoveGen.MoveList moves = new MoveGen().checkEvasions(pos); + if (onlyLegal) + MoveGen.removeIllegal(pos, moves); + ArrayList strMoves = new ArrayList(); + for (int mi = 0; mi < moves.size; mi++) { + Move m = moves.m[mi]; + String mStr = TextIO.moveToUCIString(m); + strMoves.add(mStr); + } + return strMoves; + } +} diff --git a/CuckooChessEngine/test/chess/MoveTest.java b/CuckooChessEngine/test/chess/MoveTest.java new file mode 100644 index 0000000..827ed29 --- /dev/null +++ b/CuckooChessEngine/test/chess/MoveTest.java @@ -0,0 +1,84 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class MoveTest { + + public MoveTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of move constructor, of class Move. + */ + @Test + public void testMoveConstructor() { + System.out.println("MoveTest"); + int f = Position.getSquare(4, 1); + int t = Position.getSquare(4, 3); + int p = Piece.WROOK; + Move move = new Move(f, t, p); + assertEquals(move.from, f); + assertEquals(move.to,t); + assertEquals(move.promoteTo, p); + } + + /** + * Test of equals, of class Move. + */ + @Test + public void testEquals() { + System.out.println("equals"); + Move m1 = new Move(Position.getSquare(0, 6), Position.getSquare(1, 7), Piece.WROOK); + Move m2 = new Move(Position.getSquare(0, 6), Position.getSquare(0, 7), Piece.WROOK); + Move m3 = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WROOK); + Move m4 = new Move(Position.getSquare(0, 6), Position.getSquare(1, 7), Piece.WKNIGHT); + Move m5 = new Move(Position.getSquare(0, 6), Position.getSquare(1, 7), Piece.WROOK); + assertTrue(!m1.equals(m2)); + assertTrue(!m1.equals(m3)); + assertTrue(!m1.equals(m4)); + assertTrue(m1.equals(m5)); + } +} diff --git a/CuckooChessEngine/test/chess/PieceTest.java b/CuckooChessEngine/test/chess/PieceTest.java new file mode 100644 index 0000000..b6cdd44 --- /dev/null +++ b/CuckooChessEngine/test/chess/PieceTest.java @@ -0,0 +1,54 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class PieceTest { + + public PieceTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** + * Test of isWhite method, of class Piece. + */ + @Test + public void testIsWhite() { + System.out.println("isWhite"); + assertEquals(false, Piece.isWhite(Piece.BBISHOP)); + assertEquals(true , Piece.isWhite(Piece.WBISHOP)); + assertEquals(true , Piece.isWhite(Piece.WKING)); + assertEquals(false, Piece.isWhite(Piece.BKING)); + } +} diff --git a/CuckooChessEngine/test/chess/PositionTest.java b/CuckooChessEngine/test/chess/PositionTest.java new file mode 100644 index 0000000..fb1a08f --- /dev/null +++ b/CuckooChessEngine/test/chess/PositionTest.java @@ -0,0 +1,444 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import java.util.ArrayList; +import java.util.List; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class PositionTest { + + public PositionTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of getPiece method, of class Position. + */ + @Test + public void testGetPiece() throws ChessParseError { + System.out.println("getPiece"); + Position pos = new Position(); + int result = pos.getPiece(0); + assertEquals(result, Piece.EMPTY); + + pos = TextIO.readFEN(TextIO.startPosFEN); + result = pos.getPiece(0); + assertEquals(result, Piece.WROOK); + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 2; y++) { + int p1 = pos.getPiece(Position.getSquare(x, y)); + int p2 = pos.getPiece(Position.getSquare(x, 7-y)); + int bwDiff = Piece.BPAWN - Piece.WPAWN; + assertEquals(p2, p1 + bwDiff); + } + } + } + + /** + * Test of getIndex method, of class Position. + */ + @Test + public void testGetIndex() { + System.out.println("getIndex"); + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + int sq = Position.getSquare(x, y); + int x2 = Position.getX(sq); + int y2 = Position.getY(sq); + assertEquals(x, x2); + assertEquals(y, y2); + } + } + } + + /** + * Test of setPiece method, of class Position. + */ + @Test + public void testSetPiece() { + System.out.println("setPiece"); + Position instance = new Position(); + assertEquals(Piece.EMPTY, instance.getPiece(Position.getSquare(0, 0))); + instance.setPiece(Position.getSquare(3, 4), Piece.WKING); + assertEquals(Piece.WKING, instance.getPiece(Position.getSquare(3, 4))); + } + + /** + * Test of makeMove method, of class Position. + */ + @Test + public void testMakeMove() throws ChessParseError { + System.out.println("makeMove"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Position origPos = new Position(pos); + assertTrue(pos.equals(origPos)); + Move move = new Move(Position.getSquare(4,1), Position.getSquare(4,3), Piece.EMPTY); + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(pos.whiteMove, false); + assertEquals(-1, pos.getEpSquare()); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(4,1))); + assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(4,3))); + assertTrue(!pos.equals(origPos)); + int castleMask = (1 << Position.A1_CASTLE) | + (1 << Position.H1_CASTLE) | + (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + pos.unMakeMove(move, ui); + assertEquals(pos.whiteMove, true); + assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(4,1))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(4,3))); + assertTrue(pos.equals(origPos)); + + String fen = "r1bqk2r/2ppbppp/p1n2n2/1pP1p3/B3P3/5N2/PP1P1PPP/RNBQK2R w KQkq b6 0 2"; + pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + origPos = new Position(pos); + assertEquals(Position.getSquare(1,5), pos.getEpSquare()); + + // Test capture + move = new Move(Position.getSquare(0, 3), Position.getSquare(1,4), Piece.EMPTY); + pos.makeMove(move, ui); + assertEquals(-1, pos.getEpSquare()); + assertEquals(Piece.WBISHOP, pos.getPiece(Position.getSquare(1,4))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(0,3))); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test castling + move = new Move(Position.getSquare(4, 0), Position.getSquare(6,0), Piece.EMPTY); + pos.makeMove(move, ui); + assertEquals(Piece.WROOK, pos.getPiece(Position.getSquare(5,0))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(7,0))); + castleMask = (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + assertEquals(-1, pos.getEpSquare()); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test castling rights (king move) + move = new Move(Position.getSquare(4, 0), Position.getSquare(4,1), Piece.EMPTY); + pos.makeMove(move, ui); + castleMask = (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + assertEquals(-1, pos.getEpSquare()); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test castling rights (rook move) + move = new Move(Position.getSquare(7, 0), Position.getSquare(6,0), Piece.EMPTY); + pos.makeMove(move, ui); + castleMask = (1 << Position.A1_CASTLE) | + (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + assertEquals(-1, pos.getEpSquare()); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test en passant + move = new Move(Position.getSquare(2, 4), Position.getSquare(1,5), Piece.EMPTY); + pos.makeMove(move, ui); + assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(1,5))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(2,4))); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,4))); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos)); + + // Test castling rights loss when rook captured + pos.setPiece(Position.getSquare(6,2), Piece.BKNIGHT); + pos.setWhiteMove(false); + Position origPos2 = new Position(pos); + move = new Move(Position.getSquare(6,2), Position.getSquare(7,0), Piece.EMPTY); + pos.makeMove(move, ui); + castleMask = (1 << Position.A1_CASTLE) | + (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask,pos.getCastleMask()); + assertEquals(-1, pos.getEpSquare()); + pos.unMakeMove(move, ui); + assertTrue(pos.equals(origPos2)); + } + + @Test + public void testCastleMask() throws ChessParseError { + System.out.println("castleMask"); + Position pos = TextIO.readFEN("rnbqk1nr/pppp1ppp/8/4p3/4P3/2N2N2/PPPP1bPP/R1BQKB1R w KQkq - 0 1"); + UndoInfo ui = new UndoInfo(); + Move m = TextIO.stringToMove(pos, "Kxf2"); + pos.makeMove(m, ui); + int castleMask = (1 << Position.A8_CASTLE) | + (1 << Position.H8_CASTLE); + assertEquals(castleMask, pos.getCastleMask()); + } + + /** + * Test of makeMove method, of class Position. + */ + @Test + public void testPromotion() throws ChessParseError { + System.out.println("promotion"); + String fen = "r1bqk2r/1Pppbppp/p1n2n2/2P1p3/B3P3/5N2/Pp1P1PPP/R1BQK2R w KQkq - 0 1"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + Position origPos = new Position(pos); + assertEquals(origPos, pos); + + Move move = new Move(Position.getSquare(1, 6), Position.getSquare(0,7), Piece.WQUEEN); + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,6))); + assertEquals(Piece.WQUEEN, pos.getPiece(Position.getSquare(0,7))); + pos.unMakeMove(move, ui); + assertEquals(origPos, pos); + + move = new Move(Position.getSquare(1, 6), Position.getSquare(1,7), Piece.WKNIGHT); + ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,6))); + assertEquals(Piece.WKNIGHT, pos.getPiece(Position.getSquare(1,7))); + pos.unMakeMove(move, ui); + assertEquals(origPos, pos); + + pos.setWhiteMove(false); + origPos = new Position(pos); + + move = new Move(Position.getSquare(1, 1), Position.getSquare(2, 0), Piece.BROOK); + ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,1))); + assertEquals(Piece.BROOK, pos.getPiece(Position.getSquare(2,0))); + pos.unMakeMove(move, ui); + assertEquals(origPos, pos); + } + + /** + * Test move counters, of class Position. + */ + @Test + public void testMoveCounters() throws ChessParseError { + System.out.println("moveCounters"); + String fen = "r1bqk2r/2ppbppp/p1n2n2/1pP1p3/B3P3/5N2/PP1P1PPP/RNBQK2R w KQkq b6 0 7"; + Position pos = TextIO.readFEN(fen); + + Move move = TextIO.stringToMove(pos, "Nc3"); + UndoInfo ui = new UndoInfo(); + pos.makeMove(move, ui); + assertEquals(1, pos.halfMoveClock); + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "O-O"); + pos.makeMove(move, ui); + assertEquals(1, pos.halfMoveClock); // Castling does not reset 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "a3"); + pos.makeMove(move, ui); + assertEquals(0, pos.halfMoveClock); // Pawn move resets 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "Nxe5"); + pos.makeMove(move, ui); + assertEquals(0, pos.halfMoveClock); // Capture move resets 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "cxb6"); + pos.makeMove(move, ui); + assertEquals(0, pos.halfMoveClock); // EP capture move resets 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + move = TextIO.stringToMove(pos, "Kf1"); + pos.makeMove(move, ui); + assertEquals(1, pos.halfMoveClock); // Loss of castling rights does not reset 50 move counter + assertEquals(7, pos.fullMoveCounter); + pos.unMakeMove(move, ui); + + Move firstMove = TextIO.stringToMove(pos, "Nc3"); + UndoInfo firstUi = new UndoInfo(); + pos.makeMove(move, firstUi); + move = TextIO.stringToMove(pos, "O-O"); + pos.makeMove(move, ui); + assertEquals(2, pos.halfMoveClock); + assertEquals(8, pos.fullMoveCounter); // Black move increases fullMoveCounter + pos.unMakeMove(move, ui); + pos.unMakeMove(firstMove, firstUi); + + fen = "8/8/8/4k3/8/8/2p5/5K2 b - - 47 68"; + pos = TextIO.readFEN(fen); + move = TextIO.stringToMove(pos, "c1Q"); + pos.makeMove(move, ui); + assertEquals(0, pos.halfMoveClock); // Pawn promotion resets 50 move counter + assertEquals(69, pos.fullMoveCounter); + } + + /** + * Test of drawRuleEquals, of class Position. + */ + @Test + public void testDrawRuleEquals() throws ChessParseError { + System.out.println("drawRuleEquals"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Position origPos = new Position(pos); + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.stringToMove(pos, "Nf3"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Nf6"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Ng1"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Ng8"), ui); + assertEquals(true, pos.drawRuleEquals(origPos)); + assertEquals(false, pos.equals(origPos)); // Move counters have changed + + String fen = "r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1"; + pos = TextIO.readFEN(fen); + origPos = new Position(pos); + pos.makeMove(TextIO.stringToMove(pos, "Ke2"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Be7"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Ke1"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); + pos.makeMove(TextIO.stringToMove(pos, "Bf8"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); // Not equal, castling rights lost + + pos = TextIO.readFEN(TextIO.startPosFEN); + pos.makeMove(TextIO.stringToMove(pos, "c4"), ui); + pos.makeMove(TextIO.stringToMove(pos, "a6"), ui); + pos.makeMove(TextIO.stringToMove(pos, "c5"), ui); + pos.makeMove(TextIO.stringToMove(pos, "b5"), ui); + assertEquals(Position.getSquare(1, 5), pos.getEpSquare()); + origPos = new Position(pos); + pos.makeMove(TextIO.stringToMove(pos, "Nc3"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nc6"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nb1"), ui); + pos.makeMove(TextIO.stringToMove(pos, "Nb8"), ui); + assertEquals(false, pos.drawRuleEquals(origPos)); // Not equal, en passant rights lost + } + + /** + * Test of hashCode method, of class Position. + */ + @Test + public void testHashCode() throws ChessParseError { + System.out.println("hashCode"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + long h1 = pos.zobristHash(); + assertEquals(h1, pos.computeZobristHash()); + UndoInfo ui = new UndoInfo(); + Move move = TextIO.stringToMove(pos, "e4"); + pos.makeMove(move, ui); + assertTrue(h1 != pos.zobristHash()); + pos.unMakeMove(move, ui); + assertTrue(h1 == pos.zobristHash()); + + pos.setWhiteMove(!pos.whiteMove); + long h4 = pos.zobristHash(); + assertEquals(h4, pos.computeZobristHash()); + assertTrue(h1 != pos.zobristHash()); + pos.setWhiteMove(!pos.whiteMove); + assertTrue(h1 == pos.zobristHash()); + + pos.setCastleMask(0); + assertTrue(h1 != pos.zobristHash()); + + pos = TextIO.readFEN("rnbqkbnr/pppp1ppp/8/2P1p3/8/8/PP1PPPPP/RNBQKBNR b KQkq - 0 1"); + h1 = pos.zobristHash(); + assertEquals(h1, pos.computeZobristHash()); + + String[] moves = { + "b5", "Nc3", "Nf6", "Nb1", "Ng8", "Nc3", "Nf6", "Nb1", "Ng8", "Nc3", "d5", + "cxd6", "Qxd6", "h4", "Be6", "h5", "Nc6", "h6", "o-o-o", "hxg7", "Nf6", "gxh8Q", "Be7" + }; + List uiList = new ArrayList(); + List hashList = new ArrayList(); + List moveList = new ArrayList(); + for (int i = 0; i < moves.length; i++) { + uiList.add(new UndoInfo()); + Move m = TextIO.stringToMove(pos, moves[i]); + moveList.add(m); + pos.makeMove(m, uiList.get(i)); + long h = pos.zobristHash(); + assertEquals(h, pos.computeZobristHash()); + hashList.add(h); + } + assertTrue(!hashList.get(0).equals(hashList.get(4))); + assertTrue(hashList.get(4).equals(hashList.get(8))); + for (int i = moves.length - 1; i >= 0; i--) { + pos.unMakeMove(moveList.get(i), uiList.get(i)); + long h = pos.zobristHash(); + assertEquals(h, pos.computeZobristHash()); + assertEquals(h, i > 0 ? hashList.get(i - 1) : h1); + } + } + + /** + * Test of getKingSq method, of class Position. + */ + @Test + public void testGetKingSq() throws ChessParseError { + System.out.println("getKingSq"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + assertEquals(TextIO.getSquare("e1"), pos.getKingSq(true)); + assertEquals(TextIO.getSquare("e8"), pos.getKingSq(false)); + pos = TextIO.readFEN("r1bq1bnr/ppppkppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQ - 0 4"); + assertEquals(TextIO.getSquare("e1"), pos.getKingSq(true)); + assertEquals(TextIO.getSquare("e7"), pos.getKingSq(false)); + UndoInfo ui = new UndoInfo(); + pos.makeMove(TextIO.stringToMove(pos, "o-o"), ui); + assertEquals(TextIO.getSquare("g1"), pos.getKingSq(true)); + assertEquals(TextIO.getSquare("e7"), pos.getKingSq(false)); + pos.makeMove(TextIO.stringToMove(pos, "Kd6"), ui); + assertEquals(TextIO.getSquare("g1"), pos.getKingSq(true)); + assertEquals(TextIO.getSquare("d6"), pos.getKingSq(false)); + } +} diff --git a/CuckooChessEngine/test/chess/SearchTest.java b/CuckooChessEngine/test/chess/SearchTest.java new file mode 100644 index 0000000..2c4bfc3 --- /dev/null +++ b/CuckooChessEngine/test/chess/SearchTest.java @@ -0,0 +1,461 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import chess.Search.StopSearch; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class SearchTest { + static final long[] nullHist = new long[200]; + static TranspositionTable tt = new TranspositionTable(19); + + public SearchTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** + * Test of negaScout method, of class Search. + */ + @Test + public void testNegaScout() throws ChessParseError, StopSearch { + System.out.println("negaScout"); + final int mate0 = Search.MATE0; + + Position pos = TextIO.readFEN("3k4/8/3K2R1/8/8/8/8/8 w - - 0 1"); + Search sc = new Search(pos, nullHist, 0, tt); + final int plyScale = Search.plyScale; + int score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos)); + assertEquals(mate0 - 2, score); // depth 2 is enough to find mate in 1 + int score2 = idSearch(sc, 2).score; + assertEquals(score, score2); + + pos = TextIO.readFEN("8/1P6/k7/2K5/8/8/8/8 w - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + score = sc.negaScout(-mate0, mate0, 0, 4*plyScale, -1, MoveGen.inCheck(pos)); + assertEquals(mate0 - 4, score); // depth 4 is enough to find mate in 2 + score2 = idSearch(sc, 4).score; + assertEquals(score, score2); + + pos = TextIO.readFEN("8/5P1k/5K2/8/8/8/8/8 w - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + score = sc.negaScout(-mate0, mate0, 0, 5*plyScale, -1, MoveGen.inCheck(pos)); + assertEquals(mate0 - 4, score); // must avoid stale-mate after f8Q + score2 = idSearch(sc, 5).score; + assertEquals(score, score2); + + pos = TextIO.readFEN("4k3/8/3K1Q2/8/8/8/8/8 b - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos)); + assertEquals(0, score); // Position is stale-mate + + pos = TextIO.readFEN("3kB3/8/1N1K4/8/8/8/8/8 w - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + score = sc.negaScout(-mate0, mate0, 0, 3*plyScale, -1, MoveGen.inCheck(pos)); + assertTrue(Math.abs(score) < 50); // Stale-mate trap + score2 = idSearch(sc, 5).score; + assertEquals(score, score2); + + pos = TextIO.readFEN("8/8/2K5/3QP3/P6P/1q6/8/k7 w - - 31 51"); + sc = new Search(pos, nullHist, 0, tt); + Move bestM = idSearch(sc, 2); + assertTrue(!TextIO.moveToString(pos, bestM, false).equals("Qxb3")); + } + + /** + * Test of draw by 50 move rule, of class Search. + */ + @Test + public void testDraw50() throws ChessParseError, StopSearch { + System.out.println("draw50"); + final int mate0 = Search.MATE0; + final int mateInOne = mate0 - 2; + final int matedInOne = -mate0 + 3; + final int mateInTwo = mate0 - 4; + final int mateInThree = mate0 - 6; + + Position pos = TextIO.readFEN("8/1R2k3/R7/8/8/8/8/1K6 b - - 0 1"); + Search sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + final int plyScale = Search.plyScale; + int score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos)); + assertEquals(matedInOne, score); + + pos = TextIO.readFEN("8/1R2k3/R7/8/8/8/8/1K6 b - - 99 80"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos)); + assertEquals(0, score); // Draw by 50-move rule + + pos = TextIO.readFEN("8/1R2k3/R7/8/8/8/8/1K6 b - - 98 80"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = sc.negaScout(-mate0, mate0, 0, 2*plyScale, -1, MoveGen.inCheck(pos)); + assertEquals(matedInOne, score); // No draw + + pos = TextIO.readFEN("8/1R2k3/R7/8/8/8/8/1K6 b - - 99 80"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = idSearch(sc, 3).score; + assertEquals(0, score); + + pos = TextIO.readFEN("3k4/1R6/R7/8/8/8/8/1K6 w - - 100 80"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = idSearch(sc, 2).score; + assertEquals(mateInOne, score); // Black forgot to claim draw. Now it's too late. + + pos = TextIO.readFEN("8/7k/1R6/R7/8/7P/8/1K6 w - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = idSearch(sc, 3).score; + assertEquals(mateInTwo, score); + + pos = TextIO.readFEN("8/7k/1R6/R7/8/7P/8/1K6 w - - 98 1"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = idSearch(sc, 6).score; + assertEquals(mateInThree, score); // Need an extra pawn move to avoid 50-move rule + + pos = TextIO.readFEN("8/7k/1R6/R7/8/7P/8/1K6 w - - 125 1"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = idSearch(sc, 6).score; + assertEquals(mateInThree, score); // Need an extra pawn move to avoid 50-move rule + } + + /** + * Test of draw by repetition rule, of class Search. + */ + @Test + public void testDrawRep() throws ChessParseError, StopSearch { + System.out.println("drawRep"); + final int mate0 = Search.MATE0; + Position pos = TextIO.readFEN("7k/5RR1/8/8/8/8/q3q3/2K5 w - - 0 1"); + Search sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + final int plyScale = Search.plyScale; + int score = sc.negaScout(-mate0, mate0, 0, 3*plyScale, -1, MoveGen.inCheck(pos)); + assertEquals(0, score); + + pos = TextIO.readFEN("7k/5RR1/8/8/8/8/q3q3/2K5 w - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = idSearch(sc, 3).score; + assertEquals(0, score); + + pos = TextIO.readFEN("7k/5RR1/8/8/8/8/1q3q2/3K4 w - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = idSearch(sc, 3).score; + assertTrue(score < 0); + + pos = TextIO.readFEN("7k/5RR1/8/8/8/8/1q3q2/3K4 w - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = sc.negaScout(-mate0, mate0, 0, 3*plyScale, -1, MoveGen.inCheck(pos)); + assertTrue(score < 0); + + pos = TextIO.readFEN("qn6/qn4k1/pp3R2/5R2/8/8/8/K7 w - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + sc.maxTimeMillis = -1; + score = idSearch(sc, 7).score; + assertEquals(0, score); // Draw, black can not escape from perpetual checks + } + + /** + * Test of hash table, of class Search. + */ + @Test + public void testHashing() throws ChessParseError { + System.out.println("hashing"); + Position pos = TextIO.readFEN("/k/3p/p2P1p/P2P1P///K/ w - -"); // Fine #70 + Search sc = new Search(pos, nullHist, 0, tt); + Move bestM = idSearch(sc, 28); + assertEquals(TextIO.stringToMove(pos, "Kb1"), new Move(bestM)); + } + + @Test + public void testCheckEvasion() throws ChessParseError { + System.out.println("check evasion"); + Position pos = TextIO.readFEN("6r1/R5PK/2p5/1k6/8/8/p7/8 b - - 0 62"); + Search sc = new Search(pos, nullHist, 0, tt); + Move bestM = idSearch(sc, 3); + assertTrue(bestM.score < 0); + + pos = TextIO.readFEN("r1bq2rk/pp3pbp/2p1p1pQ/7P/3P4/2PB1N2/PP3PPR/2KR4 w - -"); // WAC 004 + sc = new Search(pos, nullHist, 0, tt); + bestM = idSearch(sc, 1); + assertEquals(Search.MATE0 - 4, bestM.score); + assertEquals(TextIO.stringToMove(pos, "Qxh7+"), new Move(bestM)); + } + + @Test + public void testStalemateTrap() throws ChessParseError { + System.out.println("stalemate trap"); + Position pos = TextIO.readFEN("7k/1P3R1P/6r1/5K2/8/8/6R1/8 b - - 98 194"); + Search sc = new Search(pos, nullHist, 0, tt); + Move bestM = idSearch(sc, 3); + assertEquals(0, bestM.score); + } + + @Test + public void testKQKRNullMove() throws ChessParseError { + System.out.println("kqkrNullMove"); + Position pos = TextIO.readFEN("7K/6R1/5k2/3q4/8/8/8/8 b - - 0 1"); + Search sc = new Search(pos, nullHist, 0, tt); + Move bestM = idSearch(sc, 9); + assertEquals(Search.MATE0-18, bestM.score); + } + + private Move idSearch(Search sc, int maxDepth) { + MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(sc.pos); + MoveGen.removeIllegal(sc.pos, moves); + sc.scoreMoveList(moves, 0); + sc.timeLimit(-1, -1); + Move bestM = sc.iterativeDeepening(moves, maxDepth, -1, false); + return bestM; + } + + /** Compute SEE(m) and assure that signSEE and negSEE give matching results. */ + private int getSEE(Search sc, Move m) { + int see = sc.SEE(m); + boolean neg = sc.negSEE(m); + assertEquals(neg, see < 0); + int sign = sc.signSEE(m); + if (sign > 0) + assertTrue(see > 0); + else if (sign == 0) + assertEquals(0, see); + else + assertTrue(see < 0); + return see; + } + + /** + * Test of SEE method, of class Search. + */ + @Test + public void testSEE() throws ChessParseError { + System.out.println("SEE"); + final int pV = Evaluate.pV; + final int nV = Evaluate.nV; + final int bV = Evaluate.bV; + final int rV = Evaluate.rV; + + // Basic tests + Position pos = TextIO.readFEN("r2qk2r/ppp2ppp/1bnp1nb1/1N2p3/3PP3/1PP2N2/1P3PPP/R1BQRBK1 w kq - 0 1"); + Search sc = new Search(pos, nullHist, 0, tt); + assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "dxe5"))); + assertEquals(pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxe5"))); + assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa7"))); + assertEquals(pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxa7"))); + assertEquals(pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxd6"))); + assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "d5"))); + assertEquals(-bV, getSEE(sc, TextIO.stringToMove(pos, "Bf4"))); + assertEquals(-bV, getSEE(sc, TextIO.stringToMove(pos, "Bh6"))); + assertEquals(-rV, getSEE(sc, TextIO.stringToMove(pos, "Ra5"))); + assertEquals(-rV, getSEE(sc, TextIO.stringToMove(pos, "Ra6"))); + + pos.setWhiteMove(false); + sc = new Search(pos, nullHist, 0, tt); + assertTrue(nV <= bV); // Assumed by following test + assertEquals(pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxd4"))); + assertEquals(pV - bV, getSEE(sc, TextIO.stringToMove(pos, "Bxd4"))); + assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "exd4"))); + assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Nxe4"))); + assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Bxe4"))); + assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "d5"))); + assertEquals(-nV, getSEE(sc, TextIO.stringToMove(pos, "Nd5"))); + assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "a6"))); + + // Test X-ray attacks + pos = TextIO.readFEN("3r2k1/pp1q1ppp/1bnr1nb1/1Np1p3/1P1PP3/2P1BN2/1Q1R1PPP/3R1BK1 b - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + // 1 1 1 1 3 3 3 3 3 5 5 9 5 5 + // 0 1 0 1 0 3 0 3 0 5 0 9 0 5 + assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "exd4"))); + // 1 3 1 1 3 1 3 3 5 5 5 9 9 + //-1 2 1 0 3 0 3 0 5 0 5 0 9 + assertEquals(2 * pV - nV, getSEE(sc, TextIO.stringToMove(pos, "Nxd4"))); + + pos.setPiece(TextIO.getSquare("b2"), Piece.EMPTY); // Remove white queen + sc = new Search(pos, nullHist, 0, tt); + // 1 1 1 1 3 3 3 3 3 5 5 9 5 + // 0 1 0 1 0 3 0 3 0 4 1 4 5 + assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "exd4"))); + assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "cxb4"))); + + pos.setPiece(TextIO.getSquare("b5"), Piece.EMPTY); // Remove white knight + sc = new Search(pos, nullHist, 0, tt); + // 1 1 1 1 3 3 3 3 5 5 5 + // 1 0 1 0 3 0 3 0 5 0 5 + assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "exd4"))); + + pos.setPiece(TextIO.getSquare("b2"), Piece.WQUEEN); // Restore white queen + sc = new Search(pos, nullHist, 0, tt); + // 1 1 1 1 3 3 3 3 5 5 5 9 9 + // 1 0 1 0 3 0 3 0 5 0 5 0 9 + assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "exd4"))); + + pos.setPiece(TextIO.getSquare("b6"), Piece.EMPTY); // Remove black bishop + pos.setPiece(TextIO.getSquare("c6"), Piece.EMPTY); // Remove black knight + sc = new Search(pos, nullHist, 0, tt); + assertEquals(-pV, getSEE(sc, TextIO.stringToMove(pos, "a5"))); + + // Test EP capture + pos = TextIO.readFEN("2b3k1/1p3ppp/8/pP6/8/2PB4/5PPP/6K1 w - a6 0 2"); + sc = new Search(pos, nullHist, 0, tt); + // 1 1 1 3 + // 0 1 0 3 + assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "bxa6"))); + + pos.setPiece(TextIO.getSquare("b7"), Piece.EMPTY); // Remove black pawn + sc = new Search(pos, nullHist, 0, tt); + // 1 1 3 + // 1 0 3 + assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "bxa6"))); + + + // Test king capture + pos = TextIO.readFEN("8/8/8/4k3/r3P3/4K3/8/4R3 b - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + // 1 5 99 + // 1 0 99 + assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Rxe4+"))); + + pos = TextIO.readFEN("8/8/8/4k3/r3P1R1/4K3/8/8 b - - 0 1"); + final int kV = Evaluate.kV; + sc = new Search(pos, nullHist, 0, tt); + // 1 5 5 99 + //-4 5 0 99 + assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxe4+"))); + // 1 99 + //-98 99 + assertEquals(pV - kV, getSEE(sc, new Move(TextIO.getSquare("e5"), TextIO.getSquare("e4"), Piece.EMPTY))); + + pos = TextIO.readFEN("8/8/4k3/8/r3P3/4K3/8/8 b - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxe4+"))); + + // Test king too far away + pos = TextIO.readFEN("8/8/4k3/8/r3P3/8/4K3/8 b - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Rxe4+"))); + + pos = TextIO.readFEN("8/3k4/8/8/r1K5/8/8/2R5 w - - 0 1"); + pos.setWhiteMove(false); + sc = new Search(pos, nullHist, 0, tt); + assertEquals(kV, getSEE(sc, new Move(TextIO.getSquare("a4"), TextIO.getSquare("c4"), Piece.EMPTY))); + + + // Test blocking pieces + pos = TextIO.readFEN("r7/p2k4/8/r7/P7/8/4K3/R7 b - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); // Ra8 doesn't help + + pos.setPiece(TextIO.getSquare("a7"), Piece.BBISHOP); + sc = new Search(pos, nullHist, 0, tt); + assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); // Ra8 doesn't help + + pos.setPiece(TextIO.getSquare("a7"), Piece.BPAWN); + sc = new Search(pos, nullHist, 0, tt); + assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); // Ra8 doesn't help + + pos.setPiece(TextIO.getSquare("a7"), Piece.BQUEEN); + sc = new Search(pos, nullHist, 0, tt); + assertEquals(pV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); // Ra8 does help + + pos = TextIO.readFEN("8/3k4/R7/r7/P7/8/4K3/8 b - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); + + pos = TextIO.readFEN("Q7/q6k/R7/r7/P7/8/4K3/8 b - - 0 1"); + sc = new Search(pos, nullHist, 0, tt); + assertEquals(pV - rV, getSEE(sc, TextIO.stringToMove(pos, "Rxa4"))); + + pos = TextIO.readFEN("8/3k4/5R2/8/4pP2/8/8/3K4 b - f3 0 1"); + sc = new Search(pos, nullHist, 0, tt); + int score1 = EvaluateTest.evalWhite(sc.pos); + long h1 = sc.pos.zobristHash(); + assertEquals(0, getSEE(sc, TextIO.stringToMove(pos, "exf3"))); + int score2 = EvaluateTest.evalWhite(sc.pos); + long h2 = sc.pos.zobristHash(); + assertEquals(score1, score2); + assertEquals(h1, h2); + } + + /** + * Test of scoreMoveList method, of class Search. + */ + @Test + public void testScoreMoveList() throws ChessParseError { + System.out.println("SEEorder"); + Position pos = TextIO.readFEN("r2qk2r/ppp2ppp/1bnp1nb1/1N2p3/3PP3/1PP2N2/1P3PPP/R1BQRBK1 w kq - 0 1"); + Search sc = new Search(pos, nullHist, 0, tt); + MoveGen moveGen = new MoveGen(); + MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); + sc.scoreMoveList(moves, 0); + for (int i = 0; i < moves.size; i++) { + Search.selectBest(moves, i); + if (i > 0) { + int sc1 = moves.m[i - 1].score; + int sc2 = moves.m[i].score; + assertTrue(sc2 <= sc1); + } + } + + moves = moveGen.pseudoLegalMoves(pos); + moves.m[0].score = 17; + moves.m[1].score = 666; + moves.m[2].score = 4711; + sc.scoreMoveList(moves, 0, 2); + assertEquals(17, moves.m[0].score); + assertEquals(666, moves.m[1].score); + for (int i = 1; i < moves.size; i++) { + Search.selectBest(moves, i); + if (i > 1) { + int sc1 = moves.m[i - 1].score; + int sc2 = moves.m[i].score; + assertTrue(sc2 <= sc1); + } + } + + // The hashMove should be first in the list + Move m = TextIO.stringToMove(pos, "Ra6"); + moves = moveGen.pseudoLegalMoves(pos); + boolean res = Search.selectHashMove(moves, m); + assertEquals(true, res); + assertEquals(m, moves.m[0]); + } +} diff --git a/CuckooChessEngine/test/chess/TextIOTest.java b/CuckooChessEngine/test/chess/TextIOTest.java new file mode 100644 index 0000000..539bbc0 --- /dev/null +++ b/CuckooChessEngine/test/chess/TextIOTest.java @@ -0,0 +1,398 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class TextIOTest { + + public TextIOTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of readFEN method, of class TextIO. + */ + @Test + public void testReadFEN() throws ChessParseError { + System.out.println("readFEN"); + String fen = "rnbqk2r/1p3ppp/p7/1NpPp3/QPP1P1n1/P4N2/4KbPP/R1B2B1R b kq - 0 1"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + assertEquals(pos.getPiece(Position.getSquare(0, 3)), Piece.WQUEEN); + assertEquals(pos.getPiece(Position.getSquare(4, 7)), Piece.BKING); + assertEquals(pos.getPiece(Position.getSquare(4, 1)), Piece.WKING); + assertEquals(pos.whiteMove, false); + assertEquals(pos.a1Castle(), false); + assertEquals(pos.h1Castle(), false); + assertEquals(pos.a8Castle(), true); + assertEquals(pos.h8Castle(), true); + + fen = "8/3k4/8/5pP1/1P6/1NB5/2QP4/R3K2R w KQ f6 1 2"; + pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + assertEquals(1, pos.halfMoveClock); + assertEquals(2, pos.fullMoveCounter); + + // Must have exactly one king + boolean wasError = testFENParseError("8/8/8/8/8/8/8/kk1K4 w - - 0 1"); + assertEquals(true, wasError); + + // Must not be possible to capture the king + wasError = testFENParseError("8/8/8/8/8/8/8/k1RK4 w - - 0 1"); + assertEquals(true, wasError); + + // Make sure bogus en passant square information is removed + fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"; + pos = TextIO.readFEN(fen); + assertEquals(-1, pos.getEpSquare()); + + // Test for too many rows (slashes) + wasError = testFENParseError("8/8/8/8/4k3/8/8/8/KBN5 w - - 0 1"); + assertEquals(true, wasError); + + // Test for too many columns + wasError = testFENParseError("8K/8/8/8/4k3/8/8/8 w - - 0 1"); + assertEquals(true, wasError); + + // Pawns must not be on first/last rank + wasError = testFENParseError("kp6/8/8/8/8/8/8/K7 w - - 0 1"); + assertEquals(true, wasError); + + wasError = testFENParseError("kr/pppp/8/8/8/8/8/KBR w"); + assertEquals(false, wasError); // OK not to specify castling flags and ep square + + wasError = testFENParseError("k/8/8/8/8/8/8/K"); + assertEquals(true, wasError); // Error side to move not specified + + wasError = testFENParseError(""); + assertEquals(true, wasError); + + wasError = testFENParseError(" |"); + assertEquals(true, wasError); + + wasError = testFENParseError("1B1B4/6k1/7r/7P/6q1/r7/q7/7K b - - acn 6; acs 0;"); + assertEquals(false, wasError); // Extra stuff after FEN string is allowed + } + + /** Tests if trying to parse a FEN string causes an error. */ + private boolean testFENParseError(String fen) { + boolean wasError; + wasError = false; + try { + TextIO.readFEN(fen); + } catch (ChessParseError err) { + wasError = true; + } + return wasError; + } + + /** + * Test of moveToString method, of class TextIO. + */ + @Test + public void testMoveToString() throws ChessParseError { + System.out.println("moveToString"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + assertEquals(TextIO.startPosFEN, TextIO.toFEN(pos)); + Move move = new Move(Position.getSquare(4, 1), Position.getSquare(4, 3), + Piece.EMPTY); + boolean longForm = true; + String result = TextIO.moveToString(pos, move, longForm); + assertEquals("e2-e4", result); + + move = new Move(Position.getSquare(6, 0), Position.getSquare(5, 2), Piece.EMPTY); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("Ng1-f3", result); + + move = new Move(Position.getSquare(4, 7), Position.getSquare(2, 7), + Piece.EMPTY); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("O-O-O", result); + + String fen = "1r3k2/2P5/8/8/8/4K3/8/8 w - - 0 1"; + pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + move = new Move(Position.getSquare(2,6), Position.getSquare(1,7), Piece.WROOK); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("c7xb8R+", result); + + move = new Move(Position.getSquare(2,6), Position.getSquare(2,7), Piece.WKNIGHT); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("c7-c8N", result); + + move = new Move(Position.getSquare(2,6), Position.getSquare(2,7), Piece.WQUEEN); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("c7-c8Q+", result); + } + + /** + * Test of moveToString method, of class TextIO, mate/stalemate tests. + */ + @Test + public void testMoveToStringMate() throws ChessParseError { + System.out.println("moveToStringMate"); + Position pos = TextIO.readFEN("3k4/1PR5/3N4/8/4K3/8/8/8 w - - 0 1"); + boolean longForm = true; + + Move move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WROOK); + String result = TextIO.moveToString(pos, move, longForm); + assertEquals("b7-b8R+", result); // check + + move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WQUEEN); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("b7-b8Q#", result); // check mate + + move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WKNIGHT); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("b7-b8N", result); + + move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WBISHOP); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("b7-b8B", result); // stalemate + } + + /** + * Test of moveToString method, of class TextIO, short form. + */ + @Test + public void testMoveToStringShortForm() throws ChessParseError { + System.out.println("moveToStringShortForm"); + String fen = "r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1"; + Position pos = TextIO.readFEN(fen); + assertEquals(fen, TextIO.toFEN(pos)); + boolean longForm = false; + + Move move = new Move(Position.getSquare(4,5), Position.getSquare(4,3), Piece.EMPTY); + String result = TextIO.moveToString(pos, move, longForm); + assertEquals("Qee4", result); // File disambiguation needed + + move = new Move(Position.getSquare(2,5), Position.getSquare(4,3), Piece.EMPTY); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("Qc6e4", result); // Full disambiguation needed + + move = new Move(Position.getSquare(2,3), Position.getSquare(4,3), Piece.EMPTY); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("Q4e4", result); // Row disambiguation needed + + move = new Move(Position.getSquare(2,3), Position.getSquare(2,0), Piece.EMPTY); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("Qc1+", result); // No disambiguation needed + + move = new Move(Position.getSquare(0,1), Position.getSquare(0,0), Piece.BQUEEN); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("a1Q", result); // Normal promotion + + move = new Move(Position.getSquare(0,1), Position.getSquare(1,0), Piece.BQUEEN); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("axb1Q#", result); // Capture promotion and check mate + + move = new Move(Position.getSquare(0,1), Position.getSquare(1,0), Piece.BKNIGHT); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("axb1N", result); // Capture promotion + + move = new Move(Position.getSquare(3,6), Position.getSquare(4,4), Piece.EMPTY); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("Ne5", result); // Other knight pinned, no disambiguation needed + + move = new Move(Position.getSquare(7,6), Position.getSquare(7,4), Piece.EMPTY); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("h5", result); // Regular pawn move + + move = new Move(Position.getSquare(5,7), Position.getSquare(3,7), Piece.EMPTY); + result = TextIO.moveToString(pos, move, longForm); + assertEquals("Rfd8", result); // File disambiguation needed + } + + /** + * Test of stringToMove method, of class TextIO. + */ + @Test + public void testStringToMove() throws ChessParseError { + System.out.println("stringToMove"); + Position pos = TextIO.readFEN("r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1"); + + Move mNe5 = new Move(Position.getSquare(3, 6), Position.getSquare(4, 4), Piece.EMPTY); + Move m = TextIO.stringToMove(pos, "Ne5"); + assertEquals(mNe5, m); + m = TextIO.stringToMove(pos, "ne"); + assertEquals(mNe5, m); + m = TextIO.stringToMove(pos, "N"); + assertEquals(null, m); + + Move mQc6e4 = new Move(Position.getSquare(2, 5), Position.getSquare(4, 3), Piece.EMPTY); + m = TextIO.stringToMove(pos, "Qc6-e4"); + assertEquals(mQc6e4, m); + m = TextIO.stringToMove(pos, "Qc6e4"); + assertEquals(mQc6e4, m); + m = TextIO.stringToMove(pos, "Qce4"); + assertEquals(null, m); + m = TextIO.stringToMove(pos, "Q6e4"); + assertEquals(null, m); + + Move maxb1Q = new Move(Position.getSquare(0, 1), Position.getSquare(1, 0), Piece.BQUEEN); + m = TextIO.stringToMove(pos, "axb1Q"); + assertEquals(maxb1Q, m); + m = TextIO.stringToMove(pos, "axb1Q#"); + assertEquals(maxb1Q, m); + m = TextIO.stringToMove(pos, "axb1Q+"); + assertEquals(null, m); + + Move mh5= new Move(Position.getSquare(7, 6), Position.getSquare(7, 4), Piece.EMPTY); + m = TextIO.stringToMove(pos, "h5"); + assertEquals(mh5, m); + m = TextIO.stringToMove(pos, "h7-h5"); + assertEquals(mh5, m); + m = TextIO.stringToMove(pos, "h"); + assertEquals(null, m); + + pos = TextIO.readFEN("r1b1k2r/1pqpppbp/p5pn/3BP3/8/2pP4/PPPBQPPP/R3K2R w KQkq - 0 12"); + m = TextIO.stringToMove(pos, "bxc3"); + assertEquals(TextIO.getSquare("b2"), m.from); + m = TextIO.stringToMove(pos, "Bxc3"); + assertEquals(TextIO.getSquare("d2"), m.from); + m = TextIO.stringToMove(pos, "bxc"); + assertEquals(TextIO.getSquare("b2"), m.from); + m = TextIO.stringToMove(pos, "Bxc"); + assertEquals(TextIO.getSquare("d2"), m.from); + + // Test castling. o-o is a substring of o-o-o, which could cause problems. + pos = TextIO.readFEN("5k2/p1pQn3/1p2Bp1r/8/4P1pN/2N5/PPP2PPP/R3K2R w KQ - 0 16"); + Move kCastle = new Move(Position.getSquare(4,0), Position.getSquare(6,0), Piece.EMPTY); + Move qCastle = new Move(Position.getSquare(4,0), Position.getSquare(2,0), Piece.EMPTY); + m = TextIO.stringToMove(pos, "o"); + assertEquals(null, m); + m = TextIO.stringToMove(pos, "o-o"); + assertEquals(kCastle, m); + m = TextIO.stringToMove(pos, "O-O"); + assertEquals(kCastle, m); + m = TextIO.stringToMove(pos, "o-o-o"); + assertEquals(qCastle, m); + + // Test 'o-o+' + pos.setPiece(Position.getSquare(5,1), Piece.EMPTY); + pos.setPiece(Position.getSquare(5,5), Piece.EMPTY); + m = TextIO.stringToMove(pos, "o"); + assertEquals(null, m); + m = TextIO.stringToMove(pos, "o-o"); + assertEquals(kCastle, m); + m = TextIO.stringToMove(pos, "o-o-o"); + assertEquals(qCastle, m); + m = TextIO.stringToMove(pos, "o-o+"); + assertEquals(kCastle, m); + + // Test d8=Q+ syntax + pos = TextIO.readFEN("1r3r2/2kP2Rp/p1bN1p2/2p5/5P2/2P5/P5PP/3R2K1 w - -"); + m = TextIO.stringToMove(pos, "d8=Q+"); + Move m2 = TextIO.stringToMove(pos, "d8Q"); + assertEquals(m2, m); + } + + /** + * Test of getSquare method, of class TextIO. + */ + @Test + public void testGetSquare() throws ChessParseError { + System.out.println("getSquare"); + assertEquals(Position.getSquare(0, 0), TextIO.getSquare("a1")); + assertEquals(Position.getSquare(1, 7), TextIO.getSquare("b8")); + assertEquals(Position.getSquare(3, 3), TextIO.getSquare("d4")); + assertEquals(Position.getSquare(4, 3), TextIO.getSquare("e4")); + assertEquals(Position.getSquare(3, 1), TextIO.getSquare("d2")); + assertEquals(Position.getSquare(7, 7), TextIO.getSquare("h8")); + } + + /** + * Test of squareToString method, of class TextIO. + */ + @Test + public void testSquareToString() { + System.out.println("squareToString"); + assertEquals("a1", TextIO.squareToString(Position.getSquare(0, 0))); + assertEquals("h6", TextIO.squareToString(Position.getSquare(7, 5))); + assertEquals("e4", TextIO.squareToString(Position.getSquare(4, 3))); + } + + /** + * Test of asciiBoard method, of class TextIO. + */ + @Test + public void testAsciiBoard() throws ChessParseError { + System.out.println("asciiBoard"); + Position pos = TextIO.readFEN("r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1"); + String aBrd = TextIO.asciiBoard(pos); +// System.out.print(aBrd); + assertEquals(12, aBrd.length() - aBrd.replaceAll("\\*", "").length()); // 12 black pieces + assertEquals(3, aBrd.length() - aBrd.replaceAll("\\*Q", " ").length()); // 3 black queens + assertEquals(3, aBrd.length() - aBrd.replaceAll(" P", " ").length()); // 3 white pawns + } + + /** + * Test of uciStringToMove method, of class TextIO. + */ + @Test + public void testUciStringToMove() throws ChessParseError { + System.out.println("stringToMove"); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Move m = TextIO.uciStringToMove("e2e4"); + assertEquals(TextIO.stringToMove(pos, "e4"), m); + m = TextIO.uciStringToMove("e2e5"); + assertEquals(new Move(12, 12+8*3, Piece.EMPTY), m); + + m = TextIO.uciStringToMove("e2e5q"); + assertEquals(null, m); + + m = TextIO.uciStringToMove("e7e8q"); + assertEquals(Piece.WQUEEN, m.promoteTo); + m = TextIO.uciStringToMove("e7e8r"); + assertEquals(Piece.WROOK, m.promoteTo); + m = TextIO.uciStringToMove("e7e8b"); + assertEquals(Piece.WBISHOP, m.promoteTo); + m = TextIO.uciStringToMove("e2e1n"); + assertEquals(Piece.BKNIGHT, m.promoteTo); + m = TextIO.uciStringToMove("e7e8x"); + assertEquals(null, m); // Invalid promotion piece + m = TextIO.uciStringToMove("i1i3"); + assertEquals(null, m); // Outside board + } +} diff --git a/CuckooChessEngine/test/chess/TranspositionTableTest.java b/CuckooChessEngine/test/chess/TranspositionTableTest.java new file mode 100644 index 0000000..aef1fda --- /dev/null +++ b/CuckooChessEngine/test/chess/TranspositionTableTest.java @@ -0,0 +1,162 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package chess; + +import chess.TranspositionTable.TTEntry; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author petero + */ +public class TranspositionTableTest { + + public TranspositionTableTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + /** + * Test of TTEntry nested class, of class TranspositionTable. + */ + @Test + public void testTTEntry() throws ChessParseError { + System.out.println("TTEntry"); + final int mate0 = Search.MATE0; + Position pos = TextIO.readFEN(TextIO.startPosFEN); + Move move = TextIO.stringToMove(pos, "e4"); + + // Test "normal" (non-mate) score + int score = 17; + int ply = 3; + TTEntry ent1 = new TTEntry(); + ent1.key = 1; + ent1.setMove(move); + ent1.setScore(score, ply); + ent1.setDepth(3); + ent1.generation = 0; + ent1.type = TranspositionTable.TTEntry.T_EXACT; + ent1.setHashSlot(0); + Move tmpMove = new Move(0,0,0); + ent1.getMove(tmpMove); + assertEquals(move, tmpMove); + assertEquals(score, ent1.getScore(ply)); + assertEquals(score, ent1.getScore(ply + 3)); // Non-mate score, should be ply-independent + + // Test positive mate score + TTEntry ent2 = new TTEntry(); + score = mate0 - 6; + ply = 3; + ent2.key = 3; + move = new Move(8, 0, Piece.BQUEEN); + ent2.setMove(move); + ent2.setScore(score, ply); + ent2.setDepth(99); + ent2.generation = 0; + ent2.type = TranspositionTable.TTEntry.T_EXACT; + ent2.setHashSlot(0); + ent2.getMove(tmpMove); + assertEquals(move, tmpMove); + assertEquals(score, ent2.getScore(ply)); + assertEquals(score + 2, ent2.getScore(ply - 2)); + + // Compare ent1 and ent2 + assertTrue(!ent1.betterThan(ent2, 0)); // More depth is good + assertTrue(ent2.betterThan(ent1, 0)); + + ent2.generation = 1; + assertTrue(!ent2.betterThan(ent1, 0)); // ent2 has wrong generation + assertTrue(ent2.betterThan(ent1, 1)); // ent1 has wrong generation + + ent2.generation = 0; + ent1.setDepth(7); ent2.setDepth(7); + ent1.type = TranspositionTable.TTEntry.T_GE; + assertTrue(ent2.betterThan(ent1, 0)); + ent2.type = TranspositionTable.TTEntry.T_LE; + assertTrue(!ent2.betterThan(ent1, 0)); // T_GE is equally good as T_LE + assertTrue(!ent1.betterThan(ent2, 0)); + + // Test negative mate score + TTEntry ent3 = new TTEntry(); + score = -mate0 + 5; + ply = 3; + ent3.key = 3; + move = new Move(8, 0, Piece.BQUEEN); + ent3.setMove(move); + ent3.setScore(score, ply); + ent3.setDepth(99); + ent3.generation = 0; + ent3.type = TranspositionTable.TTEntry.T_EXACT; + ent3.setHashSlot(0); + ent3.getMove(tmpMove); + assertEquals(move, tmpMove); + assertEquals(score, ent3.getScore(ply)); + assertEquals(score - 2, ent3.getScore(ply - 2)); + } + + /** + * Test of insert method, of class TranspositionTable. + */ + @Test + public void testInsert() throws ChessParseError { + System.out.println("insert"); + TranspositionTable tt = new TranspositionTable(16); + Position pos = TextIO.readFEN(TextIO.startPosFEN); + String[] moves = { + "e4", "e5", "Nf3", "Nc6", "Bb5", "a6", "Ba4", "b5", "Bb3", "Nf6", "O-O", "Be7", "Re1" + }; + UndoInfo ui = new UndoInfo(); + for (int i = 0; i < moves.length; i++) { + Move m = TextIO.stringToMove(pos, moves[i]); + pos.makeMove(m, ui); + int score = i * 17 + 3; + m.score = score; + int type = TranspositionTable.TTEntry.T_EXACT; + int ply = i + 1; + int depth = i * 2 + 5; + tt.insert(pos.historyHash(), m, type, ply, depth, score * 2 + 3); + } + + pos = TextIO.readFEN(TextIO.startPosFEN); + for (int i = 0; i < moves.length; i++) { + Move m = TextIO.stringToMove(pos, moves[i]); + pos.makeMove(m, ui); + TranspositionTable.TTEntry ent = tt.probe(pos.historyHash()); + assertEquals(TranspositionTable.TTEntry.T_EXACT, ent.type); + int score = i * 17 + 3; + int ply = i + 1; + int depth = i * 2 + 5; + assertEquals(score, ent.getScore(ply)); + assertEquals(depth, ent.getDepth()); + assertEquals(score * 2 + 3, ent.evalScore); + Move tmpMove = new Move(0,0,0); + ent.getMove(tmpMove); + assertEquals(m, tmpMove); + } + } +} diff --git a/CuckooChessEngine/test/guibase/ChessControllerTest.java b/CuckooChessEngine/test/guibase/ChessControllerTest.java new file mode 100644 index 0000000..63dccbd --- /dev/null +++ b/CuckooChessEngine/test/guibase/ChessControllerTest.java @@ -0,0 +1,47 @@ +/* + CuckooChess - A java chess program. + Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package guibase; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import chess.ChessParseError; +import chess.Piece; +import chess.TextIO; + +public class ChessControllerTest { + + @Test + public final void testSetPGN() throws ChessParseError { + ChessController ctrl = new ChessController(null); + ctrl.newGame(true, 8, false); + ctrl.setPGN("[FEN \"k/8/8/8/8/8/KP/8 w\"]\n"); + assertEquals(TextIO.getSquare("a2"), ctrl.game.pos.getKingSq(true)); + assertEquals(TextIO.getSquare("a8"), ctrl.game.pos.getKingSq(false)); + + ctrl.setPGN("1.e4 e5 2. Nf3!!! $6 (Nc3 (a3)) Nc6?? Bb5!!? a6?! * Ba4"); + assertEquals(Piece.BPAWN, ctrl.game.pos.getPiece(TextIO.getSquare("a6"))); + assertEquals(Piece.WBISHOP, ctrl.game.pos.getPiece(TextIO.getSquare("b5"))); + assertEquals(Piece.EMPTY, ctrl.game.pos.getPiece(TextIO.getSquare("a4"))); + + ctrl.setPGN("[FEN \"r1bq1rk1/pp3ppp/2n1pn2/6B1/1bBP4/2N2N2/PPQ2PPP/R3K2R w KQ - 1 10\"]\n"); + assertEquals(10, ctrl.game.pos.fullMoveCounter); + } +}