feat(movegen): add pseudo-legal and legal move generation with perft

This commit is contained in:
dualfroz committed 2026-08-24 15:50:00 +02:00
1 parent c312e5d2df
commit 42c792d21c
2 files changed
+173

No files matched your search

+140
View File
@@ -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