Some fixes. Loser implemented. Draft Extinction,Crazyhouse,Switching
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 27 Nov 2018 17:28:36 +0000 (18:28 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 27 Nov 2018 17:28:36 +0000 (18:28 +0100)
13 files changed:
TODO
public/javascripts/base_rules.js
public/javascripts/variants/Antiking.js
public/javascripts/variants/Crazyhouse.js [new file with mode: 0644]
public/javascripts/variants/Extinction.js [new file with mode: 0644]
public/javascripts/variants/Grand.js
public/javascripts/variants/Loser.js [new file with mode: 0644]
public/javascripts/variants/Magnetic.js
public/javascripts/variants/Switching.js [new file with mode: 0644]
variants.js
views/rules/Grand.pug
views/rules/Loser.pug [new file with mode: 0644]
views/rules/Wildebeest.pug

diff --git a/TODO b/TODO
index 23b3412..bcda2e8 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,2 +1,4 @@
 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)
index 6cab349..f1188ab 100644 (file)
@@ -492,9 +492,9 @@ class ChessRules
                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)
@@ -808,13 +808,23 @@ class ChessRules
        }
 
        // 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++)
@@ -847,11 +857,10 @@ class ChessRules
                        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)
@@ -921,7 +930,7 @@ class ChessRules
        {
                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++)
index 84f774a..f908495 100644 (file)
@@ -42,7 +42,7 @@ class AntikingRules extends ChessRules
                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));
        }
 
diff --git a/public/javascripts/variants/Crazyhouse.js b/public/javascripts/variants/Crazyhouse.js
new file mode 100644 (file)
index 0000000..0ee4bd3
--- /dev/null
@@ -0,0 +1,95 @@
+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;
+       }
+}
diff --git a/public/javascripts/variants/Extinction.js b/public/javascripts/variants/Extinction.js
new file mode 100644 (file)
index 0000000..db330d9
--- /dev/null
@@ -0,0 +1,86 @@
+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();
+       }
+}
index 1409bcc..844d62c 100644 (file)
@@ -171,9 +171,9 @@ class GrandRules extends ChessRules
                        || 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)
                {
@@ -185,9 +185,9 @@ class GrandRules extends ChessRules
                }
        }
 
-       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)
                {
diff --git a/public/javascripts/variants/Loser.js b/public/javascripts/variants/Loser.js
new file mode 100644 (file)
index 0000000..0c484c1
--- /dev/null
@@ -0,0 +1,144 @@
+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
+       }
+}
index e126f1c..844cc68 100644 (file)
@@ -216,19 +216,4 @@ class MagneticRules extends ChessRules
        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);
-       }
 }
diff --git a/public/javascripts/variants/Switching.js b/public/javascripts/variants/Switching.js
new file mode 100644 (file)
index 0000000..a7d6787
--- /dev/null
@@ -0,0 +1,10 @@
+//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
+}
index 175c426..8a00fe0 100644 (file)
@@ -1,15 +1,15 @@
 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" },
 ];
index d5ca201..67b849f 100644 (file)
@@ -47,6 +47,6 @@ p.
 h3 Credits
 
 p
-       | Grand chess page on
+       | Grand chess page on 
        a(href="https://www.chessvariants.com/large.dir/freeling.html") chessvariants.com
        | .
diff --git a/views/rules/Loser.pug b/views/rules/Loser.pug
new file mode 100644 (file)
index 0000000..3b6b1b6
--- /dev/null
@@ -0,0 +1,42 @@
+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
+       | .
index cb883f9..482ab6b 100644 (file)
@@ -45,6 +45,6 @@ p.
 h3 Credits
 
 p
-       | Wildebeest page on
+       | Wildebeest page on 
        a(href="https://www.chessvariants.com/large.dir/wildebeest.html") chessvariants.com
        | .