Implemented a very basic DarkBot + a few fixes + advance on rules
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 26 Dec 2018 11:17:48 +0000 (12:17 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 26 Dec 2018 11:17:48 +0000 (12:17 +0100)
public/javascripts/base_rules.js
public/javascripts/components/game.js
public/javascripts/components/rules.js
public/javascripts/utils/printDiagram.js
public/javascripts/variants/Dark.js
views/rules/Dark/en.pug
views/rules/Marseille/en.pug
views/rules/Upsidedown/en.pug

index 389ba34..891948b 100644 (file)
@@ -137,9 +137,9 @@ class ChessRules
        }
 
        // d --> 3 (column letter to number)
        }
 
        // d --> 3 (column letter to number)
-       static ColumnToCoord(colnum)
+       static ColumnToCoord(column)
        {
        {
-               return String.fromCharCode(97 + colnum);
+               return column.charCodeAt(0) - 97;
        }
 
        // a4 --> {x:3,y:0}
        }
 
        // a4 --> {x:3,y:0}
index 94340e6..6627252 100644 (file)
@@ -62,9 +62,8 @@ Vue.component('my-game', {
                        },
                        [h('i', { 'class': { "material-icons": true } }, "accessibility")])
                );
                        },
                        [h('i', { 'class': { "material-icons": true } }, "accessibility")])
                );
-               if (variant != "Dark" &&
-                       (["idle","computer","friend"].includes(this.mode)
-                               || ["friend","human"].includes(this.mode) && this.score != "*"))
+               if (["idle","computer","friend"].includes(this.mode)
+                       || (this.mode == "human" && this.score != "*"))
                {
                        actionArray.push(
                                h('button',
                {
                        actionArray.push(
                                h('button',
@@ -81,9 +80,8 @@ Vue.component('my-game', {
                                [h('i', { 'class': { "material-icons": true } }, "computer")])
                        );
                }
                                [h('i', { 'class': { "material-icons": true } }, "computer")])
                        );
                }
-               if (variant != "Dark" &&
-                       (["idle","friend"].includes(this.mode)
-                               || ["computer","human"].includes(this.mode) && this.score != "*"))
+               if (variant != "Dark" && (["idle","friend"].includes(this.mode)
+                       || (["computer","human"].includes(this.mode) && this.score != "*")))
                {
                        actionArray.push(
                                h('button',
                {
                        actionArray.push(
                                h('button',
@@ -1130,12 +1128,13 @@ Vue.component('my-game', {
                        // before they appear on page:
                        const delay = Math.max(500-(Date.now()-self.timeStart), 0);
                        setTimeout(() => {
                        // before they appear on page:
                        const delay = Math.max(500-(Date.now()-self.timeStart), 0);
                        setTimeout(() => {
+                               const animate = (variant!="Dark" ? "animate" : null);
                                if (self.mode == "computer") //warning: mode could have changed!
                                if (self.mode == "computer") //warning: mode could have changed!
-                                       self.play(compMove[0], "animate");
+                                       self.play(compMove[0], animate);
                                if (compMove.length == 2)
                                        setTimeout( () => {
                                                if (self.mode == "computer")
                                if (compMove.length == 2)
                                        setTimeout( () => {
                                                if (self.mode == "computer")
-                                                       self.play(compMove[1], "animate");
+                                                       self.play(compMove[1], animate);
                                        }, 750);
                        }, delay);
                }
                                        }, 750);
                        }, delay);
                }
index d8aaa0f..1a59787 100644 (file)
@@ -25,6 +25,7 @@ Vue.component('my-rules', {
                                position: fenParts[0],
                                marks: fenParts[1],
                                orientation: fenParts[2],
                                position: fenParts[0],
                                marks: fenParts[1],
                                orientation: fenParts[2],
+                               shadow: fenParts[3],
                        };
                },
        },
                        };
                },
        },
index 61c726e..b7282fe 100644 (file)
@@ -7,18 +7,46 @@ function getDiagram(args)
        const board = VariantRules.GetBoard(args.position);
        const orientation = args.orientation || "w";
        let markArray = [];
        const board = VariantRules.GetBoard(args.position);
        const orientation = args.orientation || "w";
        let markArray = [];
-       if (!!args.marks)
+       if (!!args.marks && args.marks != "-")
        {
                // Turn (human) marks into coordinates
                markArray = doubleArray(sizeX, sizeY, false);
                let squares = args.marks.split(",");
                for (let i=0; i<squares.length; i++)
                {
        {
                // Turn (human) marks into coordinates
                markArray = doubleArray(sizeX, sizeY, false);
                let squares = args.marks.split(",");
                for (let i=0; i<squares.length; i++)
                {
-                       const res = /^([a-z]+)([0-9]+)$/i.exec(squares[i]);
-                       const coords = V.SquareToCoords(res);
+                       const coords = V.SquareToCoords(squares[i]);
                        markArray[coords.x][coords.y] = true;
                }
        }
                        markArray[coords.x][coords.y] = true;
                }
        }
+       let shadowArray = [];
+       if (!!args.shadow && args.shadow != "-")
+       {
+               // Turn (human) shadow indications into coordinates
+               shadowArray = doubleArray(sizeX, sizeY, false);
+               let squares = args.shadow.split(",");
+               for (let i=0; i<squares.length; i++)
+               {
+                       const rownum = V.size.x - parseInt(squares[i]);
+                       if (!isNaN(rownum))
+                       {
+                               // Shadow a full row
+                               for (let i=0; i<V.size.y; i++)
+                                       shadowArray[rownum][i] = true;
+                               continue;
+                       }
+                       if (squares[i].length == 1)
+                       {
+                               // Shadow a full column
+                               const colnum = V.ColumnToCoord(squares[i]);
+                               for (let i=0; i<V.size.x; i++)
+                                       shadowArray[i][colnum] = true;
+                               continue;
+                       }
+                       // Shadow just one square:
+                       const coords = V.SquareToCoords(squares[i]);
+                       shadowArray[coords.x][coords.y] = true;
+               }
+       }
        let boardDiv = "";
        const [startX,startY,inc] = orientation == 'w'
                ? [0, 0, 1]
        let boardDiv = "";
        const [startX,startY,inc] = orientation == 'w'
                ? [0, 0, 1]
@@ -29,13 +57,15 @@ function getDiagram(args)
                for (let j=startY; j>=0 && j<sizeY; j+=inc)
                {
                        boardDiv += "<div class='board board" + sizeY + " " +
                for (let j=startY; j>=0 && j<sizeY; j+=inc)
                {
                        boardDiv += "<div class='board board" + sizeY + " " +
-                               ((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") + "'>";
+                               ((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") +
+                               (shadowArray.length > 0 && shadowArray[i][j] ? " in-shadow" : "") +
+                               "'>";
                        if (board[i][j] != V.EMPTY)
                        {
                                boardDiv += "<img src='/images/pieces/" +
                                        V.getPpath(board[i][j]) + ".svg' class='piece'/>";
                        }
                        if (board[i][j] != V.EMPTY)
                        {
                                boardDiv += "<img src='/images/pieces/" +
                                        V.getPpath(board[i][j]) + ".svg' class='piece'/>";
                        }
-                       if (!!args.marks && markArray[i][j])
+                       if (markArray.length > 0 && markArray[i][j])
                                boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
                        boardDiv += "</div>";
                }
                                boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
                        boardDiv += "</div>";
                }
index e3e093b..96f50de 100644 (file)
@@ -126,6 +126,150 @@ class DarkRules extends ChessRules
        {
                return 500; //checkmates evals may be slightly below 1000
        }
        {
                return 500; //checkmates evals may be slightly below 1000
        }
+
+       // In this special situation, we just look 1 half move ahead
+       getComputerMove()
+       {
+               const maxeval = V.INFINITY;
+               const color = this.turn;
+               const oppCol = this.getOppCol(color);
+               const pawnShift = (color == "w" ? -1 : 1);
+               const kp = this.kingPos[color];
+
+               // Do not cheat: the current enlightment is all we can see
+               const myLight = JSON.parse(JSON.stringify(this.enlightened[color]));
+
+               // Can a slider on (i,j) apparently take my king?
+               // NOTE: inaccurate because assume yes if some squares are shadowed
+               const sliderTake = ([i,j], piece) => {
+                       let step = undefined;
+                       if (piece == V.BISHOP)
+                       {
+                               if (Math.abs(kp[0] - i) == Math.abs(kp[1] - j))
+                               {
+                                       step =
+                                       [
+                                               (i-kp[0]) / Math.abs(i-kp[0]),
+                                               (j-kp[1]) / Math.abs(j-kp[1])
+                                       ];
+                               }
+                       }
+                       else if (piece == V.ROOK)
+                       {
+                               if (kp[0] == i)
+                                       step = [0, (j-kp[1]) / Math.abs(j-kp[1])];
+                               else if (kp[1] == j)
+                                       step = [(i-kp[0]) / Math.abs(i-kp[0]), 0];
+                       }
+                       if (!step)
+                               return false;
+                       // Check for obstacles
+                       let obstacle = false;
+                       for (
+                               let x=kp[0]+step[0], y=kp[1]+step[1];
+                               x != i && y != j;
+                               x += step[0], y+= step[1])
+                       {
+                               if (myLight[x][y] && this.board[x][y] != V.EMPTY)
+                               {
+                                       obstacle = true;
+                                       break;
+                               }
+                       }
+                       if (!obstacle)
+                               return true;
+                       return false;
+               };
+
+               // Do I see something which can take my king ?
+               const kingThreats = () => {
+                       for (let i=0; i<V.size.x; i++)
+                       {
+                               for (let j=0; j<V.size.y; j++)
+                               {
+                                       if (myLight[i][j] && this.board[i][j] != V.EMPTY
+                                               && this.getColor(i,j) != color)
+                                       {
+                                               switch (this.getPiece(i,j))
+                                               {
+                                                       case V.PAWN:
+                                                               if (kp[0] + pawnShift == i && Math.abs(kp[1]-j) == 1)
+                                                                       return true;
+                                                               break;
+                                                       case V.KNIGHT:
+                                                               if ((Math.abs(kp[0] - i) == 2 && Math.abs(kp[1] - j) == 1) ||
+                                                                       (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 2))
+                                                               {
+                                                                       return true;
+                                                               }
+                                                               break;
+                                                       case V.KING:
+                                                               if (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 1)
+                                                                       return true;
+                                                               break;
+                                                       case V.BISHOP:
+                                                               if (sliderTake([i,j], V.BISHOP))
+                                                                       return true;
+                                                               break;
+                                                       case V.ROOK:
+                                                               if (sliderTake([i,j], V.ROOK))
+                                                                       return true;
+                                                               break;
+                                                       case V.QUEEN:
+                                                               if (sliderTake([i,j], V.BISHOP) || sliderTake([i,j], V.ROOK))
+                                                                       return true;
+                                                               break;
+                                               }
+                                       }
+                               }
+                       }
+                       return false;
+               };
+
+               let moves = this.getAllValidMoves();
+               for (let move of moves)
+               {
+                       this.play(move);
+                       if (this.kingPos[oppCol][0] >= 0 && kingThreats())
+                       {
+                               // We didn't take opponent king, and our king will be captured: bad
+                               move.eval = -maxeval;
+                       }
+                       this.undo(move);
+                       if (!!move.eval)
+                               continue;
+
+                       move.eval = 0; //a priori...
+
+                       // Can I take something ? If yes, do it if it seems good...
+                       if (move.vanish.length == 2 && move.vanish[1].c != color) //avoid castle
+                       {
+                               const myPieceVal = V.VALUES[move.appear[0].p];
+                               const hisPieceVal = V.VALUES[move.vanish[1].p];
+                               if (myPieceVal <= hisPieceVal)
+                                       move.eval = hisPieceVal - myPieceVal + 2; //favor captures
+                               else
+                               {
+                                       // Taking a pawn with minor piece,
+                                       // or minor piece or pawn with a rook,
+                                       // or anything but a queen with a queen,
+                                       // or anything with a king.
+                                       // ==> Do it at random, although
+                                       //     this is clearly inferior to what a human can deduce...
+                                       move.eval = (Math.random() < 0.5 ? 1 : -1);
+                               }
+                       }
+               }
+
+               // TODO: also need to implement the case when an opponent piece (in light)
+               // is threatening something - maybe not the king, but e.g. pawn takes rook.
+
+               moves.sort((a,b) => b.eval - a.eval);
+               let candidates = [0];
+               for (let j=1; j<moves.length && moves[j].eval == moves[0].eval; j++)
+                       candidates.push(j);
+               return moves[_.sample(candidates, 1)];
+       }
 }
 
 const VariantRules = DarkRules;
 }
 
 const VariantRules = DarkRules;
index 49a4fc8..7dc9aa9 100644 (file)
@@ -16,12 +16,26 @@ p Incomplete information version of the orthodox game (also shuffled).
 h3 Basics
 
 p.
 h3 Basics
 
 p.
-       TODO
+       Before every move, players see only what their pieces can reach.
+       That is to say: empty squares where your pieces can go,
+       but also occupied squares where captures can be made.
+       For example on the illustration next, after 1.e4 d5 white sees a black
+       pawn on d5, the squares e5, h5, b5, a6 and all four first ranks.
 
 figure.diagram-container
        .diagram
 
 figure.diagram-container
        .diagram
-               | fen:r3kbnr/pp3ppp/3p4/4p3/8/8/PPPPPPPP/R1BQKBNR:
-       figcaption After the moves 1.Nc3 d6?? 2.Nd5 e5 3.Nxc7
+               | fen:8/8/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR - w 8,7,b6,c6,d6,e6,f6,g6,h6,a5,c5,f5,g5:
+       figcaption Standard initial position after 1.e4 d5
+
+p.
+       Choose your move carefully, based on what you can infer from the
+       opponent's past moves. Counting material, in particular, is crucial.
+       Good luck!
+
+p.
+       Note: the bot is not cheating - it really uses only the information described earlier.
+       Moreover, it is very basic and clearly less challenging that a human.
+       But it may be a fun start :)
 
 h3 End of the game
 
 
 h3 End of the game
 
@@ -29,5 +43,9 @@ p Win by capturing the king (no checks, no stalemate).
 
 h3 More information
 
 
 h3 More information
 
-p.
-       TODO: quote buho21, ...
+p
+       | I discovered this variant on 
+       a(href="https://www.buho21.com/") Buho21
+       | , which is a nice website but its spirit is very different than here
+       | (ranking system, VIP membership or ads, etc.).
+       | It seemed to be the only place to play DarkChess in live.
index 221bd83..f2a5e18 100644 (file)
@@ -24,12 +24,21 @@ p.
        otherwise has to be the first move.
        OK even if opponent moved his pawn at subturn 1.
 
        otherwise has to be the first move.
        OK even if opponent moved his pawn at subturn 1.
 
+p.
+       PGN game notation: since there are two moves at each turn except on move 1,
+       a double move in the game is indicated as two comma-separated (ordered) moves,
+       as in 3.Na5,Bd3 e6,f4 (the two first are white moves,
+       the two others are black moves).
+
 figure.diagram-container
        .diagram
 figure.diagram-container
        .diagram
-               | fen:r3kbnr/pp3ppp/3p4/4p3/8/8/PPPPPPPP/R1BQKBNR:
-       figcaption After the moves 1.Nc3 d6?? 2.Nd5 e5 3.Nxc7
+               | fen:r1bqkbnr/pppp1p1p/2n5/4p2p/4P3/5N2/PPPP1PPP/RNB1KB1R:
+       figcaption After the moves 1.e4 e5,Nc6 2.Qh5,Nf3 g6,gxh5
 
 h3 More information
 
 
 h3 More information
 
-p.
-       Possible starting point Wikipedia page ?
+p
+       | See for example the 
+       a(href="https://www.chessvariants.com/multimove.dir/marseill.html")
+               |       Marseillais Chess
+       | &nbsp;page on chessvariants.com.
index 695cbe4..ff70cd3 100644 (file)
@@ -1,13 +1,6 @@
 p.boxed
        | Pawns start on the 7th rank. Move a knight to promote them.
 
 p.boxed
        | Pawns start on the 7th rank. Move a knight to promote them.
 
-Diagram: ... threatens Nc5-Nd3# and allow 2.b8=Q
-
-figure.diagram-container
-       .diagram
-               | fen::
-       figcaption Standard initial position after 1.Na6
-
 h3 Specifications
 
 ul
 h3 Specifications
 
 ul
@@ -29,14 +22,16 @@ p.
        Since truly random start can allow un-defendable mate in 3 with a knight,
        the kings touch at least one knight in the initial position.
        This allows to move free out of potential check from the very beginning.
        Since truly random start can allow un-defendable mate in 3 with a knight,
        the kings touch at least one knight in the initial position.
        This allows to move free out of potential check from the very beginning.
-       Here is an example:
 
 
-// TODO: diagram
+p.
+       To illustrate this phenomenon, although it's defendable the standard initial
+       position allows the attack as 1.Na6 (threatening Nc5-Nd3#),
+       forcing the defense Nf3-Ne5. With a knight next to the king you have more options.
+
 figure.diagram-container
        .diagram
 figure.diagram-container
        .diagram
-               | fen:r7/2n5/1q6/5k2/8/8/K7/8:
-       figcaption.
-               The king cannot take on a8 because it's guarded by the knight: it's checkmate
+               | fen:R1BQKBNR/PPPPPPP/N7/8/8/8/pppppppp/rnbqkbnr:
+       figcaption Standard initial position after 1.Na6
 
 h3 Source
 
 
 h3 Source