feat(movegen): add pseudo-legal and legal move generation with perft
This commit is contained in:
1 parent
c312e5d2df
commit
42c792d21c
2 files changed
+173
No files matched your search
+140
@@ -0,0 +1,140 @@
|
|||||||
|
package gambit
|
||||||
|
|
||||||
|
object MoveGen:
|
||||||
|
|
||||||
|
def generatePseudo(pos: Position, list: MoveList, capturesOnly: Boolean = false): Unit =
|
||||||
|
val us = pos.sideToMove
|
||||||
|
val them = us ^ 1
|
||||||
|
val board = pos.board
|
||||||
|
var sq = 0
|
||||||
|
while sq < 128 do
|
||||||
|
if Square.onBoard(sq) then
|
||||||
|
val piece = board(sq)
|
||||||
|
if piece != Piece.Empty && (piece >> 3) == us then
|
||||||
|
Piece.typeOf(piece) match
|
||||||
|
case Piece.Pawn => pawnMoves(pos, sq, us, them, list, capturesOnly)
|
||||||
|
case Piece.Knight => stepMoves(pos, sq, them, Directions.knight, list, capturesOnly)
|
||||||
|
case Piece.King => stepMoves(pos, sq, them, Directions.king, list, capturesOnly)
|
||||||
|
case Piece.Bishop => slideMoves(pos, sq, them, Directions.bishop, list, capturesOnly)
|
||||||
|
case Piece.Rook => slideMoves(pos, sq, them, Directions.rook, list, capturesOnly)
|
||||||
|
case Piece.Queen =>
|
||||||
|
slideMoves(pos, sq, them, Directions.bishop, list, capturesOnly)
|
||||||
|
slideMoves(pos, sq, them, Directions.rook, list, capturesOnly)
|
||||||
|
case _ => ()
|
||||||
|
sq += 1
|
||||||
|
if !capturesOnly then castlingMoves(pos, us, them, list)
|
||||||
|
|
||||||
|
private def pawnMoves(
|
||||||
|
pos: Position,
|
||||||
|
sq: Int,
|
||||||
|
us: Int,
|
||||||
|
them: Int,
|
||||||
|
list: MoveList,
|
||||||
|
capturesOnly: Boolean
|
||||||
|
): Unit =
|
||||||
|
val board = pos.board
|
||||||
|
val forward = if us == Color.White then 16 else -16
|
||||||
|
val startRank = if us == Color.White then 1 else 6
|
||||||
|
val promoRank = if us == Color.White then 7 else 0
|
||||||
|
|
||||||
|
val one = sq + forward
|
||||||
|
if Square.onBoard(one) && board(one) == Piece.Empty then
|
||||||
|
if Square.rankOf(one) == promoRank then addPromotions(sq, one, false, list)
|
||||||
|
else if !capturesOnly then
|
||||||
|
list.add(Move.make(sq, one))
|
||||||
|
if Square.rankOf(sq) == startRank then
|
||||||
|
val two = one + forward
|
||||||
|
if board(two) == Piece.Empty then list.add(Move.make(sq, two, double = true))
|
||||||
|
|
||||||
|
var d = 0
|
||||||
|
while d < 2 do
|
||||||
|
val target = sq + forward + (if d == 0 then -1 else 1)
|
||||||
|
if Square.onBoard(target) then
|
||||||
|
val tp = board(target)
|
||||||
|
if tp != Piece.Empty && (tp >> 3) == them then
|
||||||
|
if Square.rankOf(target) == promoRank then addPromotions(sq, target, true, list)
|
||||||
|
else list.add(Move.make(sq, target, capture = true))
|
||||||
|
else if target == pos.epSquare && pos.epSquare >= 0 then
|
||||||
|
list.add(Move.make(sq, target, capture = true, enPassant = true))
|
||||||
|
d += 1
|
||||||
|
|
||||||
|
private def addPromotions(from: Int, to: Int, capture: Boolean, list: MoveList): Unit =
|
||||||
|
list.add(Move.make(from, to, Piece.Queen, capture = capture))
|
||||||
|
list.add(Move.make(from, to, Piece.Rook, capture = capture))
|
||||||
|
list.add(Move.make(from, to, Piece.Bishop, capture = capture))
|
||||||
|
list.add(Move.make(from, to, Piece.Knight, capture = capture))
|
||||||
|
|
||||||
|
private def stepMoves(
|
||||||
|
pos: Position,
|
||||||
|
sq: Int,
|
||||||
|
them: Int,
|
||||||
|
offsets: Array[Int],
|
||||||
|
list: MoveList,
|
||||||
|
capturesOnly: Boolean
|
||||||
|
): Unit =
|
||||||
|
val board = pos.board
|
||||||
|
var i = 0
|
||||||
|
while i < offsets.length do
|
||||||
|
val t = sq + offsets(i)
|
||||||
|
if Square.onBoard(t) then
|
||||||
|
val tp = board(t)
|
||||||
|
if tp == Piece.Empty then
|
||||||
|
if !capturesOnly then list.add(Move.make(sq, t))
|
||||||
|
else if (tp >> 3) == them then list.add(Move.make(sq, t, capture = true))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
private def slideMoves(
|
||||||
|
pos: Position,
|
||||||
|
sq: Int,
|
||||||
|
them: Int,
|
||||||
|
offsets: Array[Int],
|
||||||
|
list: MoveList,
|
||||||
|
capturesOnly: Boolean
|
||||||
|
): Unit =
|
||||||
|
val board = pos.board
|
||||||
|
var i = 0
|
||||||
|
while i < offsets.length do
|
||||||
|
var t = sq + offsets(i)
|
||||||
|
var stop = false
|
||||||
|
while Square.onBoard(t) && !stop do
|
||||||
|
val tp = board(t)
|
||||||
|
if tp == Piece.Empty then
|
||||||
|
if !capturesOnly then list.add(Move.make(sq, t))
|
||||||
|
t += offsets(i)
|
||||||
|
else
|
||||||
|
if (tp >> 3) == them then list.add(Move.make(sq, t, capture = true))
|
||||||
|
stop = true
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
private def castlingMoves(pos: Position, us: Int, them: Int, list: MoveList): Unit =
|
||||||
|
val board = pos.board
|
||||||
|
if us == Color.White then
|
||||||
|
if (pos.castling & Castling.WhiteKing) != 0 &&
|
||||||
|
board(5) == Piece.Empty && board(6) == Piece.Empty &&
|
||||||
|
!pos.isAttacked(4, them) && !pos.isAttacked(5, them) && !pos.isAttacked(6, them)
|
||||||
|
then list.add(Move.make(4, 6, castle = true))
|
||||||
|
if (pos.castling & Castling.WhiteQueen) != 0 &&
|
||||||
|
board(1) == Piece.Empty && board(2) == Piece.Empty && board(3) == Piece.Empty &&
|
||||||
|
!pos.isAttacked(4, them) && !pos.isAttacked(3, them) && !pos.isAttacked(2, them)
|
||||||
|
then list.add(Move.make(4, 2, castle = true))
|
||||||
|
else
|
||||||
|
if (pos.castling & Castling.BlackKing) != 0 &&
|
||||||
|
board(117) == Piece.Empty && board(118) == Piece.Empty &&
|
||||||
|
!pos.isAttacked(116, them) && !pos.isAttacked(117, them) && !pos.isAttacked(118, them)
|
||||||
|
then list.add(Move.make(116, 118, castle = true))
|
||||||
|
if (pos.castling & Castling.BlackQueen) != 0 &&
|
||||||
|
board(113) == Piece.Empty && board(114) == Piece.Empty && board(115) == Piece.Empty &&
|
||||||
|
!pos.isAttacked(116, them) && !pos.isAttacked(115, them) && !pos.isAttacked(114, them)
|
||||||
|
then list.add(Move.make(116, 114, castle = true))
|
||||||
|
|
||||||
|
def generateLegal(pos: Position, list: MoveList): Unit =
|
||||||
|
val pseudo = new MoveList()
|
||||||
|
generatePseudo(pos, pseudo)
|
||||||
|
val mover = pos.sideToMove
|
||||||
|
var i = 0
|
||||||
|
while i < pseudo.size do
|
||||||
|
val m = pseudo(i)
|
||||||
|
val undo = pos.make(m)
|
||||||
|
if !pos.isAttacked(pos.kingSquare(mover), pos.sideToMove) then list.add(m)
|
||||||
|
pos.unmake(m, undo)
|
||||||
|
i += 1
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package gambit
|
||||||
|
|
||||||
|
object Perft:
|
||||||
|
def perft(pos: Position, depth: Int): Long =
|
||||||
|
if depth == 0 then 1L
|
||||||
|
else
|
||||||
|
val moves = new MoveList()
|
||||||
|
MoveGen.generateLegal(pos, moves)
|
||||||
|
if depth == 1 then moves.size.toLong
|
||||||
|
else
|
||||||
|
var nodes = 0L
|
||||||
|
var i = 0
|
||||||
|
while i < moves.size do
|
||||||
|
val m = moves(i)
|
||||||
|
val undo = pos.make(m)
|
||||||
|
nodes += perft(pos, depth - 1)
|
||||||
|
pos.unmake(m, undo)
|
||||||
|
i += 1
|
||||||
|
nodes
|
||||||
|
|
||||||
|
def divide(pos: Position, depth: Int): List[(String, Long)] =
|
||||||
|
val moves = new MoveList()
|
||||||
|
MoveGen.generateLegal(pos, moves)
|
||||||
|
val out = List.newBuilder[(String, Long)]
|
||||||
|
var i = 0
|
||||||
|
while i < moves.size do
|
||||||
|
val m = moves(i)
|
||||||
|
val undo = pos.make(m)
|
||||||
|
val count = if depth <= 1 then 1L else perft(pos, depth - 1)
|
||||||
|
pos.unmake(m, undo)
|
||||||
|
out += (Move.uci(m) -> count)
|
||||||
|
i += 1
|
||||||
|
out.result()
|
||||||
Reference in new issue
Block a user