diff --git a/tests/Eval.test.scala b/tests/Eval.test.scala new file mode 100644 index 0000000..5d39aac --- /dev/null +++ b/tests/Eval.test.scala @@ -0,0 +1,51 @@ +package gambit + +class EvalTest extends munit.FunSuite: + private def e(fen: String): Int = Eval.evaluate(Fen.parse(fen)) + + test("start position and a mirrored middlegame evaluate to zero") { + assertEquals(e(Fen.startPosition), 0) + assertEquals(e("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR w KQkq - 0 1"), 0) + } + + test("doubled and isolated pawns are penalised against a clean structure") { + val clean = e("4k3/8/8/8/8/8/1PP5/4K3 w - - 0 1") + val doubled = e("4k3/8/8/8/8/1P6/1P6/4K3 w - - 0 1") + assert(clean > doubled, s"clean $clean should beat doubled $doubled") + } + + test("a centralised knight is worth more than a cornered one") { + val center = e("4k3/8/8/8/4N3/8/8/4K3 w - - 0 1") + val corner = e("4k3/8/8/8/8/8/8/N3K3 w - - 0 1") + assert(center > corner, s"center $center should beat corner $corner") + } + + test("the bishop pair scores above two knights") { + val bishops = e("4k3/8/8/8/8/8/8/2B1KB2 w - - 0 1") + val knights = e("4k3/8/8/8/8/8/8/1N2KN2 w - - 0 1") + assert(bishops > knights, s"bishop pair $bishops should beat knight pair $knights") + } + + test("an exposed king is worse than a king behind its pawn shield") { + val sheltered = e("6k1/5ppp/8/8/8/8/5PPP/6K1 w - - 0 1") + val exposed = e("5k2/5ppp/8/8/8/8/5PPP/K7 w - - 0 1") + assert(exposed < sheltered, s"exposed $exposed should be worse than sheltered $sheltered") + } + + test("an enemy queen next to the king adds king danger") { + val far = e("6k1/5ppp/8/8/q7/8/5PPP/6K1 w - - 0 1") + val near = e("6k1/5ppp/8/8/8/5q2/5PPP/6K1 w - - 0 1") + assert(near < far, s"queen next to the king ($near) should score worse than far ($far)") + } + + test("a passed pawn is worth more than a blockaded one") { + val passed = e("4k3/2p4p/8/4P3/8/8/P7/4K3 w - - 0 1") + val blocked = e("4k3/7p/4p3/4P3/8/8/P7/4K3 w - - 0 1") + assert(passed > blocked, s"passed $passed should beat blocked $blocked") + } + + test("a rook on an open file scores above one behind its own pawn") { + val open = e("3rk3/p7/8/8/8/8/P7/3RK3 w - - 0 1") + val closed = e("3rk3/p7/8/8/8/8/3P4/3RK3 w - - 0 1") + assert(closed < open, s"closed file $closed should be worse than open $open") + } diff --git a/tests/See.test.scala b/tests/See.test.scala new file mode 100644 index 0000000..27af4a1 --- /dev/null +++ b/tests/See.test.scala @@ -0,0 +1,61 @@ +package gambit + +class SeeTest extends munit.FunSuite: + + private def seeOf(fen: String, uci: String): Int = + val pos = Fen.parse(fen) + val legal = new MoveList() + MoveGen.generateLegal(pos, legal) + var move = 0 + var i = 0 + while i < legal.size do + if Move.uci(legal(i)) == uci then move = legal(i) + i += 1 + assert(move != 0, s"move $uci not legal in $fen") + See.gain(pos, move) + + test("rook grabs an undefended pawn for a clean pawn") { + assertEquals(seeOf("4k3/8/8/4p3/8/8/8/4R1K1 w - - 0 1", "e1e5"), 100) + } + + test("rook takes a pawn defended by a pawn and loses the exchange") { + assertEquals(seeOf("4k3/8/3p4/4p3/8/8/8/4R1K1 w - - 0 1", "e1e5"), -400) + } + + test("pawn trade supported on both sides is exactly even") { + assertEquals(seeOf("4k3/8/3p4/4p3/3P4/8/8/4K3 w - - 0 1", "d4e5"), 0) + } + + test("knight takes a pawn defended by a pawn") { + assertEquals(seeOf("4k3/8/3p4/4p3/8/5N2/8/4K3 w - - 0 1", "f3e5"), -220) + } + + test("doubled rooks x-ray through the front rook to win the exchange back") { + assertEquals(seeOf("4rk2/8/8/4p3/8/4R3/8/4R1K1 w - - 0 1", "e3e5"), 100) + } + + test("capturing a hanging queen wins its full value") { + assertEquals(seeOf("4k3/8/8/3q4/8/8/8/3RK3 w - - 0 1", "d1d5"), 900) + } + + test("en passant capture is evaluated as a pawn trade") { + assertEquals(seeOf("4k3/8/8/3pP3/8/8/8/4K3 w - d6 0 1", "e5d6"), 100) + } + + test("isWinning and isLosing classify captures consistently with the swap value") { + val fen = "4k3/8/3p4/4p3/8/5N2/8/4K3 w - - 0 1" + assert(See.isLosing(Fen.parse(fen), findMove(fen, "f3e5"))) + val fenWin = "4k3/8/8/3q4/8/8/8/3RK3 w - - 0 1" + assert(See.isWinning(Fen.parse(fenWin), findMove(fenWin, "d1d5"))) + } + + private def findMove(fen: String, uci: String): Int = + val pos = Fen.parse(fen) + val legal = new MoveList() + MoveGen.generateLegal(pos, legal) + var move = 0 + var i = 0 + while i < legal.size do + if Move.uci(legal(i)) == uci then move = legal(i) + i += 1 + move