}
// 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}
},
[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',
[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',
// 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!
- self.play(compMove[0], "animate");
+ self.play(compMove[0], animate);
if (compMove.length == 2)
setTimeout( () => {
if (self.mode == "computer")
- self.play(compMove[1], "animate");
+ self.play(compMove[1], animate);
}, 750);
}, delay);
}
position: fenParts[0],
marks: fenParts[1],
orientation: fenParts[2],
+ shadow: fenParts[3],
};
},
},
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++)
{
- 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;
}
}
+ 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]
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 (!!args.marks && markArray[i][j])
+ if (markArray.length > 0 && markArray[i][j])
boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
boardDiv += "</div>";
}
{
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;
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
- | 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 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.
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
- | 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
-p.
- Possible starting point Wikipedia page ?
+p
+ | See for example the
+ a(href="https://www.chessvariants.com/multimove.dir/marseill.html")
+ | Marseillais Chess
+ | page on chessvariants.com.
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
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
- | 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