generatePieceMoves method
Generates all moves for the piece on square
in accordance with options
.
Implementation
List<Move> generatePieceMoves(
int square, [
MoveGenParams options = MoveGenParams.normal,
]) {
Square piece = board[square];
if (piece.isEmpty) return [];
Colour colour = piece.colour;
int dirMult = Bishop.playerDirection[piece.colour];
PieceType pieceType = variant.pieceType(piece, square);
if (options.onlySquare != null &&
pieceType.optimisationData != null &&
pieceType.optimisationData!
.excludePiece(square, options.onlySquare!, size)) {
return const [];
}
List<Move> moves = [];
int from = square;
bool exit = false;
void generateTeleportMoves(TeleportMoveDefinition md) {
for (int to = 0; to < size.numIndices; to++) {
if (!size.onBoard(square)) {
to += size.h;
if (!size.onBoard(square)) break;
}
if (to == square) continue;
// todo: md.firstMove, when better first move logic is built
if (options.ignorePieces) {
moves.add(StandardMove(from: from, to: to));
continue;
}
Square target = board[to];
if (target.isEmpty && md.quiet) {
moves.add(StandardMove(from: from, to: to));
continue;
}
if (target.isNotEmpty && target.colour != colour && md.capture) {
final targetPieceType = variant.pieceType(target, to);
if (targetPieceType.royal) {
// doesn't make sense for teleports to be able to capture royals
// unless we later implement region restricted teleport moves
continue;
}
moves.add(StandardMove(from: from, to: to, capturedPiece: target));
}
}
}
void addMove(StandardMove m) {
final mm = variant.generatePromotionMoves(
base: m,
state: state,
pieceType: pieceType,
);
if (mm != null) moves.addAll(mm);
if (mm == null) moves.add(m);
if (variant.gating) {
int gRank = size.rank(m.from);
if ((gRank == Bishop.rank1 && colour == Bishop.white) ||
(gRank == size.maxRank && colour == Bishop.black)) {
final gatingMoves = generateGatingMoves(m);
moves.addAll(gatingMoves);
if (gatingMoves.isNotEmpty &&
variant.gatingMode == GatingMode.fixed) {
moves.remove(m);
}
}
}
if (options.onlySquare != null && m.to == options.onlySquare) {
exit = true;
}
}
// Generate normal moves
for (MoveDefinition md in pieceType.moves) {
if (exit) break;
if (!md.capture && !options.quiet) continue;
if (!md.quiet && !options.captures) continue;
if (options.onlySquare != null &&
md.excludeMove(square, options.onlySquare!, dirMult, size)) {
continue;
}
if (md is TeleportMoveDefinition) {
generateTeleportMoves(md);
continue;
}
if (md.firstOnly &&
!variant.canFirstMove(
state: state,
from: from,
colour: colour,
moveDefinition: md,
)) {
continue;
}
if (md is! StandardMoveDefinition) continue;
int range = md.range == 0 ? variant.boardSize.maxDim : md.range;
int squaresSinceHop = -1;
for (int i = 0; i < range; i++) {
if (exit) break;
int to = square + md.normalised * (i + 1) * dirMult;
if (!size.onBoard(to)) break;
if (variant.hasRegions) {
if (!variant.allowMovement(piece, to)) break;
}
if (md.lame) {
int fromLame = from + md.normalised * i * dirMult;
int blockSq = fromLame + md.lameNormalised! * dirMult;
if (board[blockSq].isNotEmpty && !options.ignorePieces) break;
}
Square target = board[to];
bool setEnPassant =
variant.enPassant && md.firstOnly && pieceType.enPassantable;
if (md.hopper) {
if (target.isEmpty) {
if (squaresSinceHop == -1) continue;
squaresSinceHop++;
if (md.limitedHopper) {
if (squaresSinceHop > md.hopDistance) {
break;
}
if (squaresSinceHop != md.hopDistance) {
continue;
}
}
} else {
squaresSinceHop++;
if (squaresSinceHop == 0) continue;
if (md.limitedHopper && squaresSinceHop != md.hopDistance) {
break;
}
}
}
if (target.isEmpty) {
// TODO: prioritise ep? for moves that could be both ep and quiet
if (md.quiet) {
if (!options.quiet && options.onlySquare == null) continue;
StandardMove m =
StandardMove(to: to, from: from, setEnPassant: setEnPassant);
addMove(m);
} else if (variant.enPassant &&
md.enPassant &&
(state.epSquare == to || options.ignorePieces) &&
options.captures) {
// en passant
StandardMove m = StandardMove(
to: to,
from: from,
capturedPiece: makePiece(variant.epPiece, colour.opponent),
enPassant: true,
setEnPassant: setEnPassant,
);
addMove(m);
} else if (options.onlySquare != null && to == options.onlySquare) {
StandardMove m = StandardMove(
to: to,
from: from,
);
addMove(m);
} else {
if (!options.ignorePieces) {
if (md.slider) {
continue;
} else {
break;
}
}
StandardMove m = StandardMove(from: from, to: to);
addMove(m);
}
} else if (target.colour == colour) {
if (!options.ignorePieces) break;
StandardMove m = StandardMove(from: from, to: to);
addMove(m);
} else {
if (md.capture) {
if (!options.captures) break;
if (options.onlyPiece && target.type != options.pieceType) break;
StandardMove m = StandardMove(
to: to,
from: from,
capturedPiece: target,
setEnPassant: setEnPassant,
);
addMove(m);
} else if (options.ignorePieces) {
StandardMove m = StandardMove(
to: to,
from: from,
setEnPassant: setEnPassant,
);
addMove(m);
}
if (!options.ignorePieces) break;
}
}
}
// Generate castling
if (variant.castling && options.castling && pieceType.royal && !inCheck) {
bool kingside = colour == Bishop.white
? state.castlingRights.wk
: state.castlingRights.bk;
bool queenside = colour == Bishop.white
? state.castlingRights.wq
: state.castlingRights.bq;
int royalRank = size.rank(from);
for (int i = 0; i < 2; i++) {
bool sideCondition = i == 0
? (kingside && variant.castlingOptions.kingside)
: (queenside && variant.castlingOptions.queenside);
if (!sideCondition) continue;
// Conditions for castling:
// * All squares between the king's start and end (inclusive) must be
// free and not attacked
// * All squares between the rook and the king's target must be free.
// * Obviously the king's start is occupied by the king, but it can't
// be in check
// * The square the rook lands on must be free (but can be attacked)
int targetFile = i == 0
? variant.castlingOptions.kTarget!
: variant.castlingOptions.qTarget!;
int targetSq =
size.square(targetFile, royalRank); // where the king lands
int rookFile = i == 0 ? castlingTargetK! : castlingTargetQ!;
int rookSq = size.square(rookFile, royalRank); // where the rook starts
int rookTargetFile = i == 0 ? targetFile - 1 : targetFile + 1;
int rookTargetSq =
size.square(rookTargetFile, royalRank); // where the rook lands
// Check rook target square is empty (or occupied by the rook/king already)
if (board[rookTargetSq].isNotEmpty &&
rookTargetSq != rookSq &&
rookTargetSq != from) {
continue;
}
// Check king target square is empty (or occupied by the castling rook)
if (board[targetSq].isNotEmpty &&
targetSq != rookSq &&
targetSq != square) continue;
int numMidSqs = (targetFile - royalFile!).abs();
bool valid = true;
if (!options.ignorePieces) {
int numRookMidSquares = (targetFile - rookFile).abs();
if (numRookMidSquares > 1) {
for (int j = 1; j <= numRookMidSquares; j++) {
int midFile = rookFile + (i == 0 ? -j : j);
int midSq = size.square(midFile, royalRank);
if (board[midSq].isNotEmpty && midFile != royalFile) {
valid = false;
break;
}
}
}
for (int j = 1; j <= numMidSqs; j++) {
int midFile = royalFile! + (i == 0 ? j : -j);
// For some Chess960 positions.
// See also https://github.com/alexobviously/bishop/issues/11
// as to why this is first.
if (midFile == rookFile) continue;
int midSq = size.square(midFile, royalRank);
// None of these squares can be attacked
if (isAttacked(midSq, colour.opponent)) {
// squares between & dest square must not be attacked
valid = false;
break;
}
if (midFile == targetFile && targetFile == royalFile) {
continue;
} // king starting on target
if (j != numMidSqs && board[midSq].isNotEmpty) {
// squares between to and from must be empty
valid = false;
break;
}
}
}
if (valid) {
int castlingDir = i == 0 ? Castling.k : Castling.q;
StandardMove m = StandardMove(
from: from,
to: targetSq,
castlingDir: castlingDir,
castlingPieceSquare: rookSq,
);
if (variant.gatingMode != GatingMode.fixed) moves.add(m);
if (variant.gating) {
int gRank = size.rank(m.from);
if ((gRank == Bishop.rank1 && colour == Bishop.white) ||
(gRank == size.maxRank && colour == Bishop.black)) {
moves.addAll(generateGatingMoves(m));
}
}
}
}
}
if (options.onlySquare != null) {
List<Move> remove = [];
for (Move m in moves) {
if (m.to != options.onlySquare) {
remove.add(m);
}
}
for (Move m in remove) {
moves.remove(m);
}
}
if (options.legal) {
List<Move> remove = [];
for (Move m in moves) {
bool valid = makeMove(m, false);
if (!valid ||
lostBy(colour, ignoreSoftResults: true) ||
kingAttacked(colour)) {
remove.add(m);
}
undo();
}
for (Move m in remove) {
moves.remove(m);
}
}
return moves;
}