// 2) Check turn
if (!fenParsed.turn || !V.IsGoodTurn(fenParsed.turn))
return false;
- // 3) Check flags
+ // 3) Check moves count
+ if (!fenParsed.movesCount || !(parseInt(fenParsed.movesCount) >= 0))
+ return false;
+ // 4) Check flags
if (V.HasFlags && (!fenParsed.flags || !V.IsGoodFlags(fenParsed.flags)))
return false;
- // 4) Check enpassant
+ // 5) Check enpassant
if (V.HasEnpassant &&
(!fenParsed.enpassant || !V.IsGoodEnpassant(fenParsed.enpassant)))
{
}
// 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}
// Argument is a move:
const move = moveOrSquare;
const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
- if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
+ // TODO: next conditions are first for Atomic, and third for Checkered
+ if (move.appear.length > 0 && move.appear[0].p == V.PAWN && ["w","b"].includes(move.appear[0].c) && Math.abs(sx - ex) == 2)
{
return {
x: (sx + ex)/2,
// On which squares is color under check ? (for interface)
getCheckSquares(color)
{
- return this.isAttacked(this.kingPos[color], [this.getOppCol(color)])
+ return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
: [];
}
return pieces["b"].join("") +
"/pppppppp/8/8/8/8/PPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- " w 1111 -"; //add turn + flags + enpassant
+ " w 0 1111 -"; //add turn + flags + enpassant
}
// "Parse" FEN: just return untransformed string data
{
position: fenParts[0],
turn: fenParts[1],
+ movesCount: fenParts[2],
};
- let nextIdx = 2;
+ let nextIdx = 3;
if (V.HasFlags)
Object.assign(res, {flags: fenParts[nextIdx++]});
if (V.HasEnpassant)
// Return current fen (game state)
getFen()
{
- return this.getBaseFen() + " " + this.getTurnFen() +
+ return this.getBaseFen() + " " +
+ this.getTurnFen() + " " + this.movesCount +
(V.HasFlags ? (" " + this.getFlagsFen()) : "") +
(V.HasEnpassant ? (" " + this.getEnpassantFen()) : "");
}
// INITIALIZATION
// Fen string fully describes the game state
- constructor(fen, moves)
+ constructor(fen)
{
- this.moves = moves;
const fenParsed = V.ParseFen(fen);
this.board = V.GetBoard(fenParsed.position);
this.turn = fenParsed.turn[0]; //[0] to work with MarseilleRules
+ this.movesCount = parseInt(fenParsed.movesCount);
this.setOtherVariables(fen);
}
}
// Get opponent color
- getOppCol(color)
+ static GetOppCol(color)
{
return (color=="w" ? "b" : "w");
}
- get lastMove()
+ // Get next color (for compatibility with 3 and 4 players games)
+ static GetNextCol(color)
{
- const L = this.moves.length;
- return (L>0 ? this.moves[L-1] : null);
+ return V.GetOppCol(color);
}
// Pieces codes (for a clearer code)
const lastRank = (color == "w" ? 0 : sizeX-1);
const pawnColor = this.getColor(x,y); //can be different for checkered
- if (x+shiftX >= 0 && x+shiftX < sizeX) //TODO: always true
+ // NOTE: next condition is generally true (no pawn on last rank)
+ if (x+shiftX >= 0 && x+shiftX < sizeX)
{
const finalPieces = x + shiftX == lastRank
? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
return []; //x isn't first rank, or king has moved (shortcut)
// Castling ?
- const oppCol = this.getOppCol(c);
+ const oppCol = V.GetOppCol(c);
let moves = [];
let i = 0;
const finalSquares = [ [2,3], [V.size.y-2,V.size.y-3] ]; //king, then rook
getAllValidMoves()
{
const color = this.turn;
- const oppCol = this.getOppCol(color);
+ const oppCol = V.GetOppCol(color);
let potentialMoves = [];
for (let i=0; i<V.size.x; i++)
{
atLeastOneMove()
{
const color = this.turn;
- const oppCol = this.getOppCol(color);
+ const oppCol = V.GetOppCol(color);
for (let i=0; i<V.size.x; i++)
{
for (let j=0; j<V.size.y; j++)
// Is color under check after his move ?
underCheck(color)
{
- return this.isAttacked(this.kingPos[color], [this.getOppCol(color)]);
+ return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]);
}
/////////////////
// After move is played, update variables + flags
updateVariables(move)
{
- const piece = move.vanish[0].p;
- let c = move.vanish[0].c;
+ let piece = undefined;
+ let c = undefined;
+ if (move.vanish.length >= 1)
+ {
+ // Usual case, something is moved
+ piece = move.vanish[0].p;
+ c = move.vanish[0].c;
+ }
+ else
+ {
+ // Crazyhouse-like variants
+ piece = move.appear[0].p;
+ c = move.appear[0].c;
+ }
if (c == "c") //if (!["w","b"].includes(c))
{
// 'c = move.vanish[0].c' doesn't work for Checkered
- c = this.getOppCol(this.turn);
+ c = V.GetOppCol(this.turn);
}
const firstRank = (c == "w" ? V.size.x-1 : 0);
{
this.kingPos[c][0] = move.appear[0].x;
this.kingPos[c][1] = move.appear[0].y;
- this.castleFlags[c] = [false,false];
+ if (V.HasFlags)
+ this.castleFlags[c] = [false,false];
return;
}
- const oppCol = this.getOppCol(c);
- const oppFirstRank = (V.size.x-1) - firstRank;
- if (move.start.x == firstRank //our rook moves?
- && this.INIT_COL_ROOK[c].includes(move.start.y))
- {
- const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1);
- this.castleFlags[c][flagIdx] = false;
- }
- else if (move.end.x == oppFirstRank //we took opponent rook?
- && this.INIT_COL_ROOK[oppCol].includes(move.end.y))
+ if (V.HasFlags)
{
- const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1);
- this.castleFlags[oppCol][flagIdx] = false;
+ // Update castling flags if rooks are moved
+ const oppCol = V.GetOppCol(c);
+ const oppFirstRank = (V.size.x-1) - firstRank;
+ if (move.start.x == firstRank //our rook moves?
+ && this.INIT_COL_ROOK[c].includes(move.start.y))
+ {
+ const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1);
+ this.castleFlags[c][flagIdx] = false;
+ }
+ else if (move.end.x == oppFirstRank //we took opponent rook?
+ && this.INIT_COL_ROOK[oppCol].includes(move.end.y))
+ {
+ const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1);
+ this.castleFlags[oppCol][flagIdx] = false;
+ }
}
}
this.kingPos[c] = [move.start.x, move.start.y];
}
- play(move, ingame)
+ play(move)
{
// DEBUG:
// if (!this.states) this.states = [];
-// if (!ingame) this.states.push(this.getFen());
-
- if (!!ingame)
- move.notation = [this.getNotation(move), this.getLongNotation(move)];
+// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
+// this.states.push(stateFen);
if (V.HasFlags)
move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
if (V.HasEnpassant)
this.epSquares.push( this.getEpSquare(move) );
+ if (!move.color)
+ move.color = this.turn; //for interface
V.PlayOnBoard(this.board, move);
- this.turn = this.getOppCol(this.turn);
- this.moves.push(move);
+ this.turn = V.GetOppCol(this.turn);
+ this.movesCount++;
this.updateVariables(move);
-
- if (!!ingame)
- {
- // Hash of current game state *after move*, to detect repetitions
- move.hash = hex_md5(this.getFen());
- }
}
undo(move)
if (V.HasFlags)
this.disaggregateFlags(JSON.parse(move.flags));
V.UndoOnBoard(this.board, move);
- this.turn = this.getOppCol(this.turn);
- this.moves.pop();
+ this.turn = V.GetOppCol(this.turn);
+ this.movesCount--;
this.unupdateVariables(move);
// DEBUG:
-// if (this.getFen() != this.states[this.states.length-1])
-// debugger;
+// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
+// if (stateFen != this.states[this.states.length-1]) debugger;
// this.states.pop();
}
///////////////
// END OF GAME
- // Check for 3 repetitions (position + flags + turn)
- checkRepetition()
- {
- 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 move = this.moves[i];
- if (!this.hashStates[move.hash])
- this.hashStates[move.hash] = 1;
- else
- this.hashStates[move.hash]++;
- }
- return Object.values(this.hashStates).some(elt => { return (elt >= 3); });
- }
-
- // Is game over ? And if yes, what is the score ?
- checkGameOver()
+ // What is the score ? (Interesting if game is over)
+ getCurrentScore()
{
- if (this.checkRepetition())
- return "1/2";
-
if (this.atLeastOneMove()) // game not over
return "*";
// Game over
- return this.checkGameEnd();
- }
-
- // No moves are possible: compute score
- checkGameEnd()
- {
const color = this.turn;
// No valid move: stalemate or checkmate?
- if (!this.isAttacked(this.kingPos[color], [this.getOppCol(color)]))
+ if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
return "1/2";
// OK, checkmate
return (color == "w" ? "0-1" : "1-0");
{
this.play(moves1[i]);
let finish = (Math.abs(this.evalPosition()) >= V.THRESHOLD_MATE);
- if (!finish && !this.atLeastOneMove())
+ if (!finish)
{
- // Test mate (for other variants)
- const score = this.checkGameEnd();
- if (score != "1/2")
+ const score = this.getCurrentScore();
+ if (["1-0","0-1"].includes(score))
finish = true;
}
this.undo(moves1[i]);
// Initial self evaluation is very low: "I'm checkmated"
moves1[i].eval = (color=="w" ? -1 : 1) * maxeval;
this.play(moves1[i]);
+ const score1 = this.getCurrentScore();
let eval2 = undefined;
- if (this.atLeastOneMove())
+ if (score1 == "*")
{
// Initial enemy evaluation is very low too, for him
eval2 = (color=="w" ? 1 : -1) * maxeval;
for (let j=0; j<moves2.length; j++)
{
this.play(moves2[j]);
- let evalPos = undefined;
- if (this.atLeastOneMove())
- evalPos = this.evalPosition()
- else
- {
- // Working with scores is more accurate (necessary for Loser variant)
- const score = this.checkGameEnd();
- evalPos = (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval);
- }
+ const score2 = this.getCurrentScore();
+ const evalPos = score2 == "*"
+ ? this.evalPosition()
+ : (score2=="1/2" ? 0 : (score2=="1-0" ? 1 : -1) * maxeval);
if ((color == "w" && evalPos < eval2)
|| (color=="b" && evalPos > eval2))
{
}
}
else
- {
- const score = this.checkGameEnd();
- eval2 = (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval);
- }
+ eval2 = (score1=="1/2" ? 0 : (score1=="1-0" ? 1 : -1) * maxeval);
if ((color=="w" && eval2 > moves1[i].eval)
|| (color=="b" && eval2 < moves1[i].eval))
{
}
else
return currentBest;
- //console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; }));
+// console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; }));
candidates = [0];
for (let j=1; j<moves1.length && moves1[j].eval == moves1[0].eval; j++)
{
const maxeval = V.INFINITY;
const color = this.turn;
- if (!this.atLeastOneMove())
- {
- switch (this.checkGameEnd())
- {
- case "1/2":
- return 0;
- default:
- const score = this.checkGameEnd();
- return (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval);
- }
- }
+ const score = this.getCurrentScore();
+ if (score != "*")
+ return (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval);
if (depth == 0)
return this.evalPosition();
const moves = this.getAllValidMoves("computer");
/////////////////////////
// Context: just before move is played, turn hasn't changed
+ // TODO: un-ambiguous notation (switch on piece type, check directions...)
getNotation(move)
{
if (move.appear.length == 2 && move.appear[0].p == V.KING) //castle
(move.vanish.length > move.appear.length ? "x" : "") + finalSquare;
}
}
-
- // Complete the usual notation, may be required for de-ambiguification
- getLongNotation(move)
- {
- // Not encoding move. But short+long is enough
- return V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end);
- }
-
- // The score is already computed when calling this function
- getPGN(mycolor, score, fenStart, mode)
- {
- let pgn = "";
- pgn += '[Site "vchess.club"]<br>';
- const opponent = mode=="human" ? "Anonymous" : "Computer";
- pgn += '[Variant "' + variant + '"]<br>';
- pgn += '[Date "' + getDate(new Date()) + '"]<br>';
- pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]<br>';
- pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]<br>';
- pgn += '[FenStart "' + fenStart + '"]<br>';
- pgn += '[Fen "' + this.getFen() + '"]<br>';
- pgn += '[Result "' + score + '"]<br><br>';
-
- // Standard PGN
- for (let i=0; i<this.moves.length; i++)
- {
- if (i % 2 == 0)
- pgn += ((i/2)+1) + ".";
- pgn += this.moves[i].notation[0] + " ";
- }
- pgn += "<br><br>";
-
- // "Complete moves" PGN (helping in ambiguous cases)
- for (let i=0; i<this.moves.length; i++)
- {
- if (i % 2 == 0)
- pgn += ((i/2)+1) + ".";
- pgn += this.moves[i].notation[1] + " ";
- }
-
- return pgn;
- }
}