+// (Orthodox) Chess rules are defined in ChessRules class.
+// Variants generally inherit from it, and modify some parts.
+
class PiPo //Piece+Position
{
// o: {piece[p], color[c], posX[x], posY[y]}
}
}
+// TODO: for animation, moves should contains "moving" and "fading" maybe...
class Move
{
// o: {appear, vanish, [start,] [end,]}
{
this.INIT_COL_KING = {'w':-1, 'b':-1};
this.INIT_COL_ROOK = {'w':[-1,-1], 'b':[-1,-1]};
- this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; //respective squares of white and black king
+ this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; //squares of white and black king
const fenParts = fen.split(" ");
const position = fenParts[0].split("/");
for (let i=0; i<position.length; i++)
// What are the knight moves from square x,y ?
getPotentialKnightMoves(sq)
{
- return this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.KNIGHT], "oneStep");
+ return this.getSlideNJumpMoves(
+ sq, VariantRules.steps[VariantRules.KNIGHT], "oneStep");
}
// What are the bishop moves from square x,y ?
canIplay(side, [x,y])
{
- return ((side=='w' && this.moves.length%2==0) || (side=='b' && this.moves.length%2==1))
+ return ((side=='w' && this.moves.length%2==0)
+ || (side=='b' && this.moves.length%2==1))
&& this.getColor(x,y) == side;
}
return this.filterValid( this.getPotentialMovesFrom(sq) );
}
- // TODO: once a promotion is filtered, the others results are same: useless computations
+ // TODO: promotions (into R,B,N,Q) should be filtered only once
filterValid(moves)
{
if (moves.length == 0)
{
for (let j=0; j<sizeY; j++)
{
- // Next condition ... != oppCol is a little HACK to work with checkered variant
+ // Next condition "!= oppCol" = harmless hack to work with checkered variant
if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) != oppCol)
Array.prototype.push.apply(potentialMoves, this.getPotentialMovesFrom([i,j]));
}
// NOTE: works also for extinction chess because depth is 3...
getComputerMove()
{
- this.shouldReturn = false;
const maxeval = VariantRules.INFINITY;
const color = this.turn;
- let moves1 = this.getAllValidMoves();
+ // Some variants may show a bigger moves list to the human (Switching),
+ // thus the argument "computer" below (which is generally ignored)
+ let moves1 = this.getAllValidMoves("computer");
// Can I mate in 1 ? (for Magnetic & Extinction)
for (let i of _.shuffle(_.range(moves1.length)))
{
eval2 = (color=="w" ? 1 : -1) * maxeval; //initialized with checkmate value
// Second half-move:
- let moves2 = this.getAllValidMoves();
+ let moves2 = this.getAllValidMoves("computer");
for (let j=0; j<moves2.length; j++)
{
this.play(moves2[j]);
const score = this.checkGameEnd();
eval2 = (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval);
}
- if ((color=="w" && eval2 > moves1[i].eval) || (color=="b" && eval2 < moves1[i].eval))
+ if ((color=="w" && eval2 > moves1[i].eval)
+ || (color=="b" && eval2 < moves1[i].eval))
+ {
moves1[i].eval = eval2;
+ }
this.undo(moves1[i]);
}
moves1.sort( (a,b) => { return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
candidates.push(j);
let currentBest = moves1[_.sample(candidates, 1)];
+ // From here, depth >= 3: may take a while, so we control time
+ const timeStart = Date.now();
+
// 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)
{
for (let i=0; i<moves1.length; i++)
{
- if (this.shouldReturn)
- return currentBest; //depth-2, minimum
+ if (Date.now()-timeStart >= 5000) //more than 5 seconds
+ return currentBest; //depth 2 at least
this.play(moves1[i]);
// 0.1 * oldEval : heuristic to avoid some bad moves (not all...)
moves1[i].eval = 0.1*moves1[i].eval +
}
if (depth == 0)
return this.evalPosition();
- const moves = this.getAllValidMoves();
+ const moves = this.getAllValidMoves("computer");
let v = color=="w" ? -maxeval : maxeval;
if (color == "w")
{
for (let i of ['w','b'])
{
for (let j=0; j<2; j++)
- fen += this.castleFlags[i][j] ? '1' : '0';
+ fen += (this.castleFlags[i][j] ? '1' : '0');
}
return fen;
}
// Context: just before move is played, turn hasn't changed
getNotation(move)
{
- if (move.appear.length == 2 && move.appear[0].p == VariantRules.KING)
- {
- // Castle
- if (move.end.y < move.start.y)
- return "0-0-0";
- else
- return "0-0";
- }
+ if (move.appear.length == 2 && move.appear[0].p == VariantRules.KING) //castle
+ return (move.end.y < move.start.y ? "0-0-0" : "0-0");
// Translate final square
const finalSquare =
// The score is already computed when calling this function
getPGN(mycolor, score, fenStart, mode)
{
+ const zeroPad = x => { return (x<10 ? "0" : "") + x; };
let pgn = "";
pgn += '[Site "vchess.club"]<br>';
const d = new Date();
const opponent = mode=="human" ? "Anonymous" : "Computer";
pgn += '[Variant "' + variant + '"]<br>';
- pgn += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate() + '"]<br>';
+ pgn += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) +
+ '-' + zeroPad(d.getDate()) + '"]<br>';
pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]<br>';
pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]<br>';
pgn += '[FenStart "' + fenStart + '"]<br>';
pgn += ((i/2)+1) + ".";
pgn += this.moves[i].notation[0] + " ";
}
- pgn += score + "<br><br>";
+ pgn += "<br><br>";
// "Complete moves" PGN (helping in ambiguous cases)
for (let i=0; i<this.moves.length; i++)
pgn += ((i/2)+1) + ".";
pgn += this.moves[i].notation[1] + " ";
}
- pgn += score;
return pgn;
}