diff --git a/MoveGen.scala b/MoveGen.scala new file mode 100644 index 0000000..86edb4c --- /dev/null +++ b/MoveGen.scala @@ -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 diff --git a/Perft.scala b/Perft.scala new file mode 100644 index 0000000..31826ef --- /dev/null +++ b/Perft.scala @@ -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()