this.kingPos[c] = [move.start.x, move.start.y];
}
+ // Hash of position+flags+turn after a move is played (to detect repetitions)
+ getHashState()
+ {
+ return hex_md5(this.getFen() + " " + this.turn);
+ }
+
play(move, ingame)
{
+ // DEBUG:
+// if (!this.states) this.states = [];
+// if (!ingame) this.states.push(JSON.stringify(this.board));
+
if (!!ingame)
move.notation = [this.getNotation(move), this.getLongNotation(move)];
this.moves.push(move);
this.epSquares.push( this.getEpSquare(move) );
VariantRules.PlayOnBoard(this.board, move);
+
+ if (!!ingame)
+ move.hash = this.getHashState();
}
undo(move)
this.moves.pop();
this.unupdateVariables(move);
this.parseFlags(JSON.parse(move.flags));
+
+ // DEBUG:
+// if (JSON.stringify(this.board) != this.states[this.states.length-1])
+// debugger;
+// this.states.pop();
}
//////////////
// END OF GAME
- // Basic check for 3 repetitions (in the last moves only)
+ // Check for 3 repetitions (position + flags + turn)
checkRepetition()
{
- if (this.moves.length >= 8)
+ if (!this.hashStates)
+ this.hashStates = {};
+ const startIndex =
+ Object.values(this.hashStates).reduce((a,b) => { return a+b; }, 0)
+ // Update this.hashStates with last move (or all moves if continuation)
+ // NOTE: redundant storage, but faster and moderate size
+ for (let i=startIndex; i<this.moves.length; i++)
{
- const L = this.moves.length;
- if (_.isEqual(this.moves[L-1], this.moves[L-5]) &&
- _.isEqual(this.moves[L-2], this.moves[L-6]) &&
- _.isEqual(this.moves[L-3], this.moves[L-7]) &&
- _.isEqual(this.moves[L-4], this.moves[L-8]))
- {
- return true;
- }
+ const move = this.moves[i];
+ if (!this.hashStates[move.hash])
+ this.hashStates[move.hash] = 1;
+ else
+ this.hashStates[move.hash]++;
}
- return false;
+ return Object.values(this.hashStates).some(elt => { return (elt >= 3); });
}
// Is game over ? And if yes, what is the score ?
// NOTE: works also for extinction chess because depth is 3...
getComputerMove()
{
- this.shouldReturn = false;
const maxeval = VariantRules.INFINITY;
const color = this.turn;
// Some variants may show a bigger moves list to the human (Switching),
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 +
// Setup the initial random (assymetric) position
static GenRandInitFen()
{
- let pieces = [new Array(8), new Array(8)];
+ let pieces = { "w": new Array(8), "b": new Array(8) };
// Shuffle pieces on first and last rank
- for (let c = 0; c <= 1; c++)
+ for (let c of ["w","b"])
{
let positions = _.range(8);
pieces[c][knight2Pos] = 'n';
pieces[c][rook2Pos] = 'r';
}
- let fen = pieces[0].join("") +
+ return pieces["b"].join("") +
"/pppppppp/8/8/8/8/PPPPPPPP/" +
- pieces[1].join("").toUpperCase() +
+ pieces["w"].join("").toUpperCase() +
" 1111"; //add flags
- return fen;
}
// Return current fen according to pieces+colors state