if (piece == "p" && this.hasEnpassant && this.epSquare)
Array.prototype.push.apply(moves, this.getEnpassantCaptures([x, y]));
if (
- piece == "k" && this.hasCastle &&
+ this.isKing(0, 0, piece) && this.hasCastle &&
this.castleFlags[color || this.turn].some(v => v < this.size.y)
) {
Array.prototype.push.apply(moves, this.getCastleMoves([x, y]));
);
}
- underCheck([x, y], oppCol) {
+ underCheck(square_s, oppCol) {
if (this.options["taking"] || this.options["dark"])
return false;
- return this.underAttack([x, y], oppCol);
+ if (!Array.isArray(square_s))
+ square_s = [square_s];
+ return square_s.some(sq => this.underAttack(sq, oppCol));
}
- // Stop at first king found (TODO: multi-kings)
+ // Scan board for king(s)
searchKingPos(color) {
+ let res = [];
for (let i=0; i < this.size.x; i++) {
for (let j=0; j < this.size.y; j++) {
if (this.getColor(i, j) == color && this.isKing(i, j))
- return [i, j];
+ res.push([i, j]);
}
}
- return [-1, -1]; //king not found
+ return res;
}
// 'color' arg because some variants (e.g. Refusal) check opponent moves
if (!color)
color = this.turn;
const oppCol = C.GetOppCol(color);
- const kingPos = this.searchKingPos(color);
+ let kingPos = this.searchKingPos(color);
let filtered = {}; //avoid re-checking similar moves (promotions...)
return moves.filter(m => {
const key = m.start.x + m.start.y + '.' + m.end.x + m.end.y;
if (!filtered[key]) {
this.playOnBoard(m);
- let square = kingPos,
+ let newKingPP = null,
+ sqIdx = 0,
res = true; //a priori valid
- if (m.vanish.some(v => this.isKing(0, 0, v.p) && v.c == color)) {
+ const oldKingPP = m.vanish.find(v => this.isKing(0, 0, v.p) && v.c == color);
+ if (oldKingPP) {
// Search king in appear array:
- const newKingIdx =
- m.appear.findIndex(a => this.isKing(0, 0, a.p) && a.c == color);
- if (newKingIdx >= 0)
- square = [m.appear[newKingIdx].x, m.appear[newKingIdx].y];
+ newKingPP =
+ m.appear.find(a => this.isKing(0, 0, a.p) && a.c == color);
+ if (newKingPP) {
+ sqIdx = kingPos.findIndex(kp => kp[0] == oldKingPP.x && kp[1] == oldKingPP[.y);
+ kingPos[sqIdx] = [newKingPP.x, newKingPP.y];
+ }
else
- res = false;
+ res = false; //king vanished
}
- res &&= !this.underCheck(square, oppCol);
+ res &&= !this.underCheck(square_s, oppCol);
+ if (oldKingPP && newKingPP)
+ kingPos[sqIdx] = [oldKingPP.x, oldKingPP.y];
this.undoOnBoard(m);
filtered[key] = res;
return res;
return false;
const color = this.turn;
const oppKingPos = this.searchKingPos(C.GetOppCol(color));
- if (oppKingPos[0] < 0 || this.underCheck(oppKingPos, color))
+ if (oppKingPos.length == 0 || this.underCheck(oppKingPos, color))
return true;
return (
(
getCurrentScore(move) {
const color = this.turn;
const oppCol = C.GetOppCol(color);
- const kingPos = [this.searchKingPos(color), this.searchKingPos(oppCol)];
- if (kingPos[0][0] < 0 && kingPos[1][0] < 0)
+ const kingPos = {
+ [color]: this.searchKingPos(color),
+ [oppCol]: this.searchKingPos(oppCol)
+ };
+ if (kingPos[color].length == 0 && kingPos[oppCol].length == 0)
return "1/2";
- if (kingPos[0][0] < 0)
+ if (kingPos[color].length == 0)
return (color == "w" ? "0-1" : "1-0");
- if (kingPos[1][0] < 0)
+ if (kingPos[oppCol].length == 0)
return (color == "w" ? "1-0" : "0-1");
if (this.atLeastOneMove(color))
return "*";
// No valid move: stalemate or checkmate?
- if (!this.underCheck(kingPos[0], oppCol))
+ if (!this.underCheck(kingPos[color], oppCol))
return "1/2";
// OK, checkmate
return (color == "w" ? "0-1" : "1-0");
--- /dev/null
+import ChessRules from "/base_rules.js";
+
+export class AbstractAntikingRules extends ChessRules {
+
+ static get Options() {
+ return {
+ styles: [
+ "atomic",
+ "balance",
+ "cannibal",
+ "capture",
+ "crazyhouse",
+ "doublemove",
+ "madrasi",
+ "progressive",
+ "recycle",
+ "rifle",
+ "teleport",
+ "zen"
+ ]
+ };
+ }
+
+ pieces(color, x, y) {
+ "a": {
+ // Move like a king, no attacks
+ "class": "antiking",
+ moves: super.pieces(color, x, y)['k'].moves,
+ attack: []
+ }
+ }
+
+ isKing(x, y, p) {
+ if (!p)
+ p = this.getPiece(x, y);
+ return ['k', 'a'].includes(p);
+ }
+
+ canTake([x1, y1], [x2, y2]) {
+ const piece1 = this.getPiece(x1, y1);
+ const piece2 = this.getPiece(x2, y2);
+ const color1 = this.getColor(x1, y1);
+ const color2 = this.getColor(x2, y2);
+ return (
+ piece2 != 'a' &&
+ (
+ (piece1 != 'a' && color1 != color2) ||
+ (piece1 == 'a' && color1 == color2)
+ )
+ );
+ }
+
+ underCheck(squares, color) {
+ const oppCol = C.GetOppCol(color);
+ let res = false;
+ squares.forEach(sq => {
+ switch (this.getPiece(sq[0], sq[1])) {
+ case 'k':
+ res ||= super.underAttack(sq, oppCol);
+ break;
+ case 'a':
+ res ||= !super.underAttack(sq, oppCol);
+ break;
+ }
+ });
+ return res;
+ }
+
+};
--- /dev/null
+import ChessRules from "/base_rules.js";
+import AbstractAntikingRules from "/variants/AbstractAntiking.js";
+
+export class Antiking1Rules extends AbstractAntikingRules {
+
+ static get Options() {
+ return {
+ styles: [
+ "atomic",
+ "balance",
+ "cannibal",
+ "capture",
+ "crazyhouse",
+ "doublemove",
+ "madrasi",
+ "progressive",
+ "recycle",
+ "rifle",
+ "teleport",
+ "zen"
+ ]
+ };
+ }
+
+ get hasCastle() {
+ return false;
+ }
+
+ pieces(color, x, y) {
+ const pawnShift = (color == "w" ? -1 : 1);
+ let res = super.pieces(color, x, y);
+ res['p'].moves = [
+ {
+ steps: [[pawnShift, 1], [pawnShift, -1]],
+ range: 1
+ }
+ ];
+ res['p'].attack = [
+ {
+ steps: [[pawnShift, 0]],
+ range: 1
+ }
+ ];
+ return res;
+ }
+
+ genRandInitFen() {
+ // Always deterministic setup
+ return (
+ '2prbkqA/2p1nnbr/2pppppp/8/8/PPPPPP2/RBNN1P2/aQKBRP2 w 0 ' +
+ '{"flags":"KAka"}'
+ );
+ }
+
+ // (Anti)King flags at 1 (true) if they can knight-jump
+ setFlags(fenflags) {
+ this.kingFlags = { w: {}, b: {} };
+ for (let i=0; i<fenFlags.length; i++) {
+ const white = fenFlags.charCodeAt(i) <= 90;
+ const curChar = fenFlags.charCodeAt(i).toLowerCase();
+ this.kingFlags[white ? 'w' : 'b'][curChar] = true;
+ }
+ }
+
+ getFlagsFen() {
+ return (
+ Array.prototype.concat.apply(
+ ['w', 'b'].map(c => Object.keys(this.kingFlags[c]))
+ ).join("")
+ );
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ const color = this.turn;
+ let moves = super.getPotentialMovesFrom([x, y]);
+ if (this.kingFlags[color][piece]) {
+ // Allow knight jump (king or antiking)
+ const knightMoves = super.getPotentialMovesOf('n', [x, y]);
+ // Remove captures (TODO: could be done more efficiently...)
+ moves = moves.concat(knightJumps.filter(m => m.vanish.length == 1));
+ }
+ return moves;
+ }
+
+ prePlay(move) {
+ super.prePlay(move);
+ // Update king+antiking flags
+ const piece = move.vanish[0].p;
+ if (this.isKing(0, 0, piece))
+ delete this.kingFlags[move.vanish[0].c][piece];
+ }
+
+};
--- /dev/null
+https://www.chessvariants.com/diffobjective.dir/anti-king-chess.html
--- /dev/null
+import ChessRules from "/base_rules.js";
+import AbstractAntikingRules from "/variants/AbstractAntiking.js";
+import { Random } from "/utils/alea.js";
+
+export class Antiking2Rules extends AbstractAntikingRules {
+
+ static get Aliases() {
+ return Object.assign({'A': AbstractAntikingRules}, ChessRules.Aliases);
+ }
+
+ static get Options() {
+ return {
+ styles: A.Options.styles.concat("cylinder")
+ };
+ }
+
+ genRandInitFen(seed) {
+ const baseFen = super.genRandInitFen(seed);
+ // Just add an antiking on 3rd ranks
+ let akPos = [3, 3];
+ if (this.options.randomness >= 1) {
+ akPos[0] = Random.randInt(this.size.y);
+ if (this.options.randomness == 2)
+ akPos[1] = Random.randInt(this.size.y);
+ else
+ akPos[1] = akPos[0];
+ }
+ const whiteLine = (akPos[0] > 0 ? akPos[0] : "") + 'A' + (akPos < this.size.y - 1 ? ...);
+ const blackLine = ...
+ return baseFen.replace(...)
+ }
+
+};
--- /dev/null
+https://www.chessvariants.com/diffobjective.dir/anti-king-chess.html