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)
 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;
                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)
                        {
                                // 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
        }
 
        // 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;
        {
                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++)
 
                // 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)];
 
                        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)
                {
                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)
                        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;
        {
                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++)
                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);
                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));
        }
 
                        ((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");
        }
 
                        || 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)
                {
                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)
                {
                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
        }
        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 = [
 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
 h3 Credits
 
 p
-       | Grand chess page on
+       | Grand chess page on 
        a(href="https://www.chessvariants.com/large.dir/freeling.html") chessvariants.com
        | .
        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
 h3 Credits
 
 p
-       | Wildebeest page on
+       | Wildebeest page on 
        a(href="https://www.chessvariants.com/large.dir/wildebeest.html") chessvariants.com
        | .
        a(href="https://www.chessvariants.com/large.dir/wildebeest.html") chessvariants.com
        | .