For animation, moves should contains "moving" and "fading" maybe...
(But it's really just for Magnetic chess)
+setInterval "CRON" task in sockets.js to check connected clients
+(every 1hour maybe, or more)
const oppCol = this.getOppCol(color);
let potentialMoves = [];
const [sizeX,sizeY] = VariantRules.size;
- for (var i=0; i<sizeX; i++)
+ for (let i=0; i<sizeX; i++)
{
- for (var j=0; j<sizeY; j++)
+ for (let j=0; j<sizeY; j++)
{
// Next condition ... != oppCol is a little HACK to work with checkered variant
if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) != oppCol)
}
// Assumption: at least one legal move
- getComputerMove(moves1) //moves1 might be precomputed (Magnetic chess)
+ // NOTE: works also for extinction chess because depth is 3...
+ getComputerMove()
{
this.shouldReturn = false;
const maxeval = VariantRules.INFINITY;
const color = this.turn;
- if (!moves1)
- moves1 = this.getAllValidMoves();
+ let moves1 = this.getAllValidMoves();
+
+ // Can I mate in 1 ? (for Magnetic & Extinction)
+ for (let i of _.shuffle(_.range(moves1.length)))
+ {
+ this.play(moves1[i]);
+ const finish = (Math.abs(this.evalPosition()) >= VariantRules.THRESHOLD_MATE);
+ this.undo(moves1[i]);
+ if (finish)
+ return moves1[i];
+ }
// Rank moves using a min-max at depth 2
for (let i=0; i<moves1.length; i++)
candidates.push(j);
let currentBest = moves1[_.sample(candidates, 1)];
- // Skip depth 3 if we found a checkmate (or if we are checkmated in 1...)
+ // Skip depth 3+ if we found a checkmate (or if we are checkmated in 1...)
if (VariantRules.SEARCH_DEPTH >= 3
&& Math.abs(moves1[0].eval) < VariantRules.THRESHOLD_MATE)
{
- // TODO: show current analyzed move for depth 3, allow stopping eval (return moves1[0])
for (let i=0; i<moves1.length; i++)
{
if (this.shouldReturn)
{
const [sizeX,sizeY] = VariantRules.size;
let evaluation = 0;
- //Just count material for now
+ // Just count material for now
for (let i=0; i<sizeX; i++)
{
for (let j=0; j<sizeY; j++)
const piece2 = this.getPiece(x2,y2);
const color1 = this.getColor(x1,y1);
const color2 = this.getColor(x2,y2);
- return !["a","A"].includes(piece2) &&
+ return piece2 != "a" &&
((piece1 != "a" && color1 != color2) || (piece1 == "a" && color1 == color2));
}
--- /dev/null
+class CrazyhouseRules extends ChessRules
+{
+ initVariables(fen)
+ {
+ super.initVariables();
+ // Also init reserves (used by the interface to show landing pieces)
+ const V = VariantRules;
+ this.reserve =
+ {
+ "w":
+ {
+ [V.PAWN]: 0,
+ [V.ROOK]: 0,
+ [V.KNIGHT]: 0,
+ [V.BISHOP]: 0,
+ [V.QUEEN]: 0,
+ },
+ "b":
+ {
+ [V.PAWN]: 0,
+ [V.ROOK]: 0,
+ [V.KNIGHT]: 0,
+ [V.BISHOP]: 0,
+ [V.QUEEN]: 0,
+ }
+ };
+ // It may be a continuation: adjust numbers of pieces according to captures + rebirths
+ // TODO
+ }
+
+ // Used by the interface:
+ getReservePieces(color)
+ {
+ return {
+ [color+V.PAWN]: this.reserve[color][V.PAWN],
+ [color+V.ROOK]: this.reserve[color][V.ROOK],
+ [color+V.KNIGHT]: this.reserve[color][V.KNIGHT],
+ [color+V.BISHOP]: this.reserve[color][V.BISHOP],
+ [color+V.QUEEN]: this.reserve[color][V.QUEEN],
+ };
+ }
+
+ getPotentialMovesFrom([x,y])
+ {
+ let moves = super.getPotentialMovesFrom([x,y]);
+ // Add landing moves:
+ const color = this.turn;
+ Object.keys(this.reserve[color]).forEach(p => {
+
+ moves.push(...); //concat... just appear
+ });
+ return moves;
+ }
+
+ // TODO: condition "if this is reserve" --> special square !!! coordinates ??
+ getPossibleMovesFrom(sq)
+ {
+ // Assuming color is right (already checked)
+ return this.filterValid( this.getPotentialMovesFrom(sq) );
+ }
+
+ // TODO: add reserve moves
+ getAllValidMoves()
+ {
+
+ }
+
+ // TODO: also
+ atLeastOneMove()
+ {
+
+ }
+
+ // TODO: update reserve
+ updateVariables(move)
+ {
+ }
+ unupdateVariables(move)
+ {
+ }
+
+ static get SEARCH_DEPTH() { return 2; } //high branching factor
+
+ getNotation(move)
+ {
+ if (move.vanish.length > 0)
+ return super.getNotation(move);
+ // Rebirth:
+ const piece =
+ (move.appear[0].p != VariantRules.PAWN ? move.appear.p.toUpperCase() : "");
+ const finalSquare =
+ String.fromCharCode(97 + move.end.y) + (VariantRules.size[0]-move.end.x);
+ return piece + "@" + finalSquare;
+ }
+}
--- /dev/null
+class ExtinctionRules extends ChessRules
+{
+ initVariables(fen)
+ {
+ super.initVariables(fen);
+ const V = VariantRules;
+ this.material =
+ {
+ "w":
+ {
+ [V.KING]: 1,
+ [V.QUEEN]: 1,
+ [V.ROOK]: 2,
+ [V.KNIGHT]: 2,
+ [V.BISHOP]: 2,
+ [V.PAWN]: 8
+ },
+ "b":
+ {
+ [V.KING]: 1,
+ [V.QUEEN]: 1,
+ [V.ROOK]: 2,
+ [V.KNIGHT]: 2,
+ [V.BISHOP]: 2,
+ [V.PAWN]: 8
+ }
+ };
+ }
+
+ // TODO: verify this assertion
+ atLeastOneMove()
+ {
+ return true; //always at least one possible move
+ }
+
+ underCheck(move)
+ {
+ return false; //there is no check
+ }
+
+ getCheckSquares(move)
+ {
+ return [];
+ }
+
+ updateVariables(move)
+ {
+ super.updateVariables(move);
+ if (move.vanish.length==2 && move.appear.length==1)
+ this.material[move.vanish[1].c][move.vanish[1].p]--;
+ }
+
+ unupdateVariables(move)
+ {
+ super.unupdateVariables(move);
+ if (move.vanish.length==2 && move.appear.length==1)
+ this.material[move.vanish[1].c][move.vanish[1].p]++;
+ }
+
+ checkGameOver()
+ {
+ if (this.checkRepetition())
+ return "1/2";
+
+ if (this.atLeastOneMove()) // game not over?
+ {
+ const color = this.turn;
+ if (Object.keys(this.material[color]).some(
+ p => { return this.material[color][p] == 0; }))
+ {
+ return this.checkGameEnd();
+ }
+ return "*";
+ }
+
+ return this.checkGameEnd();
+ }
+
+ // Very negative (resp. positive) if white (reps. black) pieces set is incomplete
+ evalPosition()
+ {
+ if (this.missAkind())
+ return (this.turn=="w"?-1:1) * VariantRules.INFINITY;
+ return super.evalPosition();
+ }
+}
|| this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
}
- play(move, ingame)
+ updateVariables(move)
{
- super.play(move, ingame);
+ super.updateVariables(move);
if (move.vanish.length==2 && move.appear.length==1
&& move.vanish[1].p != VariantRules.PAWN)
{
}
}
- undo(move)
+ unupdateVariables(move)
{
- super.undo(move);
+ super.unupdateVariables(move);
if (move.vanish.length==2 && move.appear.length==1
&& move.vanish[1].p != VariantRules.PAWN)
{
--- /dev/null
+class LoserRules extends ChessRules
+{
+ initVariables(fen)
+ {
+ // No castling, hence no flags
+ const epSq = this.moves.length > 0 ? this.getEpSquare(this.lastMove) : undefined;
+ this.epSquares = [ epSq ];
+ }
+
+ setFlags(fen) { }
+
+ getPotentialPawnMoves([x,y])
+ {
+ let moves = super.getPotentialPawnMoves([x,y]);
+
+ // Complete with promotion(s) into king, if possible
+ const color = this.turn;
+ const V = VariantRules;
+ const [sizeX,sizeY] = VariantRules.size;
+ const shift = (color == "w" ? -1 : 1);
+ const lastRank = (color == "w" ? 0 : sizeX-1);
+ if (x+shift == lastRank)
+ {
+ let p = V.KING;
+ // Normal move
+ if (this.board[x+shift][y] == V.EMPTY)
+ moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
+ // Captures
+ if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
+ moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:p}));
+ if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
+ moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:p}));
+ }
+
+ return moves;
+ }
+
+ getPotentialKingMoves(sq)
+ {
+ const V = VariantRules;
+ return this.getSlideNJumpMoves(sq,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+ }
+
+ // Stop at the first capture found (if any)
+ atLeastOneCapture()
+ {
+ const color = this.turn;
+ const oppCol = this.getOppCol(color);
+ const [sizeX,sizeY] = VariantRules.size;
+ for (let i=0; i<sizeX; i++)
+ {
+ for (let j=0; j<sizeY; j++)
+ {
+ if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) != oppCol)
+ {
+ const moves = this.getPotentialMovesFrom([i,j]);
+ if (moves.length > 0)
+ {
+ for (let k=0; k<moves.length; k++)
+ {
+ if (moves[k].vanish.length==2 && this.filterValid([moves[k]]).length > 0)
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ // Trim all non-capturing moves
+ static KeepCaptures(moves)
+ {
+ return moves.filter(m => { return m.vanish.length == 2; });
+ }
+
+ getPossibleMovesFrom(sq)
+ {
+ let moves = this.filterValid( this.getPotentialMovesFrom(sq) );
+ // This is called from interface: we need to know if a capture is possible
+ if (this.atLeastOneCapture())
+ moves = VariantRules.KeepCaptures(moves);
+ return moves;
+ }
+
+ getAllValidMoves()
+ {
+ let moves = super.getAllValidMoves();
+ if (moves.some(m => { return m.vanish.length == 2; }))
+ moves = VariantRules.KeepCaptures(moves);
+ return moves;
+ }
+
+ underCheck(move)
+ {
+ return false; //No notion of check
+ }
+
+ getCheckSquares(move)
+ {
+ return [];
+ }
+
+ play(move, ingame)
+ {
+ if (!!ingame)
+ move.notation = this.getNotation(move);
+ this.moves.push(move);
+ this.epSquares.push( this.getEpSquare(move) );
+ VariantRules.PlayOnBoard(this.board, move);
+ }
+
+ undo(move)
+ {
+ VariantRules.UndoOnBoard(this.board, move);
+ this.epSquares.pop();
+ this.moves.pop();
+ }
+
+ checkGameEnd()
+ {
+ // No valid move: you win!
+ return this.turn == "w" ? "1-0" : "0-1";
+ }
+
+ static get VALUES() { //experimental...
+ return {
+ 'p': 1,
+ 'r': 7,
+ 'n': 3,
+ 'b': 3,
+ 'q': 5,
+ 'k': 5
+ };
+ }
+
+ static get SEARCH_DEPTH() { return 4; }
+
+ evalPosition()
+ {
+ return - super.evalPosition(); //better with less material
+ }
+}
static get THRESHOLD_MATE() {
return 500; //checkmates evals may be slightly below 1000
}
-
- getComputerMove()
- {
- let moves1 = this.getAllValidMoves();
- // Can I mate in 1 ?
- for (let i of _.shuffle(_.range(moves1.length)))
- {
- this.play(moves1[i]);
- const finish = (Math.abs(this.evalPosition()) >= VariantRules.THRESHOLD_MATE);
- this.undo(moves1[i]);
- if (finish)
- return moves1[i];
- }
- return super.getComputerMove(moves1);
- }
}
--- /dev/null
+//https://www.chessvariants.com/diffmove.dir/switching.html
+class SwitchingRules extends ChessRules
+{
+ //TODO:
+ // Move completion: promote switched pawns (as in Magnetic)
+
+ // To every piece potential moves: add switchings
+
+ // Prevent king switching if under check
+}
module.exports = [
- { "name" : "Checkered", "description" : "Shared pieces" },
- { "name" : "Zen", "description" : "Reverse captures" },
- { "name" : "Atomic", "description" : "Explosive captures" },
- { "name" : "Chess960", "description" : "Standard rules" },
- { "name" : "Antiking", "description" : "Keep antiking in check" },
- { "name" : "Magnetic", "description" : "Laws of attraction" },
- { "name" : "Alice", "description" : "Both sides of the mirror" },
- { "name" : "Grand", "description" : "Big board" },
- { "name" : "Wildebeest", "description" : "Balanced sliders & leapers" },
-// { "name" : "Loser", "description" : "Lose all pieces" },
-// { "name" : "Crazyhouse", "description" : "Captures reborn" },
-// { "name" : "Switching", "description" : "Exchange pieces positions" },
-// { "name" : "Absorption", "description" : "Capture enhance movements" },
+ { "name": "Checkered", "description": "Shared pieces" },
+ { "name": "Zen", "description": "Reverse captures" },
+ { "name": "Atomic", "description": "Explosive captures" },
+ { "name": "Chess960", "description": "Standard rules" },
+ { "name": "Antiking", "description": "Keep antiking in check" },
+ { "name": "Magnetic", "description": "Laws of attraction" },
+ { "name": "Alice", "description": "Both sides of the mirror" },
+ { "name": "Grand", "description": "Big board" },
+ { "name": "Wildebeest", "description": "Balanced sliders & leapers" },
+ { "name": "Loser", "description": "Lose all pieces" },
+ { "name": "Crazyhouse", "description": "Captures reborn" },
+ { "name": "Switching", "description": "Exchange pieces positions" },
+ { "name": "Extinction", "description": "Capture all of a kind" },
];
h3 Credits
p
- | Grand chess page on
+ | Grand chess page on
a(href="https://www.chessvariants.com/large.dir/freeling.html") chessvariants.com
| .
--- /dev/null
+p.boxed
+ | Win by losing all your pieces. Capture is mandatory.
+
+h3 Specifications
+
+ul
+ li Chessboard: standard.
+ li Material: standard.
+ li Non-capturing moves: standard (when allowed).
+ li Special moves: no castle.
+ li Captures: standard.
+ li End of game: stalemate or lose all material.
+
+h3 Basics
+
+p.
+ The goal is to lose all pieces, or get stalemated like on the following diagram.
+ The king has no royal status: it can be taken as any other piece.
+ Thus, there is no castle rule, no checks.
+
+figure.diagram-container
+ .diagram
+ | fen:6nB/6P1/8/4p3/2p1P3/2P5/8/8:
+ figcaption White cannot move: 1-0.
+
+h3 Special moves
+
+p.
+ Castling is not possible, but en-passant captures are allowed.
+ Pawns may promote into king (so you can potentially have several kings on the board).
+
+h3 End of the game
+
+p You can win by losing all material or be stalemated.
+
+h3 Credits
+
+p
+ | This is a popular variant, played in many places on the web.
+ | A starting point can be the
+ a(href="https://en.wikipedia.org/wiki/Losing_Chess") wikipedia page
+ | .
h3 Credits
p
- | Wildebeest page on
+ | Wildebeest page on
a(href="https://www.chessvariants.com/large.dir/wildebeest.html") chessvariants.com
| .