-MarseilleChess: revise bot and test
Translations (including About page), refresh rules
<html>
<head>
<meta charset="utf-8">
- <title>vchess - club</title>
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>vchess - club</title>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
- <link rel="stylesheet"
+ <link rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/mini.css/3.0.1/mini-default.min.css">
- <link rel="stylesheet"
- href="//fonts.googleapis.com/css?family=Open+Sans:400,700">
+ <link rel="stylesheet"
+ href="//fonts.googleapis.com/css?family=Open+Sans:400,700">
<style>
body {
--fore-color: #2c3e50;
export default {
name: "my-challenge-list",
- props: ["challenges"],
+ props: ["challenges"],
data: function() {
return {
st: store.state,
};
},
methods: {
- // Note: not using Vue here, but would be possible
+ // Note: not using Vue here, but would be possible
trySendMessage: function() {
let email = document.getElementById("userEmail");
let subject = document.getElementById("mailSubject");
}
);
},
- },
+ },
};
</script>
export default {
name: "my-game-list",
- props: ["games"],
+ props: ["games"],
data: function() {
return {
st: store.state,
showResult: false,
};
},
- computed: {
+ computed: {
sortedGames: function() {
// Show in order: games where it's my turn, my running games, my games, other games
this.showResult = this.games.some(g => g.score != "*");
localStorage["lang"] = e.target.value;
store.setLanguage(e.target.value);
},
- },
+ },
};
</script>
// Component for moves list on the right
export default {
name: 'my-move-list',
- props: ["moves","cursor","score","message","firstNum"],
+ props: ["moves","cursor","score","message","firstNum"],
watch: {
cursor: function(newCursor) {
if (window.innerWidth <= 767)
});
},
},
- render(h) {
- if (this.moves.length == 0)
- return;
- let tableContent = [];
- let moveCounter = 0;
- let tableRow = undefined;
- let moveCells = undefined;
- let curCellContent = "";
- let firstIndex = 0;
+ render(h) {
+ if (this.moves.length == 0)
+ return;
+ let tableContent = [];
+ let moveCounter = 0;
+ let tableRow = undefined;
+ let moveCells = undefined;
+ let curCellContent = "";
+ let firstIndex = 0;
for (let i=0; i<this.moves.length; i++)
- {
- if (this.moves[i].color == "w")
- {
- if (i == 0 || i>0 && this.moves[i-1].color=="b")
- {
- if (!!tableRow)
- {
- tableRow.children = moveCells;
- tableContent.push(tableRow);
- }
- moveCells = [
- h(
- "td",
- { domProps: { innerHTML: (++moveCounter) + "." } }
- )
- ];
- tableRow = h(
- "tr",
- { }
- );
- curCellContent = "";
+ {
+ if (this.moves[i].color == "w")
+ {
+ if (i == 0 || i>0 && this.moves[i-1].color=="b")
+ {
+ if (!!tableRow)
+ {
+ tableRow.children = moveCells;
+ tableContent.push(tableRow);
+ }
+ moveCells = [
+ h(
+ "td",
+ { domProps: { innerHTML: (++moveCounter) + "." } }
+ )
+ ];
+ tableRow = h(
+ "tr",
+ { }
+ );
+ curCellContent = "";
firstIndex = i;
- }
- }
+ }
+ }
// Next condition is fine because even if the first move is black,
// there will be the "..." which count as white move.
else if (this.moves[i].color == "b" && this.moves[i-1].color == "w")
firstIndex = i;
- curCellContent += this.moves[i].notation;
- if (i < this.moves.length-1 && this.moves[i+1].color == this.moves[i].color)
- curCellContent += ",";
- else //color change
- {
- moveCells.push(
- h(
- "td",
- {
- domProps: { innerHTML: curCellContent },
- on: { click: () => this.gotoMove(i) },
+ curCellContent += this.moves[i].notation;
+ if (i < this.moves.length-1 && this.moves[i+1].color == this.moves[i].color)
+ curCellContent += ",";
+ else //color change
+ {
+ moveCells.push(
+ h(
+ "td",
+ {
+ domProps: { innerHTML: curCellContent },
+ on: { click: () => this.gotoMove(i) },
"class": { "highlight-lm":
this.cursor >= firstIndex && this.cursor <= i },
- }
- )
- );
- curCellContent = "";
- }
- }
- // Complete last row, which might not be full:
- if (moveCells.length-1 == 1)
- {
+ }
+ )
+ );
+ curCellContent = "";
+ }
+ }
+ // Complete last row, which might not be full:
+ if (moveCells.length-1 == 1)
+ {
moveCells.push(
h(
"td",
{ domProps: { innerHTML: "" } }
)
);
- }
- tableRow.children = moveCells;
- tableContent.push(tableRow);
+ }
+ tableRow.children = moveCells;
+ tableContent.push(tableRow);
let rootElements = [];
if (this.score != "*")
{
},
},
tableContent
- )
+ )
);
- return h(
+ return h(
"div",
{ },
rootElements);
- },
+ },
methods: {
- gotoMove: function(index) {
- this.$emit("goto-move", index);
- },
- },
+ gotoMove: function(index) {
+ this.$emit("goto-move", index);
+ },
+ },
};
</script>
}
});
},
- methods: {
+ methods: {
updateSettings: function(event) {
const propName =
event.target.id.substr(3).replace(/^\w/, c => c.toLowerCase())
document.getElementById("gameContainer").style.width =
(boardSize + movesWidth) + "px";
},
- },
+ },
};
</script>
enterTime: Number.MAX_SAFE_INTEGER, //for a basic anti-bot strategy
};
},
- watch: {
- nameOrEmail: function(newValue) {
- if (newValue.indexOf('@') >= 0)
- {
- this.user.email = newValue;
- this.user.name = "";
- }
- else
- {
- this.user.name = newValue;
- this.user.email = "";
- }
- },
- },
+ watch: {
+ nameOrEmail: function(newValue) {
+ if (newValue.indexOf('@') >= 0)
+ {
+ this.user.email = newValue;
+ this.user.name = "";
+ }
+ else
+ {
+ this.user.name = newValue;
+ this.user.email = "";
+ }
+ },
+ },
computed: {
submitMessage: function() {
switch (this.stage)
export function checkChallenge(c)
{
- const vid = parseInt(c.vid);
- if (isNaN(vid) || vid <= 0)
- return "Please select a variant";
+ const vid = parseInt(c.vid);
+ if (isNaN(vid) || vid <= 0)
+ return "Please select a variant";
const tc = extractTime(c.timeControl);
if (!tc)
return "Wrong time control";
- // Basic alphanumeric check for opponent name
- if (!!c.to)
- {
+ // Basic alphanumeric check for opponent name
+ if (!!c.to)
+ {
// TODO: slightly redundant (see data/userCheck.js)
- if (!c.to.match(/^[\w]+$/))
- return "Wrong characters in opponent name";
- }
+ if (!c.to.match(/^[\w]+$/))
+ return "Wrong characters in opponent name";
+ }
// Allow custom FEN (and check it) only for individual challenges
if (c.fen.length > 0 && !!c.to)
export function checkNameEmail(o)
{
- if (typeof o.name === "string")
- {
- if (o.name.length == 0)
- return "Empty name";
- if (!o.name.match(/^[\w]+$/))
- return "Bad characters in name";
- }
- if (typeof o.email === "string")
- {
- if (o.email.length == 0)
- return "Empty email";
- if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
- return "Bad characters in email";
- }
+ if (typeof o.name === "string")
+ {
+ if (o.name.length == 0)
+ return "Empty name";
+ if (!o.name.match(/^[\w]+$/))
+ return "Bad characters in name";
+ }
+ if (typeof o.email === "string")
+ {
+ if (o.email.length == 0)
+ return "Empty email";
+ if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
+ return "Bad characters in email";
+ }
}
const Parameters =
{
- // URL of your socket server
- socketUrl: "ws://localhost:3000",
+ // URL of your socket server
+ socketUrl: "ws://localhost:3000",
- // URL of the server (leave blank for 1-server case)
- serverUrl: "http://localhost:3000",
+ // URL of the server (leave blank for 1-server case)
+ serverUrl: "http://localhost:3000",
// true if the server is at a different address
cors: true,
Vue.use(Router);
function loadView(view) {
- return () => import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`)
+ return () => import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`)
}
import { ajax } from "@/utils/ajax";
localStorage["myname"] = res.name;
localStorage["myid"] = res.id;
}
- // TODO: I don't like these 2 lines, "next('/')" should be enough
- window.location = "/";
+ // TODO: I don't like these 2 lines, "next('/')" should be enough
+ window.location = "/";
next();
}
);
// From JSON (encoded string values!) to "arg1=...&arg2=..."
function toQueryString(data)
{
- let data_str = "";
- Object.keys(data).forEach(k => {
- data_str += k + "=" + encodeURIComponent(data[k]) + "&";
- });
- return data_str.slice(0, -1); //remove last "&"
+ let data_str = "";
+ Object.keys(data).forEach(k => {
+ data_str += k + "=" + encodeURIComponent(data[k]) + "&";
+ });
+ return data_str.slice(0, -1); //remove last "&"
}
// data, error: optional
export function ajax(url, method, data, success, error)
{
- let xhr = new XMLHttpRequest();
- if (data === undefined || typeof(data) === "function") //no data
- {
- error = success;
- success = data;
- data = {};
- }
+ let xhr = new XMLHttpRequest();
+ if (data === undefined || typeof(data) === "function") //no data
+ {
+ error = success;
+ success = data;
+ data = {};
+ }
if (!success)
success = () => {}; //by default, do nothing
- if (!error)
- error = errmsg => { alert(errmsg); };
- xhr.onreadystatechange = function() {
- if (this.readyState == 4 && this.status == 200)
- {
+ if (!error)
+ error = errmsg => { alert(errmsg); };
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4 && this.status == 200)
+ {
let res_json = "";
- try {
- res_json = JSON.parse(xhr.responseText);
+ try {
+ res_json = JSON.parse(xhr.responseText);
} catch (e) {
- // Plain text (e.g. for rules retrieval)
- return success(xhr.responseText);
+ // Plain text (e.g. for rules retrieval)
+ return success(xhr.responseText);
}
if (!res_json.errmsg && !res_json.errno)
success(res_json);
- else
+ else
{
if (!!res_json.errmsg)
- error(res_json.errmsg);
+ error(res_json.errmsg);
else
error(res_json.code + ". errno = " + res_json.errno);
}
- }
- };
+ }
+ };
- if (["GET","DELETE"].includes(method) && !!data)
- {
- // Append query params to URL
- url += "/?" + toQueryString(data);
- }
- xhr.open(method, params.serverUrl + url, true);
- xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
- // Next line to allow cross-domain cookies in dev mode (TODO: if...)
+ if (["GET","DELETE"].includes(method) && !!data)
+ {
+ // Append query params to URL
+ url += "/?" + toQueryString(data);
+ }
+ xhr.open(method, params.serverUrl + url, true);
+ xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
+ // Next line to allow cross-domain cookies in dev mode (TODO: if...)
if (params.cors)
xhr.withCredentials = true;
if (["POST","PUT"].includes(method))
- {
- xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
- xhr.send(JSON.stringify(data));
- }
- else
- xhr.send();
+ {
+ xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
+ xhr.send(JSON.stringify(data));
+ }
+ else
+ xhr.send();
}
function zeroPad(x)
{
- return (x<10 ? "0" : "") + x;
+ return (x<10 ? "0" : "") + x;
}
export function getDate(d)
{
- return d.getFullYear() + '-' + zeroPad(d.getMonth()+1) + '-' + zeroPad(d.getDate());
+ return d.getFullYear() + '-' + zeroPad(d.getMonth()+1) + '-' + zeroPad(d.getDate());
}
export function getTime(d)
{
- return zeroPad(d.getHours()) + ":" + zeroPad(d.getMinutes()) + ":" +
- zeroPad(d.getSeconds());
+ return zeroPad(d.getHours()) + ":" + zeroPad(d.getMinutes()) + ":" +
+ zeroPad(d.getSeconds());
}
function padDigits(x)
// Turn (human) marks into coordinates
function getMarkArray(marks)
{
- if (!marks || marks == "-")
- return [];
- let markArray = ArrayFun.init(V.size.x, V.size.y, false);
- const squares = marks.split(",");
- for (let i=0; i<squares.length; i++)
- {
- const coords = V.SquareToCoords(squares[i]);
- markArray[coords.x][coords.y] = true;
- }
- return markArray;
+ if (!marks || marks == "-")
+ return [];
+ let markArray = ArrayFun.init(V.size.x, V.size.y, false);
+ const squares = marks.split(",");
+ for (let i=0; i<squares.length; i++)
+ {
+ const coords = V.SquareToCoords(squares[i]);
+ markArray[coords.x][coords.y] = true;
+ }
+ return markArray;
}
// Turn (human) shadow indications into coordinates
function getShadowArray(shadow)
{
- if (!shadow || shadow == "-")
- return [];
- let shadowArray = ArrayFun.init(V.size.x, V.size.y, false);
- const squares = 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;
- }
- if (squares[i].indexOf("-") >= 0)
- {
- // Shadow a range of squares, horizontally or vertically
- const firstLastSq = squares[i].split("-");
- const range =
- [
- V.SquareToCoords(firstLastSq[0]),
- V.SquareToCoords(firstLastSq[1])
- ];
- const step =
- [
- range[1].x == range[0].x
- ? 0
- : (range[1].x - range[0].x) / Math.abs(range[1].x - range[0].x),
- range[1].y == range[0].y
- ? 0
- : (range[1].y - range[0].y) / Math.abs(range[1].y - range[0].y)
- ];
- // Convention: range always from smaller to larger number
- for (let x=range[0].x, y=range[0].y; x <= range[1].x && y <= range[1].y;
- x += step[0], y += step[1])
- {
- shadowArray[x][y] = true;
- }
- continue;
- }
- // Shadow just one square:
- const coords = V.SquareToCoords(squares[i]);
- shadowArray[coords.x][coords.y] = true;
- }
- return shadowArray;
+ if (!shadow || shadow == "-")
+ return [];
+ let shadowArray = ArrayFun.init(V.size.x, V.size.y, false);
+ const squares = 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;
+ }
+ if (squares[i].indexOf("-") >= 0)
+ {
+ // Shadow a range of squares, horizontally or vertically
+ const firstLastSq = squares[i].split("-");
+ const range =
+ [
+ V.SquareToCoords(firstLastSq[0]),
+ V.SquareToCoords(firstLastSq[1])
+ ];
+ const step =
+ [
+ range[1].x == range[0].x
+ ? 0
+ : (range[1].x - range[0].x) / Math.abs(range[1].x - range[0].x),
+ range[1].y == range[0].y
+ ? 0
+ : (range[1].y - range[0].y) / Math.abs(range[1].y - range[0].y)
+ ];
+ // Convention: range always from smaller to larger number
+ for (let x=range[0].x, y=range[0].y; x <= range[1].x && y <= range[1].y;
+ x += step[0], y += step[1])
+ {
+ shadowArray[x][y] = true;
+ }
+ continue;
+ }
+ // Shadow just one square:
+ const coords = V.SquareToCoords(squares[i]);
+ shadowArray[coords.x][coords.y] = true;
+ }
+ return shadowArray;
}
// args: object with position (mandatory), and
// orientation, marks, shadow (optional)
export function getDiagram(args)
{
- // Obtain the array of pieces images names:
- const board = V.GetBoard(args.position);
- const orientation = args.orientation || "w";
- const markArray = getMarkArray(args.marks);
- const shadowArray = getShadowArray(args.shadow);
- let boardDiv = "";
- const [startX,startY,inc] = orientation == 'w'
- ? [0, 0, 1]
- : [V.size.x-1, V.size.y-1, -1];
- for (let i=startX; i>=0 && i<V.size.x; i+=inc)
- {
- boardDiv += "<div class='row'>";
- for (let j=startY; j>=0 && j<V.size.y; j+=inc)
- {
- boardDiv += "<div class='board board" + V.size.y + " " +
- ((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 " +
+ // Obtain the array of pieces images names:
+ const board = V.GetBoard(args.position);
+ const orientation = args.orientation || "w";
+ const markArray = getMarkArray(args.marks);
+ const shadowArray = getShadowArray(args.shadow);
+ let boardDiv = "";
+ const [startX,startY,inc] = orientation == 'w'
+ ? [0, 0, 1]
+ : [V.size.x-1, V.size.y-1, -1];
+ for (let i=startX; i>=0 && i<V.size.x; i+=inc)
+ {
+ boardDiv += "<div class='row'>";
+ for (let j=startY; j>=0 && j<V.size.y; j+=inc)
+ {
+ boardDiv += "<div class='board board" + V.size.y + " " +
+ ((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 (markArray.length > 0 && markArray[i][j])
- boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
- boardDiv += "</div>";
- }
- boardDiv += "</div>";
- }
- return boardDiv;
+ }
+ if (markArray.length > 0 && markArray[i][j])
+ boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
+ boardDiv += "</div>";
+ }
+ boardDiv += "</div>";
+ }
+ return boardDiv;
}
// Get the identifier of a HTML square from its numeric coordinates o.x,o.y.
export function getSquareId(o)
{
- // NOTE: a separator is required to allow any size of board
- return "sq-" + o.x + "-" + o.y;
+ // NOTE: a separator is required to allow any size of board
+ return "sq-" + o.x + "-" + o.y;
}
// Inverse function
export function getSquareFromId(id)
{
- const idParts = id.split('-');
- return [parseInt(idParts[1]), parseInt(idParts[2])];
+ const idParts = id.split('-');
+ return [parseInt(idParts[1]), parseInt(idParts[2])];
}
// TODO? atLeastOneMove() would be more efficient if rewritten here (less sideBoard computations)
export const VariantRules = class AliceRules extends ChessRules
{
- static get ALICE_PIECES()
- {
- return {
- 's': 'p',
- 't': 'q',
- 'u': 'r',
- 'c': 'b',
- 'o': 'n',
- 'l': 'k',
- };
- }
- static get ALICE_CODES()
- {
- return {
- 'p': 's',
- 'q': 't',
- 'r': 'u',
- 'b': 'c',
- 'n': 'o',
- 'k': 'l',
- };
- }
+ static get ALICE_PIECES()
+ {
+ return {
+ 's': 'p',
+ 't': 'q',
+ 'u': 'r',
+ 'c': 'b',
+ 'o': 'n',
+ 'l': 'k',
+ };
+ }
+ static get ALICE_CODES()
+ {
+ return {
+ 'p': 's',
+ 'q': 't',
+ 'r': 'u',
+ 'b': 'c',
+ 'n': 'o',
+ 'k': 'l',
+ };
+ }
- static getPpath(b)
- {
- return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
- }
+ static getPpath(b)
+ {
+ return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
+ }
- static get PIECES()
- {
- return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES));
- }
+ static get PIECES()
+ {
+ return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES));
+ }
- setOtherVariables(fen)
- {
- super.setOtherVariables(fen);
- const rows = V.ParseFen(fen).position.split("/");
- if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0)
- {
- // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
- for (let i=0; i<rows.length; i++)
- {
- let k = 0; //column index on board
- for (let j=0; j<rows[i].length; j++)
- {
- switch (rows[i].charAt(j))
- {
- case 'l':
- this.kingPos['b'] = [i,k];
- break;
- case 'L':
- this.kingPos['w'] = [i,k];
- break;
- default:
- const num = parseInt(rows[i].charAt(j));
- if (!isNaN(num))
- k += (num-1);
- }
- k++;
- }
- }
- }
- }
+ setOtherVariables(fen)
+ {
+ super.setOtherVariables(fen);
+ const rows = V.ParseFen(fen).position.split("/");
+ if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0)
+ {
+ // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
+ for (let i=0; i<rows.length; i++)
+ {
+ let k = 0; //column index on board
+ for (let j=0; j<rows[i].length; j++)
+ {
+ switch (rows[i].charAt(j))
+ {
+ case 'l':
+ this.kingPos['b'] = [i,k];
+ break;
+ case 'L':
+ this.kingPos['w'] = [i,k];
+ break;
+ default:
+ const num = parseInt(rows[i].charAt(j));
+ if (!isNaN(num))
+ k += (num-1);
+ }
+ k++;
+ }
+ }
+ }
+ }
- // Return the (standard) color+piece notation at a square for a board
- getSquareOccupation(i, j, mirrorSide)
- {
- const piece = this.getPiece(i,j);
- if (mirrorSide==1 && Object.keys(V.ALICE_CODES).includes(piece))
- return this.board[i][j];
- else if (mirrorSide==2 && Object.keys(V.ALICE_PIECES).includes(piece))
- return this.getColor(i,j) + V.ALICE_PIECES[piece];
- return "";
- }
+ // Return the (standard) color+piece notation at a square for a board
+ getSquareOccupation(i, j, mirrorSide)
+ {
+ const piece = this.getPiece(i,j);
+ if (mirrorSide==1 && Object.keys(V.ALICE_CODES).includes(piece))
+ return this.board[i][j];
+ else if (mirrorSide==2 && Object.keys(V.ALICE_PIECES).includes(piece))
+ return this.getColor(i,j) + V.ALICE_PIECES[piece];
+ return "";
+ }
- // Build board of the given (mirror)side
- getSideBoard(mirrorSide)
- {
- // Build corresponding board from complete board
- let sideBoard = ArrayFun.init(V.size.x, V.size.y, "");
- for (let i=0; i<V.size.x; i++)
- {
- for (let j=0; j<V.size.y; j++)
- sideBoard[i][j] = this.getSquareOccupation(i, j, mirrorSide);
- }
- return sideBoard;
- }
+ // Build board of the given (mirror)side
+ getSideBoard(mirrorSide)
+ {
+ // Build corresponding board from complete board
+ let sideBoard = ArrayFun.init(V.size.x, V.size.y, "");
+ for (let i=0; i<V.size.x; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ sideBoard[i][j] = this.getSquareOccupation(i, j, mirrorSide);
+ }
+ return sideBoard;
+ }
- // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
- getPotentialMovesFrom([x,y], sideBoard)
- {
- const pieces = Object.keys(V.ALICE_CODES);
- const codes = Object.keys(V.ALICE_PIECES);
- const mirrorSide = (pieces.includes(this.getPiece(x,y)) ? 1 : 2);
+ // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
+ getPotentialMovesFrom([x,y], sideBoard)
+ {
+ const pieces = Object.keys(V.ALICE_CODES);
+ const codes = Object.keys(V.ALICE_PIECES);
+ const mirrorSide = (pieces.includes(this.getPiece(x,y)) ? 1 : 2);
if (!sideBoard)
- sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
- const color = this.getColor(x,y);
+ sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
+ const color = this.getColor(x,y);
- // Search valid moves on sideBoard
- const saveBoard = this.board;
- this.board = sideBoard[mirrorSide-1];
- const moves = super.getPotentialMovesFrom([x,y])
- .filter(m => {
- // Filter out king moves which result in under-check position on
- // current board (before mirror traversing)
- let aprioriValid = true;
- if (m.appear[0].p == V.KING)
- {
- this.play(m);
- if (this.underCheck(color, sideBoard))
- aprioriValid = false;
- this.undo(m);
- }
- return aprioriValid;
- });
- this.board = saveBoard;
+ // Search valid moves on sideBoard
+ const saveBoard = this.board;
+ this.board = sideBoard[mirrorSide-1];
+ const moves = super.getPotentialMovesFrom([x,y])
+ .filter(m => {
+ // Filter out king moves which result in under-check position on
+ // current board (before mirror traversing)
+ let aprioriValid = true;
+ if (m.appear[0].p == V.KING)
+ {
+ this.play(m);
+ if (this.underCheck(color, sideBoard))
+ aprioriValid = false;
+ this.undo(m);
+ }
+ return aprioriValid;
+ });
+ this.board = saveBoard;
- // Finally filter impossible moves
- const res = moves.filter(m => {
- if (m.appear.length == 2) //castle
- {
- // appear[i] must be an empty square on the other board
- for (let psq of m.appear)
- {
- if (this.getSquareOccupation(psq.x,psq.y,3-mirrorSide) != V.EMPTY)
- return false;
- }
- }
- else if (this.board[m.end.x][m.end.y] != V.EMPTY)
- {
- // Attempt to capture
- const piece = this.getPiece(m.end.x,m.end.y);
- if ((mirrorSide==1 && codes.includes(piece))
- || (mirrorSide==2 && pieces.includes(piece)))
- {
- return false;
- }
- }
- // If the move is computed on board1, m.appear change for Alice pieces.
- if (mirrorSide==1)
- {
- m.appear.forEach(psq => { //forEach: castling taken into account
- psq.p = V.ALICE_CODES[psq.p]; //goto board2
- });
- }
- else //move on board2: mark vanishing pieces as Alice
- {
- m.vanish.forEach(psq => {
- psq.p = V.ALICE_CODES[psq.p];
- });
- }
- // Fix en-passant captures
- if (m.vanish[0].p == V.PAWN && m.vanish.length == 2
- && this.board[m.end.x][m.end.y] == V.EMPTY)
- {
- m.vanish[1].c = V.GetOppCol(this.getColor(x,y));
- // In the special case of en-passant, if
- // - board1 takes board2 : vanish[1] --> Alice
- // - board2 takes board1 : vanish[1] --> normal
- let van = m.vanish[1];
- if (mirrorSide==1 && codes.includes(this.getPiece(van.x,van.y)))
- van.p = V.ALICE_CODES[van.p];
- else if (mirrorSide==2 && pieces.includes(this.getPiece(van.x,van.y)))
- van.p = V.ALICE_PIECES[van.p];
- }
- return true;
- });
- return res;
- }
+ // Finally filter impossible moves
+ const res = moves.filter(m => {
+ if (m.appear.length == 2) //castle
+ {
+ // appear[i] must be an empty square on the other board
+ for (let psq of m.appear)
+ {
+ if (this.getSquareOccupation(psq.x,psq.y,3-mirrorSide) != V.EMPTY)
+ return false;
+ }
+ }
+ else if (this.board[m.end.x][m.end.y] != V.EMPTY)
+ {
+ // Attempt to capture
+ const piece = this.getPiece(m.end.x,m.end.y);
+ if ((mirrorSide==1 && codes.includes(piece))
+ || (mirrorSide==2 && pieces.includes(piece)))
+ {
+ return false;
+ }
+ }
+ // If the move is computed on board1, m.appear change for Alice pieces.
+ if (mirrorSide==1)
+ {
+ m.appear.forEach(psq => { //forEach: castling taken into account
+ psq.p = V.ALICE_CODES[psq.p]; //goto board2
+ });
+ }
+ else //move on board2: mark vanishing pieces as Alice
+ {
+ m.vanish.forEach(psq => {
+ psq.p = V.ALICE_CODES[psq.p];
+ });
+ }
+ // Fix en-passant captures
+ if (m.vanish[0].p == V.PAWN && m.vanish.length == 2
+ && this.board[m.end.x][m.end.y] == V.EMPTY)
+ {
+ m.vanish[1].c = V.GetOppCol(this.getColor(x,y));
+ // In the special case of en-passant, if
+ // - board1 takes board2 : vanish[1] --> Alice
+ // - board2 takes board1 : vanish[1] --> normal
+ let van = m.vanish[1];
+ if (mirrorSide==1 && codes.includes(this.getPiece(van.x,van.y)))
+ van.p = V.ALICE_CODES[van.p];
+ else if (mirrorSide==2 && pieces.includes(this.getPiece(van.x,van.y)))
+ van.p = V.ALICE_PIECES[van.p];
+ }
+ return true;
+ });
+ return res;
+ }
- filterValid(moves, sideBoard)
- {
- if (moves.length == 0)
- return [];
- if (!sideBoard)
+ filterValid(moves, sideBoard)
+ {
+ if (moves.length == 0)
+ return [];
+ if (!sideBoard)
sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
- const color = this.turn;
- return moves.filter(m => {
- this.playSide(m, sideBoard); //no need to track flags
- const res = !this.underCheck(color, sideBoard);
- this.undoSide(m, sideBoard);
- return res;
- });
- }
+ const color = this.turn;
+ return moves.filter(m => {
+ this.playSide(m, sideBoard); //no need to track flags
+ const res = !this.underCheck(color, sideBoard);
+ this.undoSide(m, sideBoard);
+ return res;
+ });
+ }
- getAllValidMoves()
- {
- const color = this.turn;
- const oppCol = V.GetOppCol(color);
- let potentialMoves = [];
- const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
- for (var i=0; i<V.size.x; i++)
- {
- for (var j=0; j<V.size.y; j++)
- {
- if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == color)
- {
- Array.prototype.push.apply(potentialMoves,
- this.getPotentialMovesFrom([i,j], sideBoard));
- }
- }
- }
- return this.filterValid(potentialMoves, sideBoard);
- }
+ getAllValidMoves()
+ {
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ let potentialMoves = [];
+ const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
+ for (var i=0; i<V.size.x; i++)
+ {
+ for (var j=0; j<V.size.y; j++)
+ {
+ if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == color)
+ {
+ Array.prototype.push.apply(potentialMoves,
+ this.getPotentialMovesFrom([i,j], sideBoard));
+ }
+ }
+ }
+ return this.filterValid(potentialMoves, sideBoard);
+ }
- // Play on sideboards [TODO: only one sideBoard required]
- playSide(move, sideBoard)
- {
- const pieces = Object.keys(V.ALICE_CODES);
- move.vanish.forEach(psq => {
- const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
- sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
- });
- move.appear.forEach(psq => {
- const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
- const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
- sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
- if (piece == V.KING)
- this.kingPos[psq.c] = [psq.x,psq.y];
- });
- }
+ // Play on sideboards [TODO: only one sideBoard required]
+ playSide(move, sideBoard)
+ {
+ const pieces = Object.keys(V.ALICE_CODES);
+ move.vanish.forEach(psq => {
+ const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+ sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
+ });
+ move.appear.forEach(psq => {
+ const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+ const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
+ sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
+ if (piece == V.KING)
+ this.kingPos[psq.c] = [psq.x,psq.y];
+ });
+ }
- // Undo on sideboards
- undoSide(move, sideBoard)
- {
- const pieces = Object.keys(V.ALICE_CODES);
- move.appear.forEach(psq => {
- const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
- sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
- });
- move.vanish.forEach(psq => {
- const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
- const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
- sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
- if (piece == V.KING)
- this.kingPos[psq.c] = [psq.x,psq.y];
- });
- }
+ // Undo on sideboards
+ undoSide(move, sideBoard)
+ {
+ const pieces = Object.keys(V.ALICE_CODES);
+ move.appear.forEach(psq => {
+ const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+ sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
+ });
+ move.vanish.forEach(psq => {
+ const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+ const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
+ sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
+ if (piece == V.KING)
+ this.kingPos[psq.c] = [psq.x,psq.y];
+ });
+ }
// sideBoard: arg containing both boards (see getAllValidMoves())
- underCheck(color, sideBoard)
- {
- const kp = this.kingPos[color];
- const mirrorSide = (sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2);
- let saveBoard = this.board;
- this.board = sideBoard[mirrorSide-1];
- let res = this.isAttacked(kp, [V.GetOppCol(color)]);
- this.board = saveBoard;
- return res;
- }
+ underCheck(color, sideBoard)
+ {
+ const kp = this.kingPos[color];
+ const mirrorSide = (sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2);
+ let saveBoard = this.board;
+ this.board = sideBoard[mirrorSide-1];
+ let res = this.isAttacked(kp, [V.GetOppCol(color)]);
+ this.board = saveBoard;
+ return res;
+ }
- getCheckSquares(color)
- {
- const pieces = Object.keys(V.ALICE_CODES);
- const kp = this.kingPos[color];
- const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
- let sideBoard = this.getSideBoard(mirrorSide);
- let saveBoard = this.board;
- this.board = sideBoard;
- let res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
- ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
- : [ ];
- this.board = saveBoard;
- return res;
- }
+ getCheckSquares(color)
+ {
+ const pieces = Object.keys(V.ALICE_CODES);
+ const kp = this.kingPos[color];
+ const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
+ let sideBoard = this.getSideBoard(mirrorSide);
+ let saveBoard = this.board;
+ this.board = sideBoard;
+ let res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
+ ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
+ : [ ];
+ this.board = saveBoard;
+ return res;
+ }
- updateVariables(move)
- {
- super.updateVariables(move); //standard king
- const piece = move.vanish[0].p;
- const c = move.vanish[0].c;
- // "l" = Alice king
- if (piece == "l")
- {
- this.kingPos[c][0] = move.appear[0].x;
- this.kingPos[c][1] = move.appear[0].y;
- this.castleFlags[c] = [false,false];
- }
- }
+ updateVariables(move)
+ {
+ super.updateVariables(move); //standard king
+ const piece = move.vanish[0].p;
+ const c = move.vanish[0].c;
+ // "l" = Alice king
+ if (piece == "l")
+ {
+ this.kingPos[c][0] = move.appear[0].x;
+ this.kingPos[c][1] = move.appear[0].y;
+ this.castleFlags[c] = [false,false];
+ }
+ }
- unupdateVariables(move)
- {
- super.unupdateVariables(move);
- const c = move.vanish[0].c;
- if (move.vanish[0].p == "l")
- this.kingPos[c] = [move.start.x, move.start.y];
- }
+ unupdateVariables(move)
+ {
+ super.unupdateVariables(move);
+ const c = move.vanish[0].c;
+ if (move.vanish[0].p == "l")
+ this.kingPos[c] = [move.start.x, move.start.y];
+ }
- getCurrentScore()
- {
+ getCurrentScore()
+ {
if (this.atLeastOneMove()) // game not over
return "*";
const pieces = Object.keys(V.ALICE_CODES);
- const color = this.turn;
- const kp = this.kingPos[color];
- const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
- let sideBoard = this.getSideBoard(mirrorSide);
- let saveBoard = this.board;
- this.board = sideBoard;
- let res = "*";
- if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
- res = "1/2";
- else
- res = (color == "w" ? "0-1" : "1-0");
- this.board = saveBoard;
- return res;
- }
+ const color = this.turn;
+ const kp = this.kingPos[color];
+ const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
+ let sideBoard = this.getSideBoard(mirrorSide);
+ let saveBoard = this.board;
+ this.board = sideBoard;
+ let res = "*";
+ if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
+ res = "1/2";
+ else
+ res = (color == "w" ? "0-1" : "1-0");
+ this.board = saveBoard;
+ return res;
+ }
- static get VALUES()
- {
- return Object.assign(
- ChessRules.VALUES,
- {
- 's': 1,
- 'u': 5,
- 'o': 3,
- 'c': 3,
- 't': 9,
- 'l': 1000,
- }
- );
- }
+ static get VALUES()
+ {
+ return Object.assign(
+ ChessRules.VALUES,
+ {
+ 's': 1,
+ 'u': 5,
+ 'o': 3,
+ 'c': 3,
+ 't': 9,
+ 'l': 1000,
+ }
+ );
+ }
- getNotation(move)
- {
- if (move.appear.length == 2 && move.appear[0].p == V.KING)
- {
- if (move.end.y < move.start.y)
- return "0-0-0";
- else
- return "0-0";
- }
+ getNotation(move)
+ {
+ if (move.appear.length == 2 && move.appear[0].p == V.KING)
+ {
+ if (move.end.y < move.start.y)
+ return "0-0-0";
+ else
+ return "0-0";
+ }
- const finalSquare = V.CoordsToSquare(move.end);
- const piece = this.getPiece(move.start.x, move.start.y);
+ const finalSquare = V.CoordsToSquare(move.end);
+ const piece = this.getPiece(move.start.x, move.start.y);
- const captureMark = (move.vanish.length > move.appear.length ? "x" : "");
- let pawnMark = "";
- if (["p","s"].includes(piece) && captureMark.length == 1)
- pawnMark = V.CoordToColumn(move.start.y); //start column
+ const captureMark = (move.vanish.length > move.appear.length ? "x" : "");
+ let pawnMark = "";
+ if (["p","s"].includes(piece) && captureMark.length == 1)
+ pawnMark = V.CoordToColumn(move.start.y); //start column
- // Piece or pawn movement
- let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
- if (['s','p'].includes(piece) && !['s','p'].includes(move.appear[0].p))
- {
- // Promotion
- notation += "=" + move.appear[0].p.toUpperCase();
- }
- return notation;
- }
+ // Piece or pawn movement
+ let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
+ if (['s','p'].includes(piece) && !['s','p'].includes(move.appear[0].p))
+ {
+ // Promotion
+ notation += "=" + move.appear[0].p.toUpperCase();
+ }
+ return notation;
+ }
}
export const VariantRules = class AntikingRules extends ChessRules
{
- static getPpath(b)
- {
- return b[1]=='a' ? "Antiking/"+b : b;
- }
-
- static get ANTIKING() { return 'a'; }
-
- static get PIECES()
- {
- return ChessRules.PIECES.concat([V.ANTIKING]);
- }
-
- setOtherVariables(fen)
- {
- super.setOtherVariables(fen);
- this.antikingPos = {'w':[-1,-1], 'b':[-1,-1]};
- const rows = V.ParseFen(fen).position.split("/");
- for (let i=0; i<rows.length; i++)
- {
- let k = 0;
- for (let j=0; j<rows[i].length; j++)
- {
- switch (rows[i].charAt(j))
- {
- case 'a':
- this.antikingPos['b'] = [i,k];
- break;
- case 'A':
- this.antikingPos['w'] = [i,k];
- break;
- default:
- const num = parseInt(rows[i].charAt(j));
- if (!isNaN(num))
- k += (num-1);
- }
- k++;
- }
- }
- }
-
- canTake([x1,y1], [x2,y2])
- {
- const piece1 = this.getPiece(x1,y1);
- const piece2 = this.getPiece(x2,y2);
- const color1 = this.getColor(x1,y1);
- const color2 = this.getColor(x2,y2);
- return piece2 != "a" &&
- ((piece1 != "a" && color1 != color2) || (piece1 == "a" && color1 == color2));
- }
-
- getPotentialMovesFrom([x,y])
- {
- switch (this.getPiece(x,y))
- {
- case V.ANTIKING:
- return this.getPotentialAntikingMoves([x,y]);
- default:
- return super.getPotentialMovesFrom([x,y]);
- }
- }
-
- getPotentialAntikingMoves(sq)
- {
- return this.getSlideNJumpMoves(sq,
- V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
- }
-
- isAttacked(sq, colors)
- {
- return (super.isAttacked(sq, colors) || this.isAttackedByAntiking(sq, colors));
- }
-
- isAttackedByKing([x,y], colors)
- {
- if (this.getPiece(x,y) == V.ANTIKING)
- return false; //antiking is not attacked by king
- return this.isAttackedBySlideNJump([x,y], colors, V.KING,
- V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
- }
-
- isAttackedByAntiking([x,y], colors)
- {
- if ([V.KING,V.ANTIKING].includes(this.getPiece(x,y)))
- return false; //(anti)king is not attacked by antiking
- return this.isAttackedBySlideNJump([x,y], colors, V.ANTIKING,
- V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
- }
-
- underCheck(color)
- {
- const oppCol = V.GetOppCol(color);
- let res = this.isAttacked(this.kingPos[color], [oppCol])
- || !this.isAttacked(this.antikingPos[color], [oppCol]);
- return res;
- }
-
- getCheckSquares(color)
- {
- let res = super.getCheckSquares(color);
- if (!this.isAttacked(this.antikingPos[color], [V.GetOppCol(color)]))
- res.push(JSON.parse(JSON.stringify(this.antikingPos[color])));
- return res;
- }
-
- updateVariables(move)
- {
- super.updateVariables(move);
- const piece = move.vanish[0].p;
- const c = move.vanish[0].c;
- // Update antiking position
- if (piece == V.ANTIKING)
- {
- this.antikingPos[c][0] = move.appear[0].x;
- this.antikingPos[c][1] = move.appear[0].y;
- }
- }
-
- unupdateVariables(move)
- {
- super.unupdateVariables(move);
- const c = move.vanish[0].c;
- if (move.vanish[0].p == V.ANTIKING)
- this.antikingPos[c] = [move.start.x, move.start.y];
- }
-
- getCurrentScore()
- {
+ static getPpath(b)
+ {
+ return b[1]=='a' ? "Antiking/"+b : b;
+ }
+
+ static get ANTIKING() { return 'a'; }
+
+ static get PIECES()
+ {
+ return ChessRules.PIECES.concat([V.ANTIKING]);
+ }
+
+ setOtherVariables(fen)
+ {
+ super.setOtherVariables(fen);
+ this.antikingPos = {'w':[-1,-1], 'b':[-1,-1]};
+ const rows = V.ParseFen(fen).position.split("/");
+ for (let i=0; i<rows.length; i++)
+ {
+ let k = 0;
+ for (let j=0; j<rows[i].length; j++)
+ {
+ switch (rows[i].charAt(j))
+ {
+ case 'a':
+ this.antikingPos['b'] = [i,k];
+ break;
+ case 'A':
+ this.antikingPos['w'] = [i,k];
+ break;
+ default:
+ const num = parseInt(rows[i].charAt(j));
+ if (!isNaN(num))
+ k += (num-1);
+ }
+ k++;
+ }
+ }
+ }
+
+ canTake([x1,y1], [x2,y2])
+ {
+ const piece1 = this.getPiece(x1,y1);
+ const piece2 = this.getPiece(x2,y2);
+ const color1 = this.getColor(x1,y1);
+ const color2 = this.getColor(x2,y2);
+ return piece2 != "a" &&
+ ((piece1 != "a" && color1 != color2) || (piece1 == "a" && color1 == color2));
+ }
+
+ getPotentialMovesFrom([x,y])
+ {
+ switch (this.getPiece(x,y))
+ {
+ case V.ANTIKING:
+ return this.getPotentialAntikingMoves([x,y]);
+ default:
+ return super.getPotentialMovesFrom([x,y]);
+ }
+ }
+
+ getPotentialAntikingMoves(sq)
+ {
+ return this.getSlideNJumpMoves(sq,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+ }
+
+ isAttacked(sq, colors)
+ {
+ return (super.isAttacked(sq, colors) || this.isAttackedByAntiking(sq, colors));
+ }
+
+ isAttackedByKing([x,y], colors)
+ {
+ if (this.getPiece(x,y) == V.ANTIKING)
+ return false; //antiking is not attacked by king
+ return this.isAttackedBySlideNJump([x,y], colors, V.KING,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+ }
+
+ isAttackedByAntiking([x,y], colors)
+ {
+ if ([V.KING,V.ANTIKING].includes(this.getPiece(x,y)))
+ return false; //(anti)king is not attacked by antiking
+ return this.isAttackedBySlideNJump([x,y], colors, V.ANTIKING,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+ }
+
+ underCheck(color)
+ {
+ const oppCol = V.GetOppCol(color);
+ let res = this.isAttacked(this.kingPos[color], [oppCol])
+ || !this.isAttacked(this.antikingPos[color], [oppCol]);
+ return res;
+ }
+
+ getCheckSquares(color)
+ {
+ let res = super.getCheckSquares(color);
+ if (!this.isAttacked(this.antikingPos[color], [V.GetOppCol(color)]))
+ res.push(JSON.parse(JSON.stringify(this.antikingPos[color])));
+ return res;
+ }
+
+ updateVariables(move)
+ {
+ super.updateVariables(move);
+ const piece = move.vanish[0].p;
+ const c = move.vanish[0].c;
+ // Update antiking position
+ if (piece == V.ANTIKING)
+ {
+ this.antikingPos[c][0] = move.appear[0].x;
+ this.antikingPos[c][1] = move.appear[0].y;
+ }
+ }
+
+ unupdateVariables(move)
+ {
+ super.unupdateVariables(move);
+ const c = move.vanish[0].c;
+ if (move.vanish[0].p == V.ANTIKING)
+ this.antikingPos[c] = [move.start.x, move.start.y];
+ }
+
+ getCurrentScore()
+ {
if (this.atLeastOneMove()) // game not over
return "*";
const color = this.turn;
- const oppCol = V.GetOppCol(color);
- if (!this.isAttacked(this.kingPos[color], [oppCol])
- && this.isAttacked(this.antikingPos[color], [oppCol]))
- {
- return "1/2";
- }
- return color == "w" ? "0-1" : "1-0";
- }
-
- static get VALUES() {
- return Object.assign(
- ChessRules.VALUES,
- { 'a': 1000 }
- );
- }
-
- static GenRandInitFen()
- {
- let pieces = { "w": new Array(8), "b": new Array(8) };
- let antikingPos = { "w": -1, "b": -1 };
- for (let c of ["w","b"])
- {
- let positions = ArrayFun.range(8);
-
- // Get random squares for bishops, but avoid corners; because,
- // if an antiking blocks a cornered bishop, it can never be checkmated
- let randIndex = 2 * randInt(1,4);
- const bishop1Pos = positions[randIndex];
- let randIndex_tmp = 2 * randInt(3) + 1;
- const bishop2Pos = positions[randIndex_tmp];
- positions.splice(Math.max(randIndex,randIndex_tmp), 1);
- positions.splice(Math.min(randIndex,randIndex_tmp), 1);
-
- randIndex = randInt(6);
- const knight1Pos = positions[randIndex];
- positions.splice(randIndex, 1);
- randIndex = randInt(5);
- const knight2Pos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- randIndex = randInt(4);
- const queenPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- const rook1Pos = positions[0];
- const kingPos = positions[1];
- const rook2Pos = positions[2];
-
- // Random squares for antikings
- antikingPos[c] = randInt(8);
-
- pieces[c][rook1Pos] = 'r';
- pieces[c][knight1Pos] = 'n';
- pieces[c][bishop1Pos] = 'b';
- pieces[c][queenPos] = 'q';
- pieces[c][kingPos] = 'k';
- pieces[c][bishop2Pos] = 'b';
- pieces[c][knight2Pos] = 'n';
- pieces[c][rook2Pos] = 'r';
- }
- const ranks23_black = "pppppppp/" + (antikingPos["w"]>0?antikingPos["w"]:"")
- + "A" + (antikingPos["w"]<7?7-antikingPos["w"]:"");
- const ranks23_white = (antikingPos["b"]>0?antikingPos["b"]:"") + "a"
- + (antikingPos["b"]<7?7-antikingPos["b"]:"") + "/PPPPPPPP";
- return pieces["b"].join("") + "/" + ranks23_black +
- "/8/8/" +
- ranks23_white + "/" + pieces["w"].join("").toUpperCase() +
- " w 0 1111 -";
- }
+ const oppCol = V.GetOppCol(color);
+ if (!this.isAttacked(this.kingPos[color], [oppCol])
+ && this.isAttacked(this.antikingPos[color], [oppCol]))
+ {
+ return "1/2";
+ }
+ return color == "w" ? "0-1" : "1-0";
+ }
+
+ static get VALUES() {
+ return Object.assign(
+ ChessRules.VALUES,
+ { 'a': 1000 }
+ );
+ }
+
+ static GenRandInitFen()
+ {
+ let pieces = { "w": new Array(8), "b": new Array(8) };
+ let antikingPos = { "w": -1, "b": -1 };
+ for (let c of ["w","b"])
+ {
+ let positions = ArrayFun.range(8);
+
+ // Get random squares for bishops, but avoid corners; because,
+ // if an antiking blocks a cornered bishop, it can never be checkmated
+ let randIndex = 2 * randInt(1,4);
+ const bishop1Pos = positions[randIndex];
+ let randIndex_tmp = 2 * randInt(3) + 1;
+ const bishop2Pos = positions[randIndex_tmp];
+ positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+ positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+ randIndex = randInt(6);
+ const knight1Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+ randIndex = randInt(5);
+ const knight2Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ randIndex = randInt(4);
+ const queenPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ const rook1Pos = positions[0];
+ const kingPos = positions[1];
+ const rook2Pos = positions[2];
+
+ // Random squares for antikings
+ antikingPos[c] = randInt(8);
+
+ pieces[c][rook1Pos] = 'r';
+ pieces[c][knight1Pos] = 'n';
+ pieces[c][bishop1Pos] = 'b';
+ pieces[c][queenPos] = 'q';
+ pieces[c][kingPos] = 'k';
+ pieces[c][bishop2Pos] = 'b';
+ pieces[c][knight2Pos] = 'n';
+ pieces[c][rook2Pos] = 'r';
+ }
+ const ranks23_black = "pppppppp/" + (antikingPos["w"]>0?antikingPos["w"]:"")
+ + "A" + (antikingPos["w"]<7?7-antikingPos["w"]:"");
+ const ranks23_white = (antikingPos["b"]>0?antikingPos["b"]:"") + "a"
+ + (antikingPos["b"]<7?7-antikingPos["b"]:"") + "/PPPPPPPP";
+ return pieces["b"].join("") + "/" + ranks23_black +
+ "/8/8/" +
+ ranks23_white + "/" + pieces["w"].join("").toUpperCase() +
+ " w 0 1111 -";
+ }
}
export const VariantRules = class AtomicRules extends ChessRules
{
- getPotentialMovesFrom([x,y])
- {
- let moves = super.getPotentialMovesFrom([x,y]);
+ getPotentialMovesFrom([x,y])
+ {
+ let moves = super.getPotentialMovesFrom([x,y]);
- // Handle explosions
- moves.forEach(m => {
- if (m.vanish.length > 1 && m.appear.length <= 1) //avoid castles
- {
- // Explosion! TODO(?): drop moves which explode our king here
- let steps = [ [-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1] ];
- for (let step of steps)
- {
- let x = m.end.x + step[0];
- let y = m.end.y + step[1];
- if (V.OnBoard(x,y) && this.board[x][y] != V.EMPTY
- && this.getPiece(x,y) != V.PAWN)
- {
- m.vanish.push(
- new PiPo({p:this.getPiece(x,y),c:this.getColor(x,y),x:x,y:y}));
- }
- }
- m.end = {x:m.appear[0].x, y:m.appear[0].y};
- m.appear.pop(); //Nothin appears in this case
- }
- });
+ // Handle explosions
+ moves.forEach(m => {
+ if (m.vanish.length > 1 && m.appear.length <= 1) //avoid castles
+ {
+ // Explosion! TODO(?): drop moves which explode our king here
+ let steps = [ [-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1] ];
+ for (let step of steps)
+ {
+ let x = m.end.x + step[0];
+ let y = m.end.y + step[1];
+ if (V.OnBoard(x,y) && this.board[x][y] != V.EMPTY
+ && this.getPiece(x,y) != V.PAWN)
+ {
+ m.vanish.push(
+ new PiPo({p:this.getPiece(x,y),c:this.getColor(x,y),x:x,y:y}));
+ }
+ }
+ m.end = {x:m.appear[0].x, y:m.appear[0].y};
+ m.appear.pop(); //Nothin appears in this case
+ }
+ });
- return moves;
- }
+ return moves;
+ }
- getPotentialKingMoves([x,y])
- {
- // King cannot capture:
- let moves = [];
- const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
- for (let step of steps)
- {
- const i = x + step[0];
- const j = y + step[1];
- if (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
- moves.push(this.getBasicMove([x,y], [i,j]));
- }
- return moves.concat(this.getCastleMoves([x,y]));
- }
+ getPotentialKingMoves([x,y])
+ {
+ // King cannot capture:
+ let moves = [];
+ const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ for (let step of steps)
+ {
+ const i = x + step[0];
+ const j = y + step[1];
+ if (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+ moves.push(this.getBasicMove([x,y], [i,j]));
+ }
+ return moves.concat(this.getCastleMoves([x,y]));
+ }
- isAttacked(sq, colors)
- {
- if (this.getPiece(sq[0],sq[1]) == V.KING && this.isAttackedByKing(sq, colors))
- return false; //king cannot take...
- return (this.isAttackedByPawn(sq, colors)
- || this.isAttackedByRook(sq, colors)
- || this.isAttackedByKnight(sq, colors)
- || this.isAttackedByBishop(sq, colors)
- || this.isAttackedByQueen(sq, colors));
- }
+ isAttacked(sq, colors)
+ {
+ if (this.getPiece(sq[0],sq[1]) == V.KING && this.isAttackedByKing(sq, colors))
+ return false; //king cannot take...
+ return (this.isAttackedByPawn(sq, colors)
+ || this.isAttackedByRook(sq, colors)
+ || this.isAttackedByKnight(sq, colors)
+ || this.isAttackedByBishop(sq, colors)
+ || this.isAttackedByQueen(sq, colors));
+ }
- updateVariables(move)
- {
- super.updateVariables(move);
- const color = move.vanish[0].c;
- if (move.appear.length == 0) //capture
- {
- const firstRank = {"w": 7, "b": 0};
- for (let c of ["w","b"])
- {
- // Did we explode king of color c ? (TODO: remove move earlier)
- if (Math.abs(this.kingPos[c][0]-move.end.x) <= 1
- && Math.abs(this.kingPos[c][1]-move.end.y) <= 1)
- {
- this.kingPos[c] = [-1,-1];
- this.castleFlags[c] = [false,false];
- }
- else
- {
- // Now check if init rook(s) exploded
- if (Math.abs(move.end.x-firstRank[c]) <= 1)
- {
- if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][0]) <= 1)
- this.castleFlags[c][0] = false;
- if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][1]) <= 1)
- this.castleFlags[c][1] = false;
- }
- }
- }
- }
- }
+ updateVariables(move)
+ {
+ super.updateVariables(move);
+ const color = move.vanish[0].c;
+ if (move.appear.length == 0) //capture
+ {
+ const firstRank = {"w": 7, "b": 0};
+ for (let c of ["w","b"])
+ {
+ // Did we explode king of color c ? (TODO: remove move earlier)
+ if (Math.abs(this.kingPos[c][0]-move.end.x) <= 1
+ && Math.abs(this.kingPos[c][1]-move.end.y) <= 1)
+ {
+ this.kingPos[c] = [-1,-1];
+ this.castleFlags[c] = [false,false];
+ }
+ else
+ {
+ // Now check if init rook(s) exploded
+ if (Math.abs(move.end.x-firstRank[c]) <= 1)
+ {
+ if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][0]) <= 1)
+ this.castleFlags[c][0] = false;
+ if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][1]) <= 1)
+ this.castleFlags[c][1] = false;
+ }
+ }
+ }
+ }
+ }
- unupdateVariables(move)
- {
- super.unupdateVariables(move);
- const c = move.vanish[0].c;
- const oppCol = V.GetOppCol(c);
- if ([this.kingPos[c][0],this.kingPos[oppCol][0]].some(e => { return e < 0; }))
- {
- // There is a chance that last move blowed some king away..
- for (let psq of move.vanish)
- {
- if (psq.p == 'k')
- this.kingPos[psq.c==c ? c : oppCol] = [psq.x, psq.y];
- }
- }
- }
+ unupdateVariables(move)
+ {
+ super.unupdateVariables(move);
+ const c = move.vanish[0].c;
+ const oppCol = V.GetOppCol(c);
+ if ([this.kingPos[c][0],this.kingPos[oppCol][0]].some(e => { return e < 0; }))
+ {
+ // There is a chance that last move blowed some king away..
+ for (let psq of move.vanish)
+ {
+ if (psq.p == 'k')
+ this.kingPos[psq.c==c ? c : oppCol] = [psq.x, psq.y];
+ }
+ }
+ }
- underCheck(color)
- {
- const oppCol = V.GetOppCol(color);
- let res = undefined;
- // If our king disappeared, move is not valid
- if (this.kingPos[color][0] < 0)
- res = true;
- // If opponent king disappeared, move is valid
- else if (this.kingPos[oppCol][0] < 0)
- res = false;
- // Otherwise, if we remain under check, move is not valid
- else
- res = this.isAttacked(this.kingPos[color], [oppCol]);
- return res;
- }
+ underCheck(color)
+ {
+ const oppCol = V.GetOppCol(color);
+ let res = undefined;
+ // If our king disappeared, move is not valid
+ if (this.kingPos[color][0] < 0)
+ res = true;
+ // If opponent king disappeared, move is valid
+ else if (this.kingPos[oppCol][0] < 0)
+ res = false;
+ // Otherwise, if we remain under check, move is not valid
+ else
+ res = this.isAttacked(this.kingPos[color], [oppCol]);
+ return res;
+ }
- getCheckSquares(color)
- {
- let res = [ ];
- if (this.kingPos[color][0] >= 0 //king might have exploded
- && this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
- {
- res = [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
- }
- return res;
- }
+ getCheckSquares(color)
+ {
+ let res = [ ];
+ if (this.kingPos[color][0] >= 0 //king might have exploded
+ && this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
+ {
+ res = [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
+ }
+ return res;
+ }
- getCurrentScore()
- {
- const color = this.turn;
- const kp = this.kingPos[color];
- if (kp[0] < 0) //king disappeared
- return color == "w" ? "0-1" : "1-0";
+ getCurrentScore()
+ {
+ const color = this.turn;
+ const kp = this.kingPos[color];
+ if (kp[0] < 0) //king disappeared
+ return color == "w" ? "0-1" : "1-0";
if (this.atLeastOneMove()) // game not over
return "*";
- if (!this.isAttacked(kp, [V.GetOppCol(color)]))
- return "1/2";
- return color == "w" ? "0-1" : "1-0"; //checkmate
- }
+ if (!this.isAttacked(kp, [V.GetOppCol(color)]))
+ return "1/2";
+ return color == "w" ? "0-1" : "1-0"; //checkmate
+ }
}
export const VariantRules = class BaroqueRules extends ChessRules
{
- static get HasFlags() { return false; }
-
- static get HasEnpassant() { return false; }
-
- static getPpath(b)
- {
- if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1)
- return "Baroque/" + b;
- return b; //usual piece
- }
-
- static get PIECES()
- {
- return ChessRules.PIECES.concat([V.IMMOBILIZER]);
- }
-
- // No castling, but checks, so keep track of kings
- setOtherVariables(fen)
- {
- this.kingPos = {'w':[-1,-1], 'b':[-1,-1]};
- const fenParts = fen.split(" ");
- const position = fenParts[0].split("/");
- for (let i=0; i<position.length; i++)
- {
- let k = 0;
- for (let j=0; j<position[i].length; j++)
- {
- switch (position[i].charAt(j))
- {
- case 'k':
- this.kingPos['b'] = [i,k];
- break;
- case 'K':
- this.kingPos['w'] = [i,k];
- break;
- default:
- let num = parseInt(position[i].charAt(j));
- if (!isNaN(num))
- k += (num-1);
- }
- k++;
- }
- }
- }
-
- static get IMMOBILIZER() { return 'm'; }
- // Although other pieces keep their names here for coding simplicity,
- // keep in mind that:
- // - a "rook" is a coordinator, capturing by coordinating with the king
- // - a "knight" is a long-leaper, capturing as in draughts
- // - a "bishop" is a chameleon, capturing as its prey
- // - a "queen" is a withdrawer, capturing by moving away from pieces
-
- // Is piece on square (x,y) immobilized?
- isImmobilized([x,y])
- {
- const piece = this.getPiece(x,y);
- const color = this.getColor(x,y);
- const oppCol = V.GetOppCol(color);
- const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
- outerLoop:
- for (let step of adjacentSteps)
- {
- const [i,j] = [x+step[0],y+step[1]];
- if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY
- && this.getColor(i,j) == oppCol)
- {
- const oppPiece = this.getPiece(i,j);
- if (oppPiece == V.IMMOBILIZER)
- {
- // Moving is impossible only if this immobilizer is not neutralized
- for (let step2 of adjacentSteps)
- {
- const [i2,j2] = [i+step2[0],j+step2[1]];
- if (i2 == x && j2 == y)
- continue; //skip initial piece!
- if (V.OnBoard(i2,j2) && this.board[i2][j2] != V.EMPTY
- && this.getColor(i2,j2) == color)
- {
- if ([V.BISHOP,V.IMMOBILIZER].includes(this.getPiece(i2,j2)))
- return false;
- }
- }
- return true; //immobilizer isn't neutralized
- }
- // Chameleons can't be immobilized twice, because there is only one immobilizer
- if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER)
- return true;
- }
- }
- return false;
- }
-
- getPotentialMovesFrom([x,y])
- {
- // Pre-check: is thing on this square immobilized?
- if (this.isImmobilized([x,y]))
- return [];
- switch (this.getPiece(x,y))
- {
- case V.IMMOBILIZER:
- return this.getPotentialImmobilizerMoves([x,y]);
- default:
- return super.getPotentialMovesFrom([x,y]);
- }
- }
-
- getSlideNJumpMoves([x,y], steps, oneStep)
- {
- const color = this.getColor(x,y);
- const piece = this.getPiece(x,y);
- let moves = [];
- outerLoop:
- for (let step of steps)
- {
- let i = x + step[0];
- let j = y + step[1];
- while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
- {
- moves.push(this.getBasicMove([x,y], [i,j]));
- if (oneStep !== undefined)
- continue outerLoop;
- i += step[0];
- j += step[1];
- }
- // Only king can take on occupied square:
- if (piece==V.KING && V.OnBoard(i,j) && this.canTake([x,y], [i,j]))
- moves.push(this.getBasicMove([x,y], [i,j]));
- }
- return moves;
- }
-
- // Modify capturing moves among listed pawn moves
- addPawnCaptures(moves, byChameleon)
- {
- const steps = V.steps[V.ROOK];
- const color = this.turn;
- const oppCol = V.GetOppCol(color);
- moves.forEach(m => {
- if (!!byChameleon && m.start.x!=m.end.x && m.start.y!=m.end.y)
- return; //chameleon not moving as pawn
- // Try capturing in every direction
- for (let step of steps)
- {
- const sq2 = [m.end.x+2*step[0],m.end.y+2*step[1]];
- if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] != V.EMPTY
- && this.getColor(sq2[0],sq2[1]) == color)
- {
- // Potential capture
- const sq1 = [m.end.x+step[0],m.end.y+step[1]];
- if (this.board[sq1[0]][sq1[1]] != V.EMPTY
- && this.getColor(sq1[0],sq1[1]) == oppCol)
- {
- const piece1 = this.getPiece(sq1[0],sq1[1]);
- if (!byChameleon || piece1 == V.PAWN)
- {
- m.vanish.push(new PiPo({
- x:sq1[0],
- y:sq1[1],
- c:oppCol,
- p:piece1
- }));
- }
- }
- }
- }
- });
- }
-
- // "Pincer"
- getPotentialPawnMoves([x,y])
- {
- let moves = super.getPotentialRookMoves([x,y]);
- this.addPawnCaptures(moves);
- return moves;
- }
-
- addRookCaptures(moves, byChameleon)
- {
- const color = this.turn;
- const oppCol = V.GetOppCol(color);
- const kp = this.kingPos[color];
- moves.forEach(m => {
- // Check piece-king rectangle (if any) corners for enemy pieces
- if (m.end.x == kp[0] || m.end.y == kp[1])
- return; //"flat rectangle"
- const corner1 = [m.end.x, kp[1]];
- const corner2 = [kp[0], m.end.y];
- for (let [i,j] of [corner1,corner2])
- {
- if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol)
- {
- const piece = this.getPiece(i,j);
- if (!byChameleon || piece == V.ROOK)
- {
- m.vanish.push( new PiPo({
- x:i,
- y:j,
- p:piece,
- c:oppCol
- }) );
- }
- }
- }
- });
- }
-
- // Coordinator
- getPotentialRookMoves(sq)
- {
- let moves = super.getPotentialQueenMoves(sq);
- this.addRookCaptures(moves);
- return moves;
- }
-
- // Long-leaper
- getKnightCaptures(startSquare, byChameleon)
- {
- // Look in every direction for captures
- const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
- const color = this.turn;
- const oppCol = V.GetOppCol(color);
- let moves = [];
- const [x,y] = [startSquare[0],startSquare[1]];
- const piece = this.getPiece(x,y); //might be a chameleon!
- outerLoop:
- for (let step of steps)
- {
- let [i,j] = [x+step[0], y+step[1]];
- while (V.OnBoard(i,j) && this.board[i][j]==V.EMPTY)
- {
- i += step[0];
- j += step[1];
- }
- if (!V.OnBoard(i,j) || this.getColor(i,j)==color
- || (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT))
- {
- continue;
- }
- // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
- // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
- // add move until cur square; if cur is occupied then stop if !!byChameleon and
- // the square not occupied by a leaper.
- let last = [i,j];
- let cur = [i+step[0],j+step[1]];
- let vanished = [ new PiPo({x:x,y:y,c:color,p:piece}) ];
- while (V.OnBoard(cur[0],cur[1]))
- {
- if (this.board[last[0]][last[1]] != V.EMPTY)
- {
- const oppPiece = this.getPiece(last[0],last[1]);
- if (!!byChameleon && oppPiece != V.KNIGHT)
- continue outerLoop;
- // Something to eat:
- vanished.push( new PiPo({x:last[0],y:last[1],c:oppCol,p:oppPiece}) );
- }
- if (this.board[cur[0]][cur[1]] != V.EMPTY)
- {
- if (this.getColor(cur[0],cur[1]) == color
- || this.board[last[0]][last[1]] != V.EMPTY) //TODO: redundant test
- {
- continue outerLoop;
- }
- }
- else
- {
- moves.push(new Move({
- appear: [ new PiPo({x:cur[0],y:cur[1],c:color,p:piece}) ],
- vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
- start: {x:x,y:y},
- end: {x:cur[0],y:cur[1]}
- }));
- }
- last = [last[0]+step[0],last[1]+step[1]];
- cur = [cur[0]+step[0],cur[1]+step[1]];
- }
- }
- return moves;
- }
-
- // Long-leaper
- getPotentialKnightMoves(sq)
- {
- return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
- }
-
- getPotentialBishopMoves([x,y])
- {
- let moves = super.getPotentialQueenMoves([x,y])
- .concat(this.getKnightCaptures([x,y],"asChameleon"));
- // No "king capture" because king cannot remain under check
- this.addPawnCaptures(moves, "asChameleon");
- this.addRookCaptures(moves, "asChameleon");
- this.addQueenCaptures(moves, "asChameleon");
- // Post-processing: merge similar moves, concatenating vanish arrays
- let mergedMoves = {};
- moves.forEach(m => {
- const key = m.end.x + V.size.x * m.end.y;
- if (!mergedMoves[key])
- mergedMoves[key] = m;
- else
- {
- for (let i=1; i<m.vanish.length; i++)
- mergedMoves[key].vanish.push(m.vanish[i]);
- }
- });
- // Finally return an array
- moves = [];
- Object.keys(mergedMoves).forEach(k => { moves.push(mergedMoves[k]); });
- return moves;
- }
-
- // Withdrawer
- addQueenCaptures(moves, byChameleon)
- {
- if (moves.length == 0)
- return;
- const [x,y] = [moves[0].start.x,moves[0].start.y];
- const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
- let capturingDirections = [];
- const color = this.turn;
- const oppCol = V.GetOppCol(color);
- adjacentSteps.forEach(step => {
- const [i,j] = [x+step[0],y+step[1]];
- if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol
- && (!byChameleon || this.getPiece(i,j) == V.QUEEN))
- {
- capturingDirections.push(step);
- }
- });
- moves.forEach(m => {
- const step = [
- m.end.x!=x ? (m.end.x-x)/Math.abs(m.end.x-x) : 0,
- m.end.y!=y ? (m.end.y-y)/Math.abs(m.end.y-y) : 0
- ];
- // NOTE: includes() and even _.isEqual() functions fail...
- // TODO: this test should be done only once per direction
- if (capturingDirections.some(dir =>
- { return (dir[0]==-step[0] && dir[1]==-step[1]); }))
- {
- const [i,j] = [x-step[0],y-step[1]];
- m.vanish.push(new PiPo({
- x:i,
- y:j,
- p:this.getPiece(i,j),
- c:oppCol
- }));
- }
- });
- }
-
- getPotentialQueenMoves(sq)
- {
- let moves = super.getPotentialQueenMoves(sq);
- this.addQueenCaptures(moves);
- return moves;
- }
-
- getPotentialImmobilizerMoves(sq)
- {
- // Immobilizer doesn't capture
- return super.getPotentialQueenMoves(sq);
- }
-
- getPotentialKingMoves(sq)
- {
- return this.getSlideNJumpMoves(sq,
- V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
- }
-
- // isAttacked() is OK because the immobilizer doesn't take
-
- isAttackedByPawn([x,y], colors)
- {
- // Square (x,y) must be surroundable by two enemy pieces,
- // and one of them at least should be a pawn (moving).
- const dirs = [ [1,0],[0,1] ];
- const steps = V.steps[V.ROOK];
- for (let dir of dirs)
- {
- const [i1,j1] = [x-dir[0],y-dir[1]]; //"before"
- const [i2,j2] = [x+dir[0],y+dir[1]]; //"after"
- if (V.OnBoard(i1,j1) && V.OnBoard(i2,j2))
- {
- if ((this.board[i1][j1]!=V.EMPTY && colors.includes(this.getColor(i1,j1))
- && this.board[i2][j2]==V.EMPTY)
- ||
- (this.board[i2][j2]!=V.EMPTY && colors.includes(this.getColor(i2,j2))
- && this.board[i1][j1]==V.EMPTY))
- {
- // Search a movable enemy pawn landing on the empty square
- for (let step of steps)
- {
- let [ii,jj] = (this.board[i1][j1]==V.EMPTY ? [i1,j1] : [i2,j2]);
- let [i3,j3] = [ii+step[0],jj+step[1]];
- while (V.OnBoard(i3,j3) && this.board[i3][j3]==V.EMPTY)
- {
- i3 += step[0];
- j3 += step[1];
- }
- if (V.OnBoard(i3,j3) && colors.includes(this.getColor(i3,j3))
- && this.getPiece(i3,j3) == V.PAWN && !this.isImmobilized([i3,j3]))
- {
- return true;
- }
- }
- }
- }
- }
- return false;
- }
-
- isAttackedByRook([x,y], colors)
- {
- // King must be on same column or row,
- // and a rook should be able to reach a capturing square
- // colors contains only one element, giving the oppCol and thus king position
- const sameRow = (x == this.kingPos[colors[0]][0]);
- const sameColumn = (y == this.kingPos[colors[0]][1]);
- if (sameRow || sameColumn)
- {
- // Look for the enemy rook (maximum 1)
- for (let i=0; i<V.size.x; i++)
- {
- for (let j=0; j<V.size.y; j++)
- {
- if (this.board[i][j] != V.EMPTY && colors.includes(this.getColor(i,j))
- && this.getPiece(i,j) == V.ROOK)
- {
- if (this.isImmobilized([i,j]))
- return false; //because only one rook
- // Can it reach a capturing square?
- // Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
- const moves = this.getPotentialMovesFrom([i,j]);
- for (let move of moves)
- {
- if (sameRow && move.end.y == y || sameColumn && move.end.x == x)
- return true;
- }
- }
- }
- }
- }
- return false;
- }
-
- isAttackedByKnight([x,y], colors)
- {
- // Square (x,y) must be on same line as a knight,
- // and there must be empty square(s) behind.
- const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
- outerLoop:
- for (let step of steps)
- {
- const [i0,j0] = [x+step[0],y+step[1]];
- if (V.OnBoard(i0,j0) && this.board[i0][j0] == V.EMPTY)
- {
- // Try in opposite direction:
- let [i,j] = [x-step[0],y-step[1]];
- while (V.OnBoard(i,j))
- {
- while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
- {
- i -= step[0];
- j -= step[1];
- }
- if (V.OnBoard(i,j))
- {
- if (colors.includes(this.getColor(i,j)))
- {
- if (this.getPiece(i,j) == V.KNIGHT && !this.isImmobilized([i,j]))
- return true;
- continue outerLoop;
- }
- // [else] Our color, could be captured *if there was an empty space*
- if (this.board[i+step[0]][j+step[1]] != V.EMPTY)
- continue outerLoop;
- i -= step[0];
- j -= step[1];
- }
- }
- }
- }
- return false;
- }
-
- isAttackedByBishop([x,y], colors)
- {
- // We cheat a little here: since this function is used exclusively for king,
- // it's enough to check the immediate surrounding of the square.
- const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
- for (let step of adjacentSteps)
- {
- const [i,j] = [x+step[0],y+step[1]];
- if (V.OnBoard(i,j) && this.board[i][j]!=V.EMPTY
- && colors.includes(this.getColor(i,j)) && this.getPiece(i,j) == V.BISHOP)
- {
- return true; //bishops are never immobilized
- }
- }
- return false;
- }
-
- isAttackedByQueen([x,y], colors)
- {
- // Square (x,y) must be adjacent to a queen, and the queen must have
- // some free space in the opposite direction from (x,y)
- const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
- for (let step of adjacentSteps)
- {
- const sq2 = [x+2*step[0],y+2*step[1]];
- if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY)
- {
- const sq1 = [x+step[0],y+step[1]];
- if (this.board[sq1[0]][sq1[1]] != V.EMPTY
- && colors.includes(this.getColor(sq1[0],sq1[1]))
- && this.getPiece(sq1[0],sq1[1]) == V.QUEEN
- && !this.isImmobilized(sq1))
- {
- return true;
- }
- }
- }
- return false;
- }
-
- static get VALUES()
- {
- // TODO: totally experimental!
- return {
- 'p': 1,
- 'r': 2,
- 'n': 5,
- 'b': 3,
- 'q': 3,
- 'm': 5,
- 'k': 1000
- };
- }
-
- static get SEARCH_DEPTH() { return 2; } //TODO?
-
- static GenRandInitFen()
- {
- let pieces = { "w": new Array(8), "b": new Array(8) };
- // Shuffle pieces on first and last rank
- for (let c of ["w","b"])
- {
- let positions = ArrayFun.range(8);
- // Get random squares for every piece, totally freely
-
- let randIndex = randInt(8);
- const bishop1Pos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- randIndex = randInt(7);
- const bishop2Pos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- randIndex = randInt(6);
- const knight1Pos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- randIndex = randInt(5);
- const knight2Pos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- randIndex = randInt(4);
- const queenPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- randIndex = randInt(3);
- const kingPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- randIndex = randInt(2);
- const rookPos = positions[randIndex];
- positions.splice(randIndex, 1);
- const immobilizerPos = positions[0];
-
- pieces[c][bishop1Pos] = 'b';
- pieces[c][bishop2Pos] = 'b';
- pieces[c][knight1Pos] = 'n';
- pieces[c][knight2Pos] = 'n';
- pieces[c][queenPos] = 'q';
- pieces[c][kingPos] = 'k';
- pieces[c][rookPos] = 'r';
- pieces[c][immobilizerPos] = 'm';
- }
- return pieces["b"].join("") +
- "/pppppppp/8/8/8/8/PPPPPPPP/" +
- pieces["w"].join("").toUpperCase() +
- " w 0";
- }
-
- getNotation(move)
- {
- const initialSquare = V.CoordsToSquare(move.start);
- const finalSquare = V.CoordsToSquare(move.end);
- let notation = undefined;
- if (move.appear[0].p == V.PAWN)
- {
- // Pawn: generally ambiguous short notation, so we use full description
- notation = "P" + initialSquare + finalSquare;
- }
- else if (move.appear[0].p == V.KING)
- notation = "K" + (move.vanish.length>1 ? "x" : "") + finalSquare;
- else
- notation = move.appear[0].p.toUpperCase() + finalSquare;
- if (move.vanish.length > 1 && move.appear[0].p != V.KING)
- notation += "X"; //capture mark (not describing what is captured...)
- return notation;
- }
+ static get HasFlags() { return false; }
+
+ static get HasEnpassant() { return false; }
+
+ static getPpath(b)
+ {
+ if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1)
+ return "Baroque/" + b;
+ return b; //usual piece
+ }
+
+ static get PIECES()
+ {
+ return ChessRules.PIECES.concat([V.IMMOBILIZER]);
+ }
+
+ // No castling, but checks, so keep track of kings
+ setOtherVariables(fen)
+ {
+ this.kingPos = {'w':[-1,-1], 'b':[-1,-1]};
+ const fenParts = fen.split(" ");
+ const position = fenParts[0].split("/");
+ for (let i=0; i<position.length; i++)
+ {
+ let k = 0;
+ for (let j=0; j<position[i].length; j++)
+ {
+ switch (position[i].charAt(j))
+ {
+ case 'k':
+ this.kingPos['b'] = [i,k];
+ break;
+ case 'K':
+ this.kingPos['w'] = [i,k];
+ break;
+ default:
+ let num = parseInt(position[i].charAt(j));
+ if (!isNaN(num))
+ k += (num-1);
+ }
+ k++;
+ }
+ }
+ }
+
+ static get IMMOBILIZER() { return 'm'; }
+ // Although other pieces keep their names here for coding simplicity,
+ // keep in mind that:
+ // - a "rook" is a coordinator, capturing by coordinating with the king
+ // - a "knight" is a long-leaper, capturing as in draughts
+ // - a "bishop" is a chameleon, capturing as its prey
+ // - a "queen" is a withdrawer, capturing by moving away from pieces
+
+ // Is piece on square (x,y) immobilized?
+ isImmobilized([x,y])
+ {
+ const piece = this.getPiece(x,y);
+ const color = this.getColor(x,y);
+ const oppCol = V.GetOppCol(color);
+ const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ outerLoop:
+ for (let step of adjacentSteps)
+ {
+ const [i,j] = [x+step[0],y+step[1]];
+ if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY
+ && this.getColor(i,j) == oppCol)
+ {
+ const oppPiece = this.getPiece(i,j);
+ if (oppPiece == V.IMMOBILIZER)
+ {
+ // Moving is impossible only if this immobilizer is not neutralized
+ for (let step2 of adjacentSteps)
+ {
+ const [i2,j2] = [i+step2[0],j+step2[1]];
+ if (i2 == x && j2 == y)
+ continue; //skip initial piece!
+ if (V.OnBoard(i2,j2) && this.board[i2][j2] != V.EMPTY
+ && this.getColor(i2,j2) == color)
+ {
+ if ([V.BISHOP,V.IMMOBILIZER].includes(this.getPiece(i2,j2)))
+ return false;
+ }
+ }
+ return true; //immobilizer isn't neutralized
+ }
+ // Chameleons can't be immobilized twice, because there is only one immobilizer
+ if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ getPotentialMovesFrom([x,y])
+ {
+ // Pre-check: is thing on this square immobilized?
+ if (this.isImmobilized([x,y]))
+ return [];
+ switch (this.getPiece(x,y))
+ {
+ case V.IMMOBILIZER:
+ return this.getPotentialImmobilizerMoves([x,y]);
+ default:
+ return super.getPotentialMovesFrom([x,y]);
+ }
+ }
+
+ getSlideNJumpMoves([x,y], steps, oneStep)
+ {
+ const color = this.getColor(x,y);
+ const piece = this.getPiece(x,y);
+ let moves = [];
+ outerLoop:
+ for (let step of steps)
+ {
+ let i = x + step[0];
+ let j = y + step[1];
+ while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+ {
+ moves.push(this.getBasicMove([x,y], [i,j]));
+ if (oneStep !== undefined)
+ continue outerLoop;
+ i += step[0];
+ j += step[1];
+ }
+ // Only king can take on occupied square:
+ if (piece==V.KING && V.OnBoard(i,j) && this.canTake([x,y], [i,j]))
+ moves.push(this.getBasicMove([x,y], [i,j]));
+ }
+ return moves;
+ }
+
+ // Modify capturing moves among listed pawn moves
+ addPawnCaptures(moves, byChameleon)
+ {
+ const steps = V.steps[V.ROOK];
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ moves.forEach(m => {
+ if (!!byChameleon && m.start.x!=m.end.x && m.start.y!=m.end.y)
+ return; //chameleon not moving as pawn
+ // Try capturing in every direction
+ for (let step of steps)
+ {
+ const sq2 = [m.end.x+2*step[0],m.end.y+2*step[1]];
+ if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] != V.EMPTY
+ && this.getColor(sq2[0],sq2[1]) == color)
+ {
+ // Potential capture
+ const sq1 = [m.end.x+step[0],m.end.y+step[1]];
+ if (this.board[sq1[0]][sq1[1]] != V.EMPTY
+ && this.getColor(sq1[0],sq1[1]) == oppCol)
+ {
+ const piece1 = this.getPiece(sq1[0],sq1[1]);
+ if (!byChameleon || piece1 == V.PAWN)
+ {
+ m.vanish.push(new PiPo({
+ x:sq1[0],
+ y:sq1[1],
+ c:oppCol,
+ p:piece1
+ }));
+ }
+ }
+ }
+ }
+ });
+ }
+
+ // "Pincer"
+ getPotentialPawnMoves([x,y])
+ {
+ let moves = super.getPotentialRookMoves([x,y]);
+ this.addPawnCaptures(moves);
+ return moves;
+ }
+
+ addRookCaptures(moves, byChameleon)
+ {
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ const kp = this.kingPos[color];
+ moves.forEach(m => {
+ // Check piece-king rectangle (if any) corners for enemy pieces
+ if (m.end.x == kp[0] || m.end.y == kp[1])
+ return; //"flat rectangle"
+ const corner1 = [m.end.x, kp[1]];
+ const corner2 = [kp[0], m.end.y];
+ for (let [i,j] of [corner1,corner2])
+ {
+ if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol)
+ {
+ const piece = this.getPiece(i,j);
+ if (!byChameleon || piece == V.ROOK)
+ {
+ m.vanish.push( new PiPo({
+ x:i,
+ y:j,
+ p:piece,
+ c:oppCol
+ }) );
+ }
+ }
+ }
+ });
+ }
+
+ // Coordinator
+ getPotentialRookMoves(sq)
+ {
+ let moves = super.getPotentialQueenMoves(sq);
+ this.addRookCaptures(moves);
+ return moves;
+ }
+
+ // Long-leaper
+ getKnightCaptures(startSquare, byChameleon)
+ {
+ // Look in every direction for captures
+ const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ let moves = [];
+ const [x,y] = [startSquare[0],startSquare[1]];
+ const piece = this.getPiece(x,y); //might be a chameleon!
+ outerLoop:
+ for (let step of steps)
+ {
+ let [i,j] = [x+step[0], y+step[1]];
+ while (V.OnBoard(i,j) && this.board[i][j]==V.EMPTY)
+ {
+ i += step[0];
+ j += step[1];
+ }
+ if (!V.OnBoard(i,j) || this.getColor(i,j)==color
+ || (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT))
+ {
+ continue;
+ }
+ // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
+ // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
+ // add move until cur square; if cur is occupied then stop if !!byChameleon and
+ // the square not occupied by a leaper.
+ let last = [i,j];
+ let cur = [i+step[0],j+step[1]];
+ let vanished = [ new PiPo({x:x,y:y,c:color,p:piece}) ];
+ while (V.OnBoard(cur[0],cur[1]))
+ {
+ if (this.board[last[0]][last[1]] != V.EMPTY)
+ {
+ const oppPiece = this.getPiece(last[0],last[1]);
+ if (!!byChameleon && oppPiece != V.KNIGHT)
+ continue outerLoop;
+ // Something to eat:
+ vanished.push( new PiPo({x:last[0],y:last[1],c:oppCol,p:oppPiece}) );
+ }
+ if (this.board[cur[0]][cur[1]] != V.EMPTY)
+ {
+ if (this.getColor(cur[0],cur[1]) == color
+ || this.board[last[0]][last[1]] != V.EMPTY) //TODO: redundant test
+ {
+ continue outerLoop;
+ }
+ }
+ else
+ {
+ moves.push(new Move({
+ appear: [ new PiPo({x:cur[0],y:cur[1],c:color,p:piece}) ],
+ vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
+ start: {x:x,y:y},
+ end: {x:cur[0],y:cur[1]}
+ }));
+ }
+ last = [last[0]+step[0],last[1]+step[1]];
+ cur = [cur[0]+step[0],cur[1]+step[1]];
+ }
+ }
+ return moves;
+ }
+
+ // Long-leaper
+ getPotentialKnightMoves(sq)
+ {
+ return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
+ }
+
+ getPotentialBishopMoves([x,y])
+ {
+ let moves = super.getPotentialQueenMoves([x,y])
+ .concat(this.getKnightCaptures([x,y],"asChameleon"));
+ // No "king capture" because king cannot remain under check
+ this.addPawnCaptures(moves, "asChameleon");
+ this.addRookCaptures(moves, "asChameleon");
+ this.addQueenCaptures(moves, "asChameleon");
+ // Post-processing: merge similar moves, concatenating vanish arrays
+ let mergedMoves = {};
+ moves.forEach(m => {
+ const key = m.end.x + V.size.x * m.end.y;
+ if (!mergedMoves[key])
+ mergedMoves[key] = m;
+ else
+ {
+ for (let i=1; i<m.vanish.length; i++)
+ mergedMoves[key].vanish.push(m.vanish[i]);
+ }
+ });
+ // Finally return an array
+ moves = [];
+ Object.keys(mergedMoves).forEach(k => { moves.push(mergedMoves[k]); });
+ return moves;
+ }
+
+ // Withdrawer
+ addQueenCaptures(moves, byChameleon)
+ {
+ if (moves.length == 0)
+ return;
+ const [x,y] = [moves[0].start.x,moves[0].start.y];
+ const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ let capturingDirections = [];
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ adjacentSteps.forEach(step => {
+ const [i,j] = [x+step[0],y+step[1]];
+ if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol
+ && (!byChameleon || this.getPiece(i,j) == V.QUEEN))
+ {
+ capturingDirections.push(step);
+ }
+ });
+ moves.forEach(m => {
+ const step = [
+ m.end.x!=x ? (m.end.x-x)/Math.abs(m.end.x-x) : 0,
+ m.end.y!=y ? (m.end.y-y)/Math.abs(m.end.y-y) : 0
+ ];
+ // NOTE: includes() and even _.isEqual() functions fail...
+ // TODO: this test should be done only once per direction
+ if (capturingDirections.some(dir =>
+ { return (dir[0]==-step[0] && dir[1]==-step[1]); }))
+ {
+ const [i,j] = [x-step[0],y-step[1]];
+ m.vanish.push(new PiPo({
+ x:i,
+ y:j,
+ p:this.getPiece(i,j),
+ c:oppCol
+ }));
+ }
+ });
+ }
+
+ getPotentialQueenMoves(sq)
+ {
+ let moves = super.getPotentialQueenMoves(sq);
+ this.addQueenCaptures(moves);
+ return moves;
+ }
+
+ getPotentialImmobilizerMoves(sq)
+ {
+ // Immobilizer doesn't capture
+ return super.getPotentialQueenMoves(sq);
+ }
+
+ getPotentialKingMoves(sq)
+ {
+ return this.getSlideNJumpMoves(sq,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+ }
+
+ // isAttacked() is OK because the immobilizer doesn't take
+
+ isAttackedByPawn([x,y], colors)
+ {
+ // Square (x,y) must be surroundable by two enemy pieces,
+ // and one of them at least should be a pawn (moving).
+ const dirs = [ [1,0],[0,1] ];
+ const steps = V.steps[V.ROOK];
+ for (let dir of dirs)
+ {
+ const [i1,j1] = [x-dir[0],y-dir[1]]; //"before"
+ const [i2,j2] = [x+dir[0],y+dir[1]]; //"after"
+ if (V.OnBoard(i1,j1) && V.OnBoard(i2,j2))
+ {
+ if ((this.board[i1][j1]!=V.EMPTY && colors.includes(this.getColor(i1,j1))
+ && this.board[i2][j2]==V.EMPTY)
+ ||
+ (this.board[i2][j2]!=V.EMPTY && colors.includes(this.getColor(i2,j2))
+ && this.board[i1][j1]==V.EMPTY))
+ {
+ // Search a movable enemy pawn landing on the empty square
+ for (let step of steps)
+ {
+ let [ii,jj] = (this.board[i1][j1]==V.EMPTY ? [i1,j1] : [i2,j2]);
+ let [i3,j3] = [ii+step[0],jj+step[1]];
+ while (V.OnBoard(i3,j3) && this.board[i3][j3]==V.EMPTY)
+ {
+ i3 += step[0];
+ j3 += step[1];
+ }
+ if (V.OnBoard(i3,j3) && colors.includes(this.getColor(i3,j3))
+ && this.getPiece(i3,j3) == V.PAWN && !this.isImmobilized([i3,j3]))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ isAttackedByRook([x,y], colors)
+ {
+ // King must be on same column or row,
+ // and a rook should be able to reach a capturing square
+ // colors contains only one element, giving the oppCol and thus king position
+ const sameRow = (x == this.kingPos[colors[0]][0]);
+ const sameColumn = (y == this.kingPos[colors[0]][1]);
+ if (sameRow || sameColumn)
+ {
+ // Look for the enemy rook (maximum 1)
+ for (let i=0; i<V.size.x; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ {
+ if (this.board[i][j] != V.EMPTY && colors.includes(this.getColor(i,j))
+ && this.getPiece(i,j) == V.ROOK)
+ {
+ if (this.isImmobilized([i,j]))
+ return false; //because only one rook
+ // Can it reach a capturing square?
+ // Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
+ const moves = this.getPotentialMovesFrom([i,j]);
+ for (let move of moves)
+ {
+ if (sameRow && move.end.y == y || sameColumn && move.end.x == x)
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ isAttackedByKnight([x,y], colors)
+ {
+ // Square (x,y) must be on same line as a knight,
+ // and there must be empty square(s) behind.
+ const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ outerLoop:
+ for (let step of steps)
+ {
+ const [i0,j0] = [x+step[0],y+step[1]];
+ if (V.OnBoard(i0,j0) && this.board[i0][j0] == V.EMPTY)
+ {
+ // Try in opposite direction:
+ let [i,j] = [x-step[0],y-step[1]];
+ while (V.OnBoard(i,j))
+ {
+ while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+ {
+ i -= step[0];
+ j -= step[1];
+ }
+ if (V.OnBoard(i,j))
+ {
+ if (colors.includes(this.getColor(i,j)))
+ {
+ if (this.getPiece(i,j) == V.KNIGHT && !this.isImmobilized([i,j]))
+ return true;
+ continue outerLoop;
+ }
+ // [else] Our color, could be captured *if there was an empty space*
+ if (this.board[i+step[0]][j+step[1]] != V.EMPTY)
+ continue outerLoop;
+ i -= step[0];
+ j -= step[1];
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ isAttackedByBishop([x,y], colors)
+ {
+ // We cheat a little here: since this function is used exclusively for king,
+ // it's enough to check the immediate surrounding of the square.
+ const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ for (let step of adjacentSteps)
+ {
+ const [i,j] = [x+step[0],y+step[1]];
+ if (V.OnBoard(i,j) && this.board[i][j]!=V.EMPTY
+ && colors.includes(this.getColor(i,j)) && this.getPiece(i,j) == V.BISHOP)
+ {
+ return true; //bishops are never immobilized
+ }
+ }
+ return false;
+ }
+
+ isAttackedByQueen([x,y], colors)
+ {
+ // Square (x,y) must be adjacent to a queen, and the queen must have
+ // some free space in the opposite direction from (x,y)
+ const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ for (let step of adjacentSteps)
+ {
+ const sq2 = [x+2*step[0],y+2*step[1]];
+ if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY)
+ {
+ const sq1 = [x+step[0],y+step[1]];
+ if (this.board[sq1[0]][sq1[1]] != V.EMPTY
+ && colors.includes(this.getColor(sq1[0],sq1[1]))
+ && this.getPiece(sq1[0],sq1[1]) == V.QUEEN
+ && !this.isImmobilized(sq1))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ static get VALUES()
+ {
+ // TODO: totally experimental!
+ return {
+ 'p': 1,
+ 'r': 2,
+ 'n': 5,
+ 'b': 3,
+ 'q': 3,
+ 'm': 5,
+ 'k': 1000
+ };
+ }
+
+ static get SEARCH_DEPTH() { return 2; } //TODO?
+
+ static GenRandInitFen()
+ {
+ let pieces = { "w": new Array(8), "b": new Array(8) };
+ // Shuffle pieces on first and last rank
+ for (let c of ["w","b"])
+ {
+ let positions = ArrayFun.range(8);
+ // Get random squares for every piece, totally freely
+
+ let randIndex = randInt(8);
+ const bishop1Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ randIndex = randInt(7);
+ const bishop2Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ randIndex = randInt(6);
+ const knight1Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ randIndex = randInt(5);
+ const knight2Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ randIndex = randInt(4);
+ const queenPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ randIndex = randInt(3);
+ const kingPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ randIndex = randInt(2);
+ const rookPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+ const immobilizerPos = positions[0];
+
+ pieces[c][bishop1Pos] = 'b';
+ pieces[c][bishop2Pos] = 'b';
+ pieces[c][knight1Pos] = 'n';
+ pieces[c][knight2Pos] = 'n';
+ pieces[c][queenPos] = 'q';
+ pieces[c][kingPos] = 'k';
+ pieces[c][rookPos] = 'r';
+ pieces[c][immobilizerPos] = 'm';
+ }
+ return pieces["b"].join("") +
+ "/pppppppp/8/8/8/8/PPPPPPPP/" +
+ pieces["w"].join("").toUpperCase() +
+ " w 0";
+ }
+
+ getNotation(move)
+ {
+ const initialSquare = V.CoordsToSquare(move.start);
+ const finalSquare = V.CoordsToSquare(move.end);
+ let notation = undefined;
+ if (move.appear[0].p == V.PAWN)
+ {
+ // Pawn: generally ambiguous short notation, so we use full description
+ notation = "P" + initialSquare + finalSquare;
+ }
+ else if (move.appear[0].p == V.KING)
+ notation = "K" + (move.vanish.length>1 ? "x" : "") + finalSquare;
+ else
+ notation = move.appear[0].p.toUpperCase() + finalSquare;
+ if (move.vanish.length > 1 && move.appear[0].p != V.KING)
+ notation += "X"; //capture mark (not describing what is captured...)
+ return notation;
+ }
}
export const VariantRules = class BerolinaRules extends ChessRules
{
- // En-passant after 2-sq jump
- getEpSquare(moveOrSquare)
- {
- if (!moveOrSquare)
- return undefined;
- if (typeof moveOrSquare === "string")
- {
- const square = moveOrSquare;
- if (square == "-")
- return undefined;
- // Enemy pawn initial column must be given too:
- let res = [];
- const epParts = square.split(",");
- res.push(V.SquareToCoords(epParts[0]));
- res.push(V.ColumnToCoord(epParts[1]));
- return res;
- }
- // Argument is a move:
- const move = moveOrSquare;
- const [sx,ex,sy] = [move.start.x,move.end.x,move.start.y];
- if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
- {
- return
- [
- {
- x: (ex + sx)/2,
- y: (move.end.y + sy)/2
- },
- move.end.y
- ];
- }
- return undefined; //default
- }
+ // En-passant after 2-sq jump
+ getEpSquare(moveOrSquare)
+ {
+ if (!moveOrSquare)
+ return undefined;
+ if (typeof moveOrSquare === "string")
+ {
+ const square = moveOrSquare;
+ if (square == "-")
+ return undefined;
+ // Enemy pawn initial column must be given too:
+ let res = [];
+ const epParts = square.split(",");
+ res.push(V.SquareToCoords(epParts[0]));
+ res.push(V.ColumnToCoord(epParts[1]));
+ return res;
+ }
+ // Argument is a move:
+ const move = moveOrSquare;
+ const [sx,ex,sy] = [move.start.x,move.end.x,move.start.y];
+ if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
+ {
+ return
+ [
+ {
+ x: (ex + sx)/2,
+ y: (move.end.y + sy)/2
+ },
+ move.end.y
+ ];
+ }
+ return undefined; //default
+ }
- // Special pawns movements
- getPotentialPawnMoves([x,y])
- {
- const color = this.turn;
- let moves = [];
- const [sizeX,sizeY] = [V.size.x,V.size.y];
- const shiftX = (color == "w" ? -1 : 1);
- const firstRank = (color == 'w' ? sizeX-1 : 0);
- const startRank = (color == "w" ? sizeX-2 : 1);
- const lastRank = (color == "w" ? 0 : sizeX-1);
- const finalPieces = x + shiftX == lastRank
- ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
- : [V.PAWN];
+ // Special pawns movements
+ getPotentialPawnMoves([x,y])
+ {
+ const color = this.turn;
+ let moves = [];
+ const [sizeX,sizeY] = [V.size.x,V.size.y];
+ const shiftX = (color == "w" ? -1 : 1);
+ const firstRank = (color == 'w' ? sizeX-1 : 0);
+ const startRank = (color == "w" ? sizeX-2 : 1);
+ const lastRank = (color == "w" ? 0 : sizeX-1);
+ const finalPieces = x + shiftX == lastRank
+ ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+ : [V.PAWN];
- // One square diagonally
- for (let shiftY of [-1,1])
- {
- if (this.board[x+shiftX][y+shiftY] == V.EMPTY)
- {
- for (let piece of finalPieces)
- {
- moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
- {c:color,p:piece}));
- }
- if (x == startRank && y+2*shiftY>=0 && y+2*shiftY<sizeY
- && this.board[x+2*shiftX][y+2*shiftY] == V.EMPTY)
- {
- // Two squares jump
- moves.push(this.getBasicMove([x,y], [x+2*shiftX,y+2*shiftY]));
- }
- }
- }
- // Capture
- if (this.board[x+shiftX][y] != V.EMPTY
- && this.canTake([x,y], [x+shiftX,y]))
- {
- for (let piece of finalPieces)
- moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
- }
+ // One square diagonally
+ for (let shiftY of [-1,1])
+ {
+ if (this.board[x+shiftX][y+shiftY] == V.EMPTY)
+ {
+ for (let piece of finalPieces)
+ {
+ moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+ {c:color,p:piece}));
+ }
+ if (x == startRank && y+2*shiftY>=0 && y+2*shiftY<sizeY
+ && this.board[x+2*shiftX][y+2*shiftY] == V.EMPTY)
+ {
+ // Two squares jump
+ moves.push(this.getBasicMove([x,y], [x+2*shiftX,y+2*shiftY]));
+ }
+ }
+ }
+ // Capture
+ if (this.board[x+shiftX][y] != V.EMPTY
+ && this.canTake([x,y], [x+shiftX,y]))
+ {
+ for (let piece of finalPieces)
+ moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
+ }
- // En passant
- const Lep = this.epSquares.length;
- const epSquare = this.epSquares[Lep-1]; //always at least one element
- if (!!epSquare && epSquare[0].x == x+shiftX && epSquare[0].y == y
- && Math.abs(epSquare[1] - y) == 1)
- {
- let enpassantMove = this.getBasicMove([x,y], [x+shiftX,y]);
- enpassantMove.vanish.push({
- x: x,
- y: epSquare[1],
- p: 'p',
- c: this.getColor(x,epSquare[1])
- });
- moves.push(enpassantMove);
- }
+ // En passant
+ const Lep = this.epSquares.length;
+ const epSquare = this.epSquares[Lep-1]; //always at least one element
+ if (!!epSquare && epSquare[0].x == x+shiftX && epSquare[0].y == y
+ && Math.abs(epSquare[1] - y) == 1)
+ {
+ let enpassantMove = this.getBasicMove([x,y], [x+shiftX,y]);
+ enpassantMove.vanish.push({
+ x: x,
+ y: epSquare[1],
+ p: 'p',
+ c: this.getColor(x,epSquare[1])
+ });
+ moves.push(enpassantMove);
+ }
- return moves;
- }
+ return moves;
+ }
- isAttackedByPawn([x,y], colors)
- {
- for (let c of colors)
- {
- let pawnShift = (c=="w" ? 1 : -1);
- if (x+pawnShift>=0 && x+pawnShift<V.size.x)
- {
- if (this.getPiece(x+pawnShift,y)==V.PAWN
- && this.getColor(x+pawnShift,y)==c)
- {
- return true;
- }
- }
- }
- return false;
- }
+ isAttackedByPawn([x,y], colors)
+ {
+ for (let c of colors)
+ {
+ let pawnShift = (c=="w" ? 1 : -1);
+ if (x+pawnShift>=0 && x+pawnShift<V.size.x)
+ {
+ if (this.getPiece(x+pawnShift,y)==V.PAWN
+ && this.getColor(x+pawnShift,y)==c)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
- getNotation(move)
- {
- const piece = this.getPiece(move.start.x, move.start.y);
- if (piece == V.PAWN)
- {
- // Pawn move
- const finalSquare = V.CoordsToSquare(move.end);
- let notation = "";
- if (move.vanish.length == 2) //capture
- notation = "Px" + finalSquare;
- else
- {
- // No capture: indicate the initial square for potential ambiguity
- const startSquare = V.CoordsToSquare(move.start);
- notation = startSquare + finalSquare;
- }
- if (move.appear[0].p != V.PAWN) //promotion
- notation += "=" + move.appear[0].p.toUpperCase();
- return notation;
- }
- return super.getNotation(move); //all other pieces are orthodox
- }
+ getNotation(move)
+ {
+ const piece = this.getPiece(move.start.x, move.start.y);
+ if (piece == V.PAWN)
+ {
+ // Pawn move
+ const finalSquare = V.CoordsToSquare(move.end);
+ let notation = "";
+ if (move.vanish.length == 2) //capture
+ notation = "Px" + finalSquare;
+ else
+ {
+ // No capture: indicate the initial square for potential ambiguity
+ const startSquare = V.CoordsToSquare(move.start);
+ notation = startSquare + finalSquare;
+ }
+ if (move.appear[0].p != V.PAWN) //promotion
+ notation += "=" + move.appear[0].p.toUpperCase();
+ return notation;
+ }
+ return super.getNotation(move); //all other pieces are orthodox
+ }
}
export const VariantRules = class CheckeredRules extends ChessRules
{
- static getPpath(b)
- {
- return b[0]=='c' ? "Checkered/"+b : b;
- }
-
- static board2fen(b)
- {
- const checkered_codes = {
- 'p': 's',
- 'q': 't',
- 'r': 'u',
- 'b': 'c',
- 'n': 'o',
- };
- if (b[0]=="c")
- return checkered_codes[b[1]];
- return ChessRules.board2fen(b);
- }
-
- static fen2board(f)
- {
- // Tolerate upper-case versions of checkered pieces (why not?)
- const checkered_pieces = {
- 's': 'p',
- 'S': 'p',
- 't': 'q',
- 'T': 'q',
- 'u': 'r',
- 'U': 'r',
- 'c': 'b',
- 'C': 'b',
- 'o': 'n',
- 'O': 'n',
- };
- if (Object.keys(checkered_pieces).includes(f))
- return 'c'+checkered_pieces[f];
- return ChessRules.fen2board(f);
- }
-
- static get PIECES()
- {
- return ChessRules.PIECES.concat(['s','t','u','c','o']);
- }
+ static getPpath(b)
+ {
+ return b[0]=='c' ? "Checkered/"+b : b;
+ }
+
+ static board2fen(b)
+ {
+ const checkered_codes = {
+ 'p': 's',
+ 'q': 't',
+ 'r': 'u',
+ 'b': 'c',
+ 'n': 'o',
+ };
+ if (b[0]=="c")
+ return checkered_codes[b[1]];
+ return ChessRules.board2fen(b);
+ }
+
+ static fen2board(f)
+ {
+ // Tolerate upper-case versions of checkered pieces (why not?)
+ const checkered_pieces = {
+ 's': 'p',
+ 'S': 'p',
+ 't': 'q',
+ 'T': 'q',
+ 'u': 'r',
+ 'U': 'r',
+ 'c': 'b',
+ 'C': 'b',
+ 'o': 'n',
+ 'O': 'n',
+ };
+ if (Object.keys(checkered_pieces).includes(f))
+ return 'c'+checkered_pieces[f];
+ return ChessRules.fen2board(f);
+ }
+
+ static get PIECES()
+ {
+ return ChessRules.PIECES.concat(['s','t','u','c','o']);
+ }
setOtherVariables(fen)
{
static IsGoodFen(fen)
{
- if (!ChessRules.IsGoodFen(fen))
+ if (!ChessRules.IsGoodFen(fen))
return false;
const fenParts = fen.split(" ");
if (fenParts.length != 6)
return true;
}
- static IsGoodFlags(flags)
- {
- // 4 for castle + 16 for pawns
- return !!flags.match(/^[01]{20,20}$/);
- }
-
- setFlags(fenflags)
- {
- super.setFlags(fenflags); //castleFlags
- this.pawnFlags =
- {
- "w": [...Array(8).fill(true)], //pawns can move 2 squares?
- "b": [...Array(8).fill(true)],
- };
- if (!fenflags)
- return;
- const flags = fenflags.substr(4); //skip first 4 digits, for castle
- for (let c of ['w','b'])
- {
- for (let i=0; i<8; i++)
- this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1');
- }
- }
-
- aggregateFlags()
- {
- return [this.castleFlags, this.pawnFlags];
- }
-
- disaggregateFlags(flags)
- {
- this.castleFlags = flags[0];
- this.pawnFlags = flags[1];
- }
+ static IsGoodFlags(flags)
+ {
+ // 4 for castle + 16 for pawns
+ return !!flags.match(/^[01]{20,20}$/);
+ }
+
+ setFlags(fenflags)
+ {
+ super.setFlags(fenflags); //castleFlags
+ this.pawnFlags =
+ {
+ "w": [...Array(8).fill(true)], //pawns can move 2 squares?
+ "b": [...Array(8).fill(true)],
+ };
+ if (!fenflags)
+ return;
+ const flags = fenflags.substr(4); //skip first 4 digits, for castle
+ for (let c of ['w','b'])
+ {
+ for (let i=0; i<8; i++)
+ this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1');
+ }
+ }
+
+ aggregateFlags()
+ {
+ return [this.castleFlags, this.pawnFlags];
+ }
+
+ disaggregateFlags(flags)
+ {
+ this.castleFlags = flags[0];
+ this.pawnFlags = flags[1];
+ }
getCmove(move)
{
return null;
}
- canTake([x1,y1], [x2,y2])
- {
- const color1 = this.getColor(x1,y1);
- const color2 = this.getColor(x2,y2);
- // Checkered aren't captured
- return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
- }
-
- // Post-processing: apply "checkerization" of standard moves
- getPotentialMovesFrom([x,y])
- {
- let standardMoves = super.getPotentialMovesFrom([x,y]);
- const lastRank = this.turn == "w" ? 0 : 7;
- if (this.getPiece(x,y) == V.KING)
- return standardMoves; //king has to be treated differently (for castles)
- let moves = [];
- standardMoves.forEach(m => {
- if (m.vanish[0].p == V.PAWN)
- {
- if (Math.abs(m.end.x-m.start.x)==2 && !this.pawnFlags[this.turn][m.start.y])
- return; //skip forbidden 2-squares jumps
- if (this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length==2
- && this.getColor(m.start.x,m.start.y) == 'c')
- {
- return; //checkered pawns cannot take en-passant
- }
- }
- if (m.vanish.length == 1)
- moves.push(m); //no capture
- else
- {
- // A capture occured (m.vanish.length == 2)
- m.appear[0].c = "c";
- moves.push(m);
- if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
- && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
- {
- // Add transformation into captured piece
- let m2 = JSON.parse(JSON.stringify(m));
- m2.appear[0].p = m.vanish[1].p;
- moves.push(m2);
- }
- }
- });
- return moves;
- }
-
- canIplay(side, [x,y])
- {
- return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
- }
-
- // Does m2 un-do m1 ? (to disallow undoing checkered moves)
- oppositeMoves(m1, m2)
- {
- return (!!m1 && m2.appear[0].c == 'c'
+ canTake([x1,y1], [x2,y2])
+ {
+ const color1 = this.getColor(x1,y1);
+ const color2 = this.getColor(x2,y2);
+ // Checkered aren't captured
+ return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
+ }
+
+ // Post-processing: apply "checkerization" of standard moves
+ getPotentialMovesFrom([x,y])
+ {
+ let standardMoves = super.getPotentialMovesFrom([x,y]);
+ const lastRank = this.turn == "w" ? 0 : 7;
+ if (this.getPiece(x,y) == V.KING)
+ return standardMoves; //king has to be treated differently (for castles)
+ let moves = [];
+ standardMoves.forEach(m => {
+ if (m.vanish[0].p == V.PAWN)
+ {
+ if (Math.abs(m.end.x-m.start.x)==2 && !this.pawnFlags[this.turn][m.start.y])
+ return; //skip forbidden 2-squares jumps
+ if (this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length==2
+ && this.getColor(m.start.x,m.start.y) == 'c')
+ {
+ return; //checkered pawns cannot take en-passant
+ }
+ }
+ if (m.vanish.length == 1)
+ moves.push(m); //no capture
+ else
+ {
+ // A capture occured (m.vanish.length == 2)
+ m.appear[0].c = "c";
+ moves.push(m);
+ if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
+ && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
+ {
+ // Add transformation into captured piece
+ let m2 = JSON.parse(JSON.stringify(m));
+ m2.appear[0].p = m.vanish[1].p;
+ moves.push(m2);
+ }
+ }
+ });
+ return moves;
+ }
+
+ canIplay(side, [x,y])
+ {
+ return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
+ }
+
+ // Does m2 un-do m1 ? (to disallow undoing checkered moves)
+ oppositeMoves(m1, m2)
+ {
+ return (!!m1 && m2.appear[0].c == 'c'
&& m2.appear.length == 1 && m2.vanish.length == 1
- && m1.start.x == m2.end.x && m1.end.x == m2.start.x
- && m1.start.y == m2.end.y && m1.end.y == m2.start.y);
- }
-
- filterValid(moves)
- {
- if (moves.length == 0)
- return [];
- const color = this.turn;
- return moves.filter(m => {
- const L = this.cmoves.length; //at least 1: init from FEN
- if (this.oppositeMoves(this.cmoves[L-1], m))
- return false;
- this.play(m);
- const res = !this.underCheck(color);
- this.undo(m);
- return res;
- });
- }
-
- isAttackedByPawn([x,y], colors)
- {
- for (let c of colors)
- {
- const color = (c=="c" ? this.turn : c);
- let pawnShift = (color=="w" ? 1 : -1);
- if (x+pawnShift>=0 && x+pawnShift<8)
- {
- for (let i of [-1,1])
- {
- if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
- && this.getColor(x+pawnShift,y+i)==c)
- {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- underCheck(color)
- {
- return this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']);
- }
-
- getCheckSquares(color)
- {
- // Artifically change turn, for checkered pawns
- this.turn = V.GetOppCol(color);
- const kingAttacked = this.isAttacked(
- this.kingPos[color], [V.GetOppCol(color),'c']);
- let res = kingAttacked
- ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
- : [];
- this.turn = color;
- return res;
- }
-
- updateVariables(move)
- {
- super.updateVariables(move);
- // Does this move turn off a 2-squares pawn flag?
- const secondRank = [1,6];
- if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
- this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
- }
-
- getCurrentScore()
- {
+ && m1.start.x == m2.end.x && m1.end.x == m2.start.x
+ && m1.start.y == m2.end.y && m1.end.y == m2.start.y);
+ }
+
+ filterValid(moves)
+ {
+ if (moves.length == 0)
+ return [];
+ const color = this.turn;
+ return moves.filter(m => {
+ const L = this.cmoves.length; //at least 1: init from FEN
+ if (this.oppositeMoves(this.cmoves[L-1], m))
+ return false;
+ this.play(m);
+ const res = !this.underCheck(color);
+ this.undo(m);
+ return res;
+ });
+ }
+
+ isAttackedByPawn([x,y], colors)
+ {
+ for (let c of colors)
+ {
+ const color = (c=="c" ? this.turn : c);
+ let pawnShift = (color=="w" ? 1 : -1);
+ if (x+pawnShift>=0 && x+pawnShift<8)
+ {
+ for (let i of [-1,1])
+ {
+ if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
+ && this.getColor(x+pawnShift,y+i)==c)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ underCheck(color)
+ {
+ return this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']);
+ }
+
+ getCheckSquares(color)
+ {
+ // Artifically change turn, for checkered pawns
+ this.turn = V.GetOppCol(color);
+ const kingAttacked = this.isAttacked(
+ this.kingPos[color], [V.GetOppCol(color),'c']);
+ let res = kingAttacked
+ ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
+ : [];
+ this.turn = color;
+ return res;
+ }
+
+ updateVariables(move)
+ {
+ super.updateVariables(move);
+ // Does this move turn off a 2-squares pawn flag?
+ const secondRank = [1,6];
+ if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
+ this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
+ }
+
+ getCurrentScore()
+ {
if (this.atLeastOneMove()) // game not over
return "*";
const color = this.turn;
- // Artifically change turn, for checkered pawns
- this.turn = V.GetOppCol(this.turn);
- const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c'])
- ? (color == "w" ? "0-1" : "1-0")
- : "1/2";
- this.turn = V.GetOppCol(this.turn);
- return res;
- }
-
- evalPosition()
- {
- let evaluation = 0;
- //Just count material for now, considering checkered neutral (...)
- for (let i=0; i<V.size.x; i++)
- {
- for (let j=0; j<V.size.y; j++)
- {
- if (this.board[i][j] != V.EMPTY)
- {
- const sqColor = this.getColor(i,j);
- const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
- evaluation += sign * V.VALUES[this.getPiece(i,j)];
- }
- }
- }
- return evaluation;
- }
-
- static GenRandInitFen()
- {
- const randFen = ChessRules.GenRandInitFen();
- // Add 16 pawns flags + empty cmove:
- return randFen.replace(" w 0 1111", " w 0 11111111111111111111 -");
- }
+ // Artifically change turn, for checkered pawns
+ this.turn = V.GetOppCol(this.turn);
+ const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c'])
+ ? (color == "w" ? "0-1" : "1-0")
+ : "1/2";
+ this.turn = V.GetOppCol(this.turn);
+ return res;
+ }
+
+ evalPosition()
+ {
+ let evaluation = 0;
+ //Just count material for now, considering checkered neutral (...)
+ for (let i=0; i<V.size.x; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ {
+ if (this.board[i][j] != V.EMPTY)
+ {
+ const sqColor = this.getColor(i,j);
+ const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
+ evaluation += sign * V.VALUES[this.getPiece(i,j)];
+ }
+ }
+ }
+ return evaluation;
+ }
+
+ static GenRandInitFen()
+ {
+ const randFen = ChessRules.GenRandInitFen();
+ // Add 16 pawns flags + empty cmove:
+ return randFen.replace(" w 0 1111", " w 0 11111111111111111111 -");
+ }
static ParseFen(fen)
{
return super.getFen() + " " + cmoveFen;
}
- getFlagsFen()
- {
- let fen = super.getFlagsFen();
- // Add pawns flags
- for (let c of ['w','b'])
- {
- for (let i=0; i<8; i++)
- fen += this.pawnFlags[c][i] ? '1' : '0';
- }
- return fen;
- }
+ getFlagsFen()
+ {
+ let fen = super.getFlagsFen();
+ // Add pawns flags
+ for (let c of ['w','b'])
+ {
+ for (let i=0; i<8; i++)
+ fen += this.pawnFlags[c][i] ? '1' : '0';
+ }
+ return fen;
+ }
// TODO (design): this cmove update here or in (un)updateVariables ?
play(move)
super.undo(move);
}
- getNotation(move)
- {
- if (move.appear.length == 2)
- {
- // Castle
- if (move.end.y < move.start.y)
- return "0-0-0";
- else
- return "0-0";
- }
-
- // Translate final square
- const finalSquare = V.CoordsToSquare(move.end);
-
- const piece = this.getPiece(move.start.x, move.start.y);
- if (piece == V.PAWN)
- {
- // Pawn move
- let notation = "";
- if (move.vanish.length > 1)
- {
- // Capture
- const startColumn = V.CoordToColumn(move.start.y);
- notation = startColumn + "x" + finalSquare +
- "=" + move.appear[0].p.toUpperCase();
- }
- else //no capture
- {
- notation = finalSquare;
- if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
- notation += "=" + move.appear[0].p.toUpperCase();
- }
- return notation;
- }
-
- else
- {
- // Piece movement
- return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
- + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
- }
- }
+ getNotation(move)
+ {
+ if (move.appear.length == 2)
+ {
+ // Castle
+ if (move.end.y < move.start.y)
+ return "0-0-0";
+ else
+ return "0-0";
+ }
+
+ // Translate final square
+ const finalSquare = V.CoordsToSquare(move.end);
+
+ const piece = this.getPiece(move.start.x, move.start.y);
+ if (piece == V.PAWN)
+ {
+ // Pawn move
+ let notation = "";
+ if (move.vanish.length > 1)
+ {
+ // Capture
+ const startColumn = V.CoordToColumn(move.start.y);
+ notation = startColumn + "x" + finalSquare +
+ "=" + move.appear[0].p.toUpperCase();
+ }
+ else //no capture
+ {
+ notation = finalSquare;
+ if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
+ notation += "=" + move.appear[0].p.toUpperCase();
+ }
+ return notation;
+ }
+
+ else
+ {
+ // Piece movement
+ return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
+ + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
+ }
+ }
}
import { ChessRules } from "@/base_rules";
export const VariantRules = class Chess960Rules extends ChessRules
{
- // Standard rules
+ // Standard rules
}
export const VariantRules = class CrazyhouseRules extends ChessRules
{
- static IsGoodFen(fen)
- {
- if (!ChessRules.IsGoodFen(fen))
- return false;
- const fenParsed = V.ParseFen(fen);
- // 5) Check reserves
- if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
- return false;
- // 6) Check promoted array
- if (!fenParsed.promoted)
- return false;
- if (fenParsed.promoted == "-")
- return true; //no promoted piece on board
- const squares = fenParsed.promoted.split(",");
- for (let square of squares)
- {
- const c = V.SquareToCoords(square);
- if (c.y < 0 || c.y > V.size.y || isNaN(c.x) || c.x < 0 || c.x > V.size.x)
- return false;
- }
- return true;
- }
+ static IsGoodFen(fen)
+ {
+ if (!ChessRules.IsGoodFen(fen))
+ return false;
+ const fenParsed = V.ParseFen(fen);
+ // 5) Check reserves
+ if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
+ return false;
+ // 6) Check promoted array
+ if (!fenParsed.promoted)
+ return false;
+ if (fenParsed.promoted == "-")
+ return true; //no promoted piece on board
+ const squares = fenParsed.promoted.split(",");
+ for (let square of squares)
+ {
+ const c = V.SquareToCoords(square);
+ if (c.y < 0 || c.y > V.size.y || isNaN(c.x) || c.x < 0 || c.x > V.size.x)
+ return false;
+ }
+ return true;
+ }
- static ParseFen(fen)
- {
- const fenParts = fen.split(" ");
- return Object.assign(
- ChessRules.ParseFen(fen),
- {
- reserve: fenParts[5],
- promoted: fenParts[6],
- }
- );
- }
+ static ParseFen(fen)
+ {
+ const fenParts = fen.split(" ");
+ return Object.assign(
+ ChessRules.ParseFen(fen),
+ {
+ reserve: fenParts[5],
+ promoted: fenParts[6],
+ }
+ );
+ }
- static GenRandInitFen()
- {
- return ChessRules.GenRandInitFen() + " 0000000000 -";
- }
+ static GenRandInitFen()
+ {
+ return ChessRules.GenRandInitFen() + " 0000000000 -";
+ }
- getFen()
- {
- return super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen();
- }
+ getFen()
+ {
+ return super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen();
+ }
- getReserveFen()
- {
- let counts = new Array(10);
- for (let i=0; i<V.PIECES.length-1; i++) //-1: no king reserve
- {
- counts[i] = this.reserve["w"][V.PIECES[i]];
- counts[5+i] = this.reserve["b"][V.PIECES[i]];
- }
- return counts.join("");
- }
+ getReserveFen()
+ {
+ let counts = new Array(10);
+ for (let i=0; i<V.PIECES.length-1; i++) //-1: no king reserve
+ {
+ counts[i] = this.reserve["w"][V.PIECES[i]];
+ counts[5+i] = this.reserve["b"][V.PIECES[i]];
+ }
+ return counts.join("");
+ }
- getPromotedFen()
- {
- let res = "";
- for (let i=0; i<V.size.x; i++)
- {
- for (let j=0; j<V.size.y; j++)
- {
- if (this.promoted[i][j])
- res += V.CoordsToSquare({x:i,y:j});
- }
- }
- if (res.length > 0)
- res = res.slice(0,-1); //remove last comma
- else
- res = "-";
- return res;
- }
+ getPromotedFen()
+ {
+ let res = "";
+ for (let i=0; i<V.size.x; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ {
+ if (this.promoted[i][j])
+ res += V.CoordsToSquare({x:i,y:j});
+ }
+ }
+ if (res.length > 0)
+ res = res.slice(0,-1); //remove last comma
+ else
+ res = "-";
+ return res;
+ }
- setOtherVariables(fen)
- {
- super.setOtherVariables(fen);
- const fenParsed = V.ParseFen(fen);
- // Also init reserves (used by the interface to show landable pieces)
- this.reserve =
- {
- "w":
- {
- [V.PAWN]: parseInt(fenParsed.reserve[0]),
- [V.ROOK]: parseInt(fenParsed.reserve[1]),
- [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
- [V.BISHOP]: parseInt(fenParsed.reserve[3]),
- [V.QUEEN]: parseInt(fenParsed.reserve[4]),
- },
- "b":
- {
- [V.PAWN]: parseInt(fenParsed.reserve[5]),
- [V.ROOK]: parseInt(fenParsed.reserve[6]),
- [V.KNIGHT]: parseInt(fenParsed.reserve[7]),
- [V.BISHOP]: parseInt(fenParsed.reserve[8]),
- [V.QUEEN]: parseInt(fenParsed.reserve[9]),
- }
- };
- this.promoted = ArrayFun.init(V.size.x, V.size.y, false);
- if (fenParsed.promoted != "-")
- {
- for (let square of fenParsed.promoted.split(","))
- {
- const [x,y] = V.SquareToCoords(square);
- promoted[x][y] = true;
- }
- }
- }
+ setOtherVariables(fen)
+ {
+ super.setOtherVariables(fen);
+ const fenParsed = V.ParseFen(fen);
+ // Also init reserves (used by the interface to show landable pieces)
+ this.reserve =
+ {
+ "w":
+ {
+ [V.PAWN]: parseInt(fenParsed.reserve[0]),
+ [V.ROOK]: parseInt(fenParsed.reserve[1]),
+ [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
+ [V.BISHOP]: parseInt(fenParsed.reserve[3]),
+ [V.QUEEN]: parseInt(fenParsed.reserve[4]),
+ },
+ "b":
+ {
+ [V.PAWN]: parseInt(fenParsed.reserve[5]),
+ [V.ROOK]: parseInt(fenParsed.reserve[6]),
+ [V.KNIGHT]: parseInt(fenParsed.reserve[7]),
+ [V.BISHOP]: parseInt(fenParsed.reserve[8]),
+ [V.QUEEN]: parseInt(fenParsed.reserve[9]),
+ }
+ };
+ this.promoted = ArrayFun.init(V.size.x, V.size.y, false);
+ if (fenParsed.promoted != "-")
+ {
+ for (let square of fenParsed.promoted.split(","))
+ {
+ const [x,y] = V.SquareToCoords(square);
+ promoted[x][y] = true;
+ }
+ }
+ }
- getColor(i,j)
- {
- if (i >= V.size.x)
- return (i==V.size.x ? "w" : "b");
- return this.board[i][j].charAt(0);
- }
+ getColor(i,j)
+ {
+ if (i >= V.size.x)
+ return (i==V.size.x ? "w" : "b");
+ return this.board[i][j].charAt(0);
+ }
- getPiece(i,j)
- {
- if (i >= V.size.x)
- return V.RESERVE_PIECES[j];
- return this.board[i][j].charAt(1);
- }
+ getPiece(i,j)
+ {
+ if (i >= V.size.x)
+ return V.RESERVE_PIECES[j];
+ return this.board[i][j].charAt(1);
+ }
- // Used by the interface:
- getReservePpath(color, index)
- {
- return color + V.RESERVE_PIECES[index];
- }
+ // Used by the interface:
+ getReservePpath(color, index)
+ {
+ return color + V.RESERVE_PIECES[index];
+ }
- // Ordering on reserve pieces
- static get RESERVE_PIECES()
- {
- return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
- }
+ // Ordering on reserve pieces
+ static get RESERVE_PIECES()
+ {
+ return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+ }
- getReserveMoves([x,y])
- {
- const color = this.turn;
- const p = V.RESERVE_PIECES[y];
- if (this.reserve[color][p] == 0)
- return [];
- let moves = [];
- const pawnShift = (p==V.PAWN ? 1 : 0);
- for (let i=pawnShift; i<V.size.x-pawnShift; i++)
- {
- for (let j=0; j<V.size.y; j++)
- {
- if (this.board[i][j] == V.EMPTY)
- {
- let mv = new Move({
- appear: [
- new PiPo({
- x: i,
- y: j,
- c: color,
- p: p
- })
- ],
- vanish: [],
- start: {x:x, y:y}, //a bit artificial...
- end: {x:i, y:j}
- });
- moves.push(mv);
- }
- }
- }
- return moves;
- }
+ getReserveMoves([x,y])
+ {
+ const color = this.turn;
+ const p = V.RESERVE_PIECES[y];
+ if (this.reserve[color][p] == 0)
+ return [];
+ let moves = [];
+ const pawnShift = (p==V.PAWN ? 1 : 0);
+ for (let i=pawnShift; i<V.size.x-pawnShift; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ {
+ if (this.board[i][j] == V.EMPTY)
+ {
+ let mv = new Move({
+ appear: [
+ new PiPo({
+ x: i,
+ y: j,
+ c: color,
+ p: p
+ })
+ ],
+ vanish: [],
+ start: {x:x, y:y}, //a bit artificial...
+ end: {x:i, y:j}
+ });
+ moves.push(mv);
+ }
+ }
+ }
+ return moves;
+ }
- getPotentialMovesFrom([x,y])
- {
- if (x >= V.size.x)
- {
- // Reserves, outside of board: x == sizeX(+1)
- return this.getReserveMoves([x,y]);
- }
- // Standard moves
- return super.getPotentialMovesFrom([x,y]);
- }
+ getPotentialMovesFrom([x,y])
+ {
+ if (x >= V.size.x)
+ {
+ // Reserves, outside of board: x == sizeX(+1)
+ return this.getReserveMoves([x,y]);
+ }
+ // Standard moves
+ return super.getPotentialMovesFrom([x,y]);
+ }
- getAllValidMoves()
- {
- let moves = super.getAllValidMoves();
- const color = this.turn;
- for (let i=0; i<V.RESERVE_PIECES.length; i++)
- moves = moves.concat(this.getReserveMoves([V.size.x+(color=="w"?0:1),i]));
- return this.filterValid(moves);
- }
+ getAllValidMoves()
+ {
+ let moves = super.getAllValidMoves();
+ const color = this.turn;
+ for (let i=0; i<V.RESERVE_PIECES.length; i++)
+ moves = moves.concat(this.getReserveMoves([V.size.x+(color=="w"?0:1),i]));
+ return this.filterValid(moves);
+ }
- atLeastOneMove()
- {
- if (!super.atLeastOneMove())
- {
- const color = this.turn;
- // Search one reserve move
- for (let i=0; i<V.RESERVE_PIECES.length; i++)
- {
- let moves = this.filterValid(
- this.getReserveMoves([V.size.x+(this.turn=="w"?0:1), i]) );
- if (moves.length > 0)
- return true;
- }
- return false;
- }
- return true;
- }
+ atLeastOneMove()
+ {
+ if (!super.atLeastOneMove())
+ {
+ const color = this.turn;
+ // Search one reserve move
+ for (let i=0; i<V.RESERVE_PIECES.length; i++)
+ {
+ let moves = this.filterValid(
+ this.getReserveMoves([V.size.x+(this.turn=="w"?0:1), i]) );
+ if (moves.length > 0)
+ return true;
+ }
+ return false;
+ }
+ return true;
+ }
- updateVariables(move)
- {
- super.updateVariables(move);
- if (move.vanish.length == 2 && move.appear.length == 2)
- return; //skip castle
- const color = move.appear[0].c;
- if (move.vanish.length == 0)
- {
- this.reserve[color][move.appear[0].p]--;
- return;
- }
- move.movePromoted = this.promoted[move.start.x][move.start.y];
- move.capturePromoted = this.promoted[move.end.x][move.end.y]
- this.promoted[move.start.x][move.start.y] = false;
- this.promoted[move.end.x][move.end.y] = move.movePromoted
- || (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN);
- if (move.capturePromoted)
- this.reserve[color][V.PAWN]++;
- else if (move.vanish.length == 2)
- this.reserve[color][move.vanish[1].p]++;
- }
+ updateVariables(move)
+ {
+ super.updateVariables(move);
+ if (move.vanish.length == 2 && move.appear.length == 2)
+ return; //skip castle
+ const color = move.appear[0].c;
+ if (move.vanish.length == 0)
+ {
+ this.reserve[color][move.appear[0].p]--;
+ return;
+ }
+ move.movePromoted = this.promoted[move.start.x][move.start.y];
+ move.capturePromoted = this.promoted[move.end.x][move.end.y]
+ this.promoted[move.start.x][move.start.y] = false;
+ this.promoted[move.end.x][move.end.y] = move.movePromoted
+ || (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN);
+ if (move.capturePromoted)
+ this.reserve[color][V.PAWN]++;
+ else if (move.vanish.length == 2)
+ this.reserve[color][move.vanish[1].p]++;
+ }
- unupdateVariables(move)
- {
- super.unupdateVariables(move);
- if (move.vanish.length == 2 && move.appear.length == 2)
- return;
- const color = this.turn;
- if (move.vanish.length == 0)
- {
- this.reserve[color][move.appear[0].p]++;
- return;
- }
- if (move.movePromoted)
- this.promoted[move.start.x][move.start.y] = true;
- this.promoted[move.end.x][move.end.y] = move.capturePromoted;
- if (move.capturePromoted)
- this.reserve[color][V.PAWN]--;
- else if (move.vanish.length == 2)
- this.reserve[color][move.vanish[1].p]--;
- }
+ unupdateVariables(move)
+ {
+ super.unupdateVariables(move);
+ if (move.vanish.length == 2 && move.appear.length == 2)
+ return;
+ const color = this.turn;
+ if (move.vanish.length == 0)
+ {
+ this.reserve[color][move.appear[0].p]++;
+ return;
+ }
+ if (move.movePromoted)
+ this.promoted[move.start.x][move.start.y] = true;
+ this.promoted[move.end.x][move.end.y] = move.capturePromoted;
+ if (move.capturePromoted)
+ this.reserve[color][V.PAWN]--;
+ else if (move.vanish.length == 2)
+ this.reserve[color][move.vanish[1].p]--;
+ }
- static get SEARCH_DEPTH() { return 2; } //high branching factor
+ static get SEARCH_DEPTH() { return 2; } //high branching factor
- evalPosition()
- {
- let evaluation = super.evalPosition();
- // Add reserves:
- for (let i=0; i<V.RESERVE_PIECES.length; i++)
- {
- const p = V.RESERVE_PIECES[i];
- evaluation += this.reserve["w"][p] * V.VALUES[p];
- evaluation -= this.reserve["b"][p] * V.VALUES[p];
- }
- return evaluation;
- }
+ evalPosition()
+ {
+ let evaluation = super.evalPosition();
+ // Add reserves:
+ for (let i=0; i<V.RESERVE_PIECES.length; i++)
+ {
+ const p = V.RESERVE_PIECES[i];
+ evaluation += this.reserve["w"][p] * V.VALUES[p];
+ evaluation -= this.reserve["b"][p] * V.VALUES[p];
+ }
+ return evaluation;
+ }
- getNotation(move)
- {
- if (move.vanish.length > 0)
- return super.getNotation(move);
- // Rebirth:
- const piece =
- (move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "");
- return piece + "@" + V.CoordsToSquare(move.end);
- }
+ getNotation(move)
+ {
+ if (move.vanish.length > 0)
+ return super.getNotation(move);
+ // Rebirth:
+ const piece =
+ (move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "");
+ return piece + "@" + V.CoordsToSquare(move.end);
+ }
- getLongNotation(move)
- {
- if (move.vanish.length > 0)
- return super.getLongNotation(move);
- return "@" + V.CoordsToSquare(move.end);
- }
+ getLongNotation(move)
+ {
+ if (move.vanish.length > 0)
+ return super.getLongNotation(move);
+ return "@" + V.CoordsToSquare(move.end);
+ }
}
export const VariantRules = class DarkRules extends ChessRules
{
- // Standard rules, in the shadow
- setOtherVariables(fen)
- {
- super.setOtherVariables(fen);
- const [sizeX,sizeY] = [V.size.x,V.size.y];
- this.enlightened = {
- "w": ArrayFun.init(sizeX,sizeY),
- "b": ArrayFun.init(sizeX,sizeY)
- };
- // Setup enlightened: squares reachable by each side
- // (TODO: one side would be enough ?)
- this.updateEnlightened();
- }
+ // Standard rules, in the shadow
+ setOtherVariables(fen)
+ {
+ super.setOtherVariables(fen);
+ const [sizeX,sizeY] = [V.size.x,V.size.y];
+ this.enlightened = {
+ "w": ArrayFun.init(sizeX,sizeY),
+ "b": ArrayFun.init(sizeX,sizeY)
+ };
+ // Setup enlightened: squares reachable by each side
+ // (TODO: one side would be enough ?)
+ this.updateEnlightened();
+ }
- updateEnlightened()
- {
- for (let i=0; i<V.size.x; i++)
- {
- for (let j=0; j<V.size.y; j++)
- {
- this.enlightened["w"][i][j] = false;
- this.enlightened["b"][i][j] = false;
- }
- }
- const pawnShift = {"w":-1, "b":1};
- // Initialize with pieces positions (which are seen)
- for (let i=0; i<V.size.x; i++)
- {
- for (let j=0; j<V.size.y; j++)
- {
- if (this.board[i][j] != V.EMPTY)
- {
- const color = this.getColor(i,j);
- this.enlightened[color][i][j] = true;
- // Add potential squares visible by "impossible pawn capture"
- if (this.getPiece(i,j) == V.PAWN)
- {
- for (let shiftY of [-1,1])
- {
- if (V.OnBoard(i+pawnShift[color],j+shiftY)
- && this.board[i+pawnShift[color]][j+shiftY] == V.EMPTY)
- {
- this.enlightened[color][i+pawnShift[color]][j+shiftY] = true;
- }
- }
- }
- }
- }
- }
- const currentTurn = this.turn;
- this.turn = "w";
- const movesWhite = this.getAllValidMoves();
- this.turn = "b";
- const movesBlack = this.getAllValidMoves();
- this.turn = currentTurn;
- for (let move of movesWhite)
- this.enlightened["w"][move.end.x][move.end.y] = true;
- for (let move of movesBlack)
- this.enlightened["b"][move.end.x][move.end.y] = true;
- }
+ updateEnlightened()
+ {
+ for (let i=0; i<V.size.x; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ {
+ this.enlightened["w"][i][j] = false;
+ this.enlightened["b"][i][j] = false;
+ }
+ }
+ const pawnShift = {"w":-1, "b":1};
+ // Initialize with pieces positions (which are seen)
+ for (let i=0; i<V.size.x; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ {
+ if (this.board[i][j] != V.EMPTY)
+ {
+ const color = this.getColor(i,j);
+ this.enlightened[color][i][j] = true;
+ // Add potential squares visible by "impossible pawn capture"
+ if (this.getPiece(i,j) == V.PAWN)
+ {
+ for (let shiftY of [-1,1])
+ {
+ if (V.OnBoard(i+pawnShift[color],j+shiftY)
+ && this.board[i+pawnShift[color]][j+shiftY] == V.EMPTY)
+ {
+ this.enlightened[color][i+pawnShift[color]][j+shiftY] = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ const currentTurn = this.turn;
+ this.turn = "w";
+ const movesWhite = this.getAllValidMoves();
+ this.turn = "b";
+ const movesBlack = this.getAllValidMoves();
+ this.turn = currentTurn;
+ for (let move of movesWhite)
+ this.enlightened["w"][move.end.x][move.end.y] = true;
+ for (let move of movesBlack)
+ this.enlightened["b"][move.end.x][move.end.y] = true;
+ }
- // Has to be redefined to avoid an infinite loop
- getAllValidMoves()
- {
- const color = this.turn;
- const oppCol = V.GetOppCol(color);
- let potentialMoves = [];
- for (let i=0; i<V.size.x; i++)
- {
- for (let j=0; j<V.size.y; j++)
- {
- if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == color)
- Array.prototype.push.apply(potentialMoves, this.getPotentialMovesFrom([i,j]));
- }
- }
- return potentialMoves; //because there are no checks
- }
+ // Has to be redefined to avoid an infinite loop
+ getAllValidMoves()
+ {
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ let potentialMoves = [];
+ for (let i=0; i<V.size.x; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ {
+ if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == color)
+ Array.prototype.push.apply(potentialMoves, this.getPotentialMovesFrom([i,j]));
+ }
+ }
+ return potentialMoves; //because there are no checks
+ }
- atLeastOneMove()
- {
- if (this.kingPos[this.turn][0] < 0)
- return false;
- return true; //TODO: is it right?
- }
+ atLeastOneMove()
+ {
+ if (this.kingPos[this.turn][0] < 0)
+ return false;
+ return true; //TODO: is it right?
+ }
- underCheck(color)
- {
- return false; //there is no check
- }
+ underCheck(color)
+ {
+ return false; //there is no check
+ }
- getCheckSquares(color)
- {
- return [];
- }
+ getCheckSquares(color)
+ {
+ return [];
+ }
- updateVariables(move)
- {
- super.updateVariables(move);
- if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
- {
- // We took opponent king ! (because if castle vanish[1] is a rook)
- this.kingPos[this.turn] = [-1,-1];
- }
+ updateVariables(move)
+ {
+ super.updateVariables(move);
+ if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
+ {
+ // We took opponent king ! (because if castle vanish[1] is a rook)
+ this.kingPos[this.turn] = [-1,-1];
+ }
- // Update lights for both colors:
- this.updateEnlightened();
- }
+ // Update lights for both colors:
+ this.updateEnlightened();
+ }
- unupdateVariables(move)
- {
- super.unupdateVariables(move);
- const c = move.vanish[0].c;
- const oppCol = V.GetOppCol(c);
- if (this.kingPos[oppCol][0] < 0)
- {
- // Last move took opponent's king
- for (let psq of move.vanish)
- {
- if (psq.p == 'k')
- {
- this.kingPos[oppCol] = [psq.x, psq.y];
- break;
- }
- }
- }
+ unupdateVariables(move)
+ {
+ super.unupdateVariables(move);
+ const c = move.vanish[0].c;
+ const oppCol = V.GetOppCol(c);
+ if (this.kingPos[oppCol][0] < 0)
+ {
+ // Last move took opponent's king
+ for (let psq of move.vanish)
+ {
+ if (psq.p == 'k')
+ {
+ this.kingPos[oppCol] = [psq.x, psq.y];
+ break;
+ }
+ }
+ }
- // Update lights for both colors:
- this.updateEnlightened();
- }
+ // Update lights for both colors:
+ this.updateEnlightened();
+ }
getCurrentScore()
{
- const color = this.turn;
- const kp = this.kingPos[color];
- if (kp[0] < 0) //king disappeared
- return (color == "w" ? "0-1" : "1-0");
+ const color = this.turn;
+ const kp = this.kingPos[color];
+ if (kp[0] < 0) //king disappeared
+ return (color == "w" ? "0-1" : "1-0");
if (this.atLeastOneMove()) // game not over
return "*";
return "1/2"; //no moves but kings still there (seems impossible)
- }
+ }
- static get THRESHOLD_MATE()
- {
- return 500; //checkmates evals may be slightly below 1000
- }
+ static get THRESHOLD_MATE()
+ {
+ 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 = V.GetOppCol(color);
- const pawnShift = (color == "w" ? -1 : 1);
+ // In this special situation, we just look 1 half move ahead
+ getComputerMove()
+ {
+ const maxeval = V.INFINITY;
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ const pawnShift = (color == "w" ? -1 : 1);
- // Do not cheat: the current enlightment is all we can see
- const myLight = JSON.parse(JSON.stringify(this.enlightened[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) => {
- const kp = this.kingPos[color];
- 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 (
+ // 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) => {
+ const kp = this.kingPos[color];
+ 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])
+ 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;
- };
+ 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 = () => {
- const kp = this.kingPos[color];
- 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;
- };
+ // Do I see something which can take my king ?
+ const kingThreats = () => {
+ const kp = this.kingPos[color];
+ 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);
+ 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;
+ continue;
- move.eval = 0; //a priori...
+ 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);
- }
- }
- }
+ // 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.
+ // 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[candidates[randInt(candidates.length)]];
- }
+ 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[candidates[randInt(candidates.length)]];
+ }
}
export const VariantRules = class ExtinctionRules extends ChessRules
{
- setOtherVariables(fen)
- {
- super.setOtherVariables(fen);
- const pos = V.ParseFen(fen).position;
- // NOTE: no need for safety "|| []", because each piece type must be present
- // (otherwise game is already over!)
- this.material =
- {
- "w":
- {
- [V.KING]: pos.match(/K/g).length,
- [V.QUEEN]: pos.match(/Q/g).length,
- [V.ROOK]: pos.match(/R/g).length,
- [V.KNIGHT]: pos.match(/N/g).length,
- [V.BISHOP]: pos.match(/B/g).length,
- [V.PAWN]: pos.match(/P/g).length
- },
- "b":
- {
- [V.KING]: pos.match(/k/g).length,
- [V.QUEEN]: pos.match(/q/g).length,
- [V.ROOK]: pos.match(/r/g).length,
- [V.KNIGHT]: pos.match(/n/g).length,
- [V.BISHOP]: pos.match(/b/g).length,
- [V.PAWN]: pos.match(/p/g).length
- }
- };
- }
+ setOtherVariables(fen)
+ {
+ super.setOtherVariables(fen);
+ const pos = V.ParseFen(fen).position;
+ // NOTE: no need for safety "|| []", because each piece type must be present
+ // (otherwise game is already over!)
+ this.material =
+ {
+ "w":
+ {
+ [V.KING]: pos.match(/K/g).length,
+ [V.QUEEN]: pos.match(/Q/g).length,
+ [V.ROOK]: pos.match(/R/g).length,
+ [V.KNIGHT]: pos.match(/N/g).length,
+ [V.BISHOP]: pos.match(/B/g).length,
+ [V.PAWN]: pos.match(/P/g).length
+ },
+ "b":
+ {
+ [V.KING]: pos.match(/k/g).length,
+ [V.QUEEN]: pos.match(/q/g).length,
+ [V.ROOK]: pos.match(/r/g).length,
+ [V.KNIGHT]: pos.match(/n/g).length,
+ [V.BISHOP]: pos.match(/b/g).length,
+ [V.PAWN]: pos.match(/p/g).length
+ }
+ };
+ }
- getPotentialPawnMoves([x,y])
- {
- let moves = super.getPotentialPawnMoves([x,y]);
- // Add potential promotions into king
- const color = this.turn;
- const shift = (color == "w" ? -1 : 1);
- const lastRank = (color == "w" ? 0 : V.size.x-1);
+ getPotentialPawnMoves([x,y])
+ {
+ let moves = super.getPotentialPawnMoves([x,y]);
+ // Add potential promotions into king
+ const color = this.turn;
+ const shift = (color == "w" ? -1 : 1);
+ const lastRank = (color == "w" ? 0 : V.size.x-1);
- if (x+shift == lastRank)
- {
- // Normal move
- if (this.board[x+shift][y] == V.EMPTY)
- moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
- // Captures
- if (y>0 && this.board[x+shift][y-1] != V.EMPTY
- && this.canTake([x,y], [x+shift,y-1]))
- {
- moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
- }
- if (y<V.size.y-1 && this.board[x+shift][y+1] != V.EMPTY
- && this.canTake([x,y], [x+shift,y+1]))
- {
- moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
- }
- }
+ if (x+shift == lastRank)
+ {
+ // Normal move
+ if (this.board[x+shift][y] == V.EMPTY)
+ moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
+ // Captures
+ if (y>0 && this.board[x+shift][y-1] != V.EMPTY
+ && this.canTake([x,y], [x+shift,y-1]))
+ {
+ moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
+ }
+ if (y<V.size.y-1 && this.board[x+shift][y+1] != V.EMPTY
+ && this.canTake([x,y], [x+shift,y+1]))
+ {
+ moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
+ }
+ }
- return moves;
- }
+ return moves;
+ }
- // TODO: verify this assertion
- atLeastOneMove()
- {
- return true; //always at least one possible move
- }
+ // TODO: verify this assertion
+ atLeastOneMove()
+ {
+ return true; //always at least one possible move
+ }
- underCheck(color)
- {
- return false; //there is no check
- }
+ underCheck(color)
+ {
+ return false; //there is no check
+ }
- getCheckSquares(color)
- {
- return [];
- }
+ getCheckSquares(color)
+ {
+ return [];
+ }
- updateVariables(move)
- {
- super.updateVariables(move);
- // Treat the promotion case: (not the capture part)
- if (move.appear[0].p != move.vanish[0].p)
- {
- this.material[move.appear[0].c][move.appear[0].p]++;
- this.material[move.appear[0].c][V.PAWN]--;
- }
- if (move.vanish.length==2 && move.appear.length==1) //capture
- this.material[move.vanish[1].c][move.vanish[1].p]--;
- }
+ updateVariables(move)
+ {
+ super.updateVariables(move);
+ // Treat the promotion case: (not the capture part)
+ if (move.appear[0].p != move.vanish[0].p)
+ {
+ this.material[move.appear[0].c][move.appear[0].p]++;
+ this.material[move.appear[0].c][V.PAWN]--;
+ }
+ if (move.vanish.length==2 && move.appear.length==1) //capture
+ this.material[move.vanish[1].c][move.vanish[1].p]--;
+ }
- unupdateVariables(move)
- {
- super.unupdateVariables(move);
- if (move.appear[0].p != move.vanish[0].p)
- {
- this.material[move.appear[0].c][move.appear[0].p]--;
- this.material[move.appear[0].c][V.PAWN]++;
- }
- if (move.vanish.length==2 && move.appear.length==1)
- this.material[move.vanish[1].c][move.vanish[1].p]++;
- }
+ unupdateVariables(move)
+ {
+ super.unupdateVariables(move);
+ if (move.appear[0].p != move.vanish[0].p)
+ {
+ this.material[move.appear[0].c][move.appear[0].p]--;
+ this.material[move.appear[0].c][V.PAWN]++;
+ }
+ if (move.vanish.length==2 && move.appear.length==1)
+ this.material[move.vanish[1].c][move.vanish[1].p]++;
+ }
- getCurrentScore()
- {
- if (this.atLeastOneMove()) // game not over?
- {
- const color = this.turn;
- if (Object.keys(this.material[color]).some(
- p => { return this.material[color][p] == 0; }))
- {
- return (this.turn == "w" ? "0-1" : "1-0");
- }
- return "*";
- }
+ getCurrentScore()
+ {
+ if (this.atLeastOneMove()) // game not over?
+ {
+ const color = this.turn;
+ if (Object.keys(this.material[color]).some(
+ p => { return this.material[color][p] == 0; }))
+ {
+ return (this.turn == "w" ? "0-1" : "1-0");
+ }
+ return "*";
+ }
- return (this.turn == "w" ? "0-1" : "1-0"); //NOTE: currently unreachable...
- }
+ return (this.turn == "w" ? "0-1" : "1-0"); //NOTE: currently unreachable...
+ }
- evalPosition()
- {
- const color = this.turn;
- if (Object.keys(this.material[color]).some(
- p => { return this.material[color][p] == 0; }))
- {
- // Very negative (resp. positive) if white (reps. black) pieces set is incomplete
- return (color=="w"?-1:1) * V.INFINITY;
- }
- return super.evalPosition();
- }
+ evalPosition()
+ {
+ const color = this.turn;
+ if (Object.keys(this.material[color]).some(
+ p => { return this.material[color][p] == 0; }))
+ {
+ // Very negative (resp. positive) if white (reps. black) pieces set is incomplete
+ return (color=="w"?-1:1) * V.INFINITY;
+ }
+ return super.evalPosition();
+ }
}
// https://www.chessvariants.com/large.dir/freeling.html
export const VariantRules = class GrandRules extends ChessRules
{
- static getPpath(b)
- {
- return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
- }
-
- static IsGoodFen(fen)
- {
- if (!ChessRules.IsGoodFen(fen))
- return false;
- const fenParsed = V.ParseFen(fen);
- // 5) Check captures
- if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/))
- return false;
- return true;
- }
-
- static IsGoodEnpassant(enpassant)
- {
- if (enpassant != "-")
- {
- const squares = enpassant.split(",");
- if (squares.length > 2)
- return false;
- for (let sq of squares)
- {
- const ep = V.SquareToCoords(sq);
- if (isNaN(ep.x) || !V.OnBoard(ep))
- return false;
- }
- }
- return true;
- }
-
- static ParseFen(fen)
- {
- const fenParts = fen.split(" ");
- return Object.assign(
- ChessRules.ParseFen(fen),
- { captured: fenParts[5] }
- );
- }
-
- getFen()
- {
- return super.getFen() + " " + this.getCapturedFen();
- }
-
- getCapturedFen()
- {
- let counts = [...Array(14).fill(0)];
- let i = 0;
- for (let j=0; j<V.PIECES.length; j++)
- {
- if (V.PIECES[j] == V.KING) //no king captured
- continue;
- counts[i] = this.captured["w"][V.PIECES[i]];
- counts[7+i] = this.captured["b"][V.PIECES[i]];
- i++;
- }
- return counts.join("");
- }
-
- setOtherVariables(fen)
- {
- super.setOtherVariables(fen);
- const fenParsed = V.ParseFen(fen);
- // Initialize captured pieces' counts from FEN
- this.captured =
- {
- "w":
- {
- [V.PAWN]: parseInt(fenParsed.captured[0]),
- [V.ROOK]: parseInt(fenParsed.captured[1]),
- [V.KNIGHT]: parseInt(fenParsed.captured[2]),
- [V.BISHOP]: parseInt(fenParsed.captured[3]),
- [V.QUEEN]: parseInt(fenParsed.captured[4]),
- [V.MARSHALL]: parseInt(fenParsed.captured[5]),
- [V.CARDINAL]: parseInt(fenParsed.captured[6]),
- },
- "b":
- {
- [V.PAWN]: parseInt(fenParsed.captured[7]),
- [V.ROOK]: parseInt(fenParsed.captured[8]),
- [V.KNIGHT]: parseInt(fenParsed.captured[9]),
- [V.BISHOP]: parseInt(fenParsed.captured[10]),
- [V.QUEEN]: parseInt(fenParsed.captured[11]),
- [V.MARSHALL]: parseInt(fenParsed.captured[12]),
- [V.CARDINAL]: parseInt(fenParsed.captured[13]),
- }
- };
- }
-
- static get size() { return {x:10,y:10}; }
-
- static get MARSHALL() { return 'm'; } //rook+knight
- static get CARDINAL() { return 'c'; } //bishop+knight
-
- static get PIECES()
- {
- return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]);
- }
-
- // There may be 2 enPassant squares (if pawn jump 3 squares)
- getEnpassantFen()
- {
- const L = this.epSquares.length;
- if (!this.epSquares[L-1])
- return "-"; //no en-passant
- let res = "";
- this.epSquares[L-1].forEach(sq => {
- res += V.CoordsToSquare(sq) + ",";
- });
- return res.slice(0,-1); //remove last comma
- }
-
- // En-passant after 2-sq or 3-sq jumps
- getEpSquare(moveOrSquare)
- {
- if (!moveOrSquare)
- return undefined;
- if (typeof moveOrSquare === "string")
- {
- const square = moveOrSquare;
- if (square == "-")
- return undefined;
- let res = [];
- square.split(",").forEach(sq => {
- res.push(V.SquareToCoords(sq));
- });
- return res;
- }
- // 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)
- {
- const step = (ex-sx) / Math.abs(ex-sx);
- let res = [{
- x: sx + step,
- y: sy
- }];
- if (sx + 2*step != ex) //3-squares move
- {
- res.push({
- x: sx + 2*step,
- y: sy
- });
- }
- return res;
- }
- return undefined; //default
- }
-
- getPotentialMovesFrom([x,y])
- {
- switch (this.getPiece(x,y))
- {
- case V.MARSHALL:
- return this.getPotentialMarshallMoves([x,y]);
- case V.CARDINAL:
- return this.getPotentialCardinalMoves([x,y]);
- default:
- return super.getPotentialMovesFrom([x,y])
- }
- }
-
- // Special pawn rules: promotions to captured friendly pieces,
- // optional on ranks 8-9 and mandatory on rank 10.
- getPotentialPawnMoves([x,y])
- {
- const color = this.turn;
- let moves = [];
- const [sizeX,sizeY] = [V.size.x,V.size.y];
- const shiftX = (color == "w" ? -1 : 1);
- const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
- const lastRanks = (color == "w" ? [0,1,2] : [sizeX-1,sizeX-2,sizeX-3]);
- const promotionPieces =
- [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
-
- // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
- let finalPieces = undefined;
- if (lastRanks.includes(x + shiftX))
- {
- finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0);
- if (x + shiftX != lastRanks[0])
- finalPieces.push(V.PAWN);
- }
- else
- finalPieces = [V.PAWN];
- if (this.board[x+shiftX][y] == V.EMPTY)
- {
- // One square forward
- for (let piece of finalPieces)
- moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
- if (startRanks.includes(x))
- {
- if (this.board[x+2*shiftX][y] == V.EMPTY)
- {
- // Two squares jump
- moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
- if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
- {
- // Three squares jump
- moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
- }
- }
- }
- }
- // Captures
- for (let shiftY of [-1,1])
- {
- if (y + shiftY >= 0 && y + shiftY < sizeY
- && this.board[x+shiftX][y+shiftY] != V.EMPTY
- && this.canTake([x,y], [x+shiftX,y+shiftY]))
- {
- for (let piece of finalPieces)
- {
- moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
- {c:color,p:piece}));
- }
- }
- }
-
- // En passant
- const Lep = this.epSquares.length;
- const epSquare = this.epSquares[Lep-1];
- if (!!epSquare)
- {
- for (let epsq of epSquare)
- {
- // TODO: some redundant checks
- if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
- {
- var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
- // WARNING: the captured pawn may be diagonally behind us,
- // if it's a 3-squares jump and we take on 1st passing square
- const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
- enpassantMove.vanish.push({
- x: px,
- y: epsq.y,
- p: 'p',
- c: this.getColor(px,epsq.y)
- });
- moves.push(enpassantMove);
- }
- }
- }
-
- return moves;
- }
-
- // TODO: different castle?
-
- getPotentialMarshallMoves(sq)
- {
- return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
- this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
- }
-
- getPotentialCardinalMoves(sq)
- {
- return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
- this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
- }
-
- isAttacked(sq, colors)
- {
- return super.isAttacked(sq, colors)
- || this.isAttackedByMarshall(sq, colors)
- || this.isAttackedByCardinal(sq, colors);
- }
-
- isAttackedByMarshall(sq, colors)
- {
- return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
- || this.isAttackedBySlideNJump(
- sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
- }
-
- isAttackedByCardinal(sq, colors)
- {
- return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
- || this.isAttackedBySlideNJump(
- sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
- }
-
- updateVariables(move)
- {
- super.updateVariables(move);
- if (move.vanish.length == 2 && move.appear.length == 1)
- {
- // Capture: update this.captured
- this.captured[move.vanish[1].c][move.vanish[1].p]++;
- }
- if (move.vanish[0].p != move.appear[0].p)
- {
- // Promotion: update this.captured
- this.captured[move.vanish[0].c][move.appear[0].p]--;
- }
- }
-
- unupdateVariables(move)
- {
- super.unupdateVariables(move);
- if (move.vanish.length == 2 && move.appear.length == 1)
- this.captured[move.vanish[1].c][move.vanish[1].p]--;
- if (move.vanish[0].p != move.appear[0].p)
- this.captured[move.vanish[0].c][move.appear[0].p]++;
- }
-
- static get VALUES()
- {
- return Object.assign(
- ChessRules.VALUES,
- {'c': 5, 'm': 7} //experimental
- );
- }
-
- static get SEARCH_DEPTH() { return 2; }
-
- // TODO: this function could be generalized and shared better (how ?!...)
- static GenRandInitFen()
- {
- let pieces = { "w": new Array(10), "b": new Array(10) };
- // Shuffle pieces on first and last rank
- for (let c of ["w","b"])
- {
- let positions = ArrayFun.range(10);
-
- // Get random squares for bishops
- let randIndex = 2 * randInt(5);
- let bishop1Pos = positions[randIndex];
- // The second bishop must be on a square of different color
- let randIndex_tmp = 2 * randInt(5) + 1;
- let bishop2Pos = positions[randIndex_tmp];
- // Remove chosen squares
- positions.splice(Math.max(randIndex,randIndex_tmp), 1);
- positions.splice(Math.min(randIndex,randIndex_tmp), 1);
-
- // Get random squares for knights
- randIndex = randInt(8);
- let knight1Pos = positions[randIndex];
- positions.splice(randIndex, 1);
- randIndex = randInt(7);
- let knight2Pos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- // Get random square for queen
- randIndex = randInt(6);
- let queenPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- // ...random square for marshall
- randIndex = randInt(5);
- let marshallPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- // ...random square for cardinal
- randIndex = randInt(4);
- let cardinalPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- // Rooks and king positions are now fixed, because of the ordering rook-king-rook
- let rook1Pos = positions[0];
- let kingPos = positions[1];
- let rook2Pos = positions[2];
-
- // Finally put the shuffled pieces in the board array
- pieces[c][rook1Pos] = 'r';
- pieces[c][knight1Pos] = 'n';
- pieces[c][bishop1Pos] = 'b';
- pieces[c][queenPos] = 'q';
- pieces[c][marshallPos] = 'm';
- pieces[c][cardinalPos] = 'c';
- pieces[c][kingPos] = 'k';
- pieces[c][bishop2Pos] = 'b';
- pieces[c][knight2Pos] = 'n';
- pieces[c][rook2Pos] = 'r';
- }
- return pieces["b"].join("") +
- "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
- pieces["w"].join("").toUpperCase() +
- " w 0 1111 - 00000000000000";
- }
+ static getPpath(b)
+ {
+ return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
+ }
+
+ static IsGoodFen(fen)
+ {
+ if (!ChessRules.IsGoodFen(fen))
+ return false;
+ const fenParsed = V.ParseFen(fen);
+ // 5) Check captures
+ if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/))
+ return false;
+ return true;
+ }
+
+ static IsGoodEnpassant(enpassant)
+ {
+ if (enpassant != "-")
+ {
+ const squares = enpassant.split(",");
+ if (squares.length > 2)
+ return false;
+ for (let sq of squares)
+ {
+ const ep = V.SquareToCoords(sq);
+ if (isNaN(ep.x) || !V.OnBoard(ep))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static ParseFen(fen)
+ {
+ const fenParts = fen.split(" ");
+ return Object.assign(
+ ChessRules.ParseFen(fen),
+ { captured: fenParts[5] }
+ );
+ }
+
+ getFen()
+ {
+ return super.getFen() + " " + this.getCapturedFen();
+ }
+
+ getCapturedFen()
+ {
+ let counts = [...Array(14).fill(0)];
+ let i = 0;
+ for (let j=0; j<V.PIECES.length; j++)
+ {
+ if (V.PIECES[j] == V.KING) //no king captured
+ continue;
+ counts[i] = this.captured["w"][V.PIECES[i]];
+ counts[7+i] = this.captured["b"][V.PIECES[i]];
+ i++;
+ }
+ return counts.join("");
+ }
+
+ setOtherVariables(fen)
+ {
+ super.setOtherVariables(fen);
+ const fenParsed = V.ParseFen(fen);
+ // Initialize captured pieces' counts from FEN
+ this.captured =
+ {
+ "w":
+ {
+ [V.PAWN]: parseInt(fenParsed.captured[0]),
+ [V.ROOK]: parseInt(fenParsed.captured[1]),
+ [V.KNIGHT]: parseInt(fenParsed.captured[2]),
+ [V.BISHOP]: parseInt(fenParsed.captured[3]),
+ [V.QUEEN]: parseInt(fenParsed.captured[4]),
+ [V.MARSHALL]: parseInt(fenParsed.captured[5]),
+ [V.CARDINAL]: parseInt(fenParsed.captured[6]),
+ },
+ "b":
+ {
+ [V.PAWN]: parseInt(fenParsed.captured[7]),
+ [V.ROOK]: parseInt(fenParsed.captured[8]),
+ [V.KNIGHT]: parseInt(fenParsed.captured[9]),
+ [V.BISHOP]: parseInt(fenParsed.captured[10]),
+ [V.QUEEN]: parseInt(fenParsed.captured[11]),
+ [V.MARSHALL]: parseInt(fenParsed.captured[12]),
+ [V.CARDINAL]: parseInt(fenParsed.captured[13]),
+ }
+ };
+ }
+
+ static get size() { return {x:10,y:10}; }
+
+ static get MARSHALL() { return 'm'; } //rook+knight
+ static get CARDINAL() { return 'c'; } //bishop+knight
+
+ static get PIECES()
+ {
+ return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]);
+ }
+
+ // There may be 2 enPassant squares (if pawn jump 3 squares)
+ getEnpassantFen()
+ {
+ const L = this.epSquares.length;
+ if (!this.epSquares[L-1])
+ return "-"; //no en-passant
+ let res = "";
+ this.epSquares[L-1].forEach(sq => {
+ res += V.CoordsToSquare(sq) + ",";
+ });
+ return res.slice(0,-1); //remove last comma
+ }
+
+ // En-passant after 2-sq or 3-sq jumps
+ getEpSquare(moveOrSquare)
+ {
+ if (!moveOrSquare)
+ return undefined;
+ if (typeof moveOrSquare === "string")
+ {
+ const square = moveOrSquare;
+ if (square == "-")
+ return undefined;
+ let res = [];
+ square.split(",").forEach(sq => {
+ res.push(V.SquareToCoords(sq));
+ });
+ return res;
+ }
+ // 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)
+ {
+ const step = (ex-sx) / Math.abs(ex-sx);
+ let res = [{
+ x: sx + step,
+ y: sy
+ }];
+ if (sx + 2*step != ex) //3-squares move
+ {
+ res.push({
+ x: sx + 2*step,
+ y: sy
+ });
+ }
+ return res;
+ }
+ return undefined; //default
+ }
+
+ getPotentialMovesFrom([x,y])
+ {
+ switch (this.getPiece(x,y))
+ {
+ case V.MARSHALL:
+ return this.getPotentialMarshallMoves([x,y]);
+ case V.CARDINAL:
+ return this.getPotentialCardinalMoves([x,y]);
+ default:
+ return super.getPotentialMovesFrom([x,y])
+ }
+ }
+
+ // Special pawn rules: promotions to captured friendly pieces,
+ // optional on ranks 8-9 and mandatory on rank 10.
+ getPotentialPawnMoves([x,y])
+ {
+ const color = this.turn;
+ let moves = [];
+ const [sizeX,sizeY] = [V.size.x,V.size.y];
+ const shiftX = (color == "w" ? -1 : 1);
+ const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
+ const lastRanks = (color == "w" ? [0,1,2] : [sizeX-1,sizeX-2,sizeX-3]);
+ const promotionPieces =
+ [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
+
+ // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
+ let finalPieces = undefined;
+ if (lastRanks.includes(x + shiftX))
+ {
+ finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0);
+ if (x + shiftX != lastRanks[0])
+ finalPieces.push(V.PAWN);
+ }
+ else
+ finalPieces = [V.PAWN];
+ if (this.board[x+shiftX][y] == V.EMPTY)
+ {
+ // One square forward
+ for (let piece of finalPieces)
+ moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
+ if (startRanks.includes(x))
+ {
+ if (this.board[x+2*shiftX][y] == V.EMPTY)
+ {
+ // Two squares jump
+ moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+ if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
+ {
+ // Three squares jump
+ moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
+ }
+ }
+ }
+ }
+ // Captures
+ for (let shiftY of [-1,1])
+ {
+ if (y + shiftY >= 0 && y + shiftY < sizeY
+ && this.board[x+shiftX][y+shiftY] != V.EMPTY
+ && this.canTake([x,y], [x+shiftX,y+shiftY]))
+ {
+ for (let piece of finalPieces)
+ {
+ moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+ {c:color,p:piece}));
+ }
+ }
+ }
+
+ // En passant
+ const Lep = this.epSquares.length;
+ const epSquare = this.epSquares[Lep-1];
+ if (!!epSquare)
+ {
+ for (let epsq of epSquare)
+ {
+ // TODO: some redundant checks
+ if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
+ {
+ var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
+ // WARNING: the captured pawn may be diagonally behind us,
+ // if it's a 3-squares jump and we take on 1st passing square
+ const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
+ enpassantMove.vanish.push({
+ x: px,
+ y: epsq.y,
+ p: 'p',
+ c: this.getColor(px,epsq.y)
+ });
+ moves.push(enpassantMove);
+ }
+ }
+ }
+
+ return moves;
+ }
+
+ // TODO: different castle?
+
+ getPotentialMarshallMoves(sq)
+ {
+ return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
+ this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
+ }
+
+ getPotentialCardinalMoves(sq)
+ {
+ return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
+ this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
+ }
+
+ isAttacked(sq, colors)
+ {
+ return super.isAttacked(sq, colors)
+ || this.isAttackedByMarshall(sq, colors)
+ || this.isAttackedByCardinal(sq, colors);
+ }
+
+ isAttackedByMarshall(sq, colors)
+ {
+ return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
+ || this.isAttackedBySlideNJump(
+ sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
+ }
+
+ isAttackedByCardinal(sq, colors)
+ {
+ return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
+ || this.isAttackedBySlideNJump(
+ sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
+ }
+
+ updateVariables(move)
+ {
+ super.updateVariables(move);
+ if (move.vanish.length == 2 && move.appear.length == 1)
+ {
+ // Capture: update this.captured
+ this.captured[move.vanish[1].c][move.vanish[1].p]++;
+ }
+ if (move.vanish[0].p != move.appear[0].p)
+ {
+ // Promotion: update this.captured
+ this.captured[move.vanish[0].c][move.appear[0].p]--;
+ }
+ }
+
+ unupdateVariables(move)
+ {
+ super.unupdateVariables(move);
+ if (move.vanish.length == 2 && move.appear.length == 1)
+ this.captured[move.vanish[1].c][move.vanish[1].p]--;
+ if (move.vanish[0].p != move.appear[0].p)
+ this.captured[move.vanish[0].c][move.appear[0].p]++;
+ }
+
+ static get VALUES()
+ {
+ return Object.assign(
+ ChessRules.VALUES,
+ {'c': 5, 'm': 7} //experimental
+ );
+ }
+
+ static get SEARCH_DEPTH() { return 2; }
+
+ // TODO: this function could be generalized and shared better (how ?!...)
+ static GenRandInitFen()
+ {
+ let pieces = { "w": new Array(10), "b": new Array(10) };
+ // Shuffle pieces on first and last rank
+ for (let c of ["w","b"])
+ {
+ let positions = ArrayFun.range(10);
+
+ // Get random squares for bishops
+ let randIndex = 2 * randInt(5);
+ let bishop1Pos = positions[randIndex];
+ // The second bishop must be on a square of different color
+ let randIndex_tmp = 2 * randInt(5) + 1;
+ let bishop2Pos = positions[randIndex_tmp];
+ // Remove chosen squares
+ positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+ positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+ // Get random squares for knights
+ randIndex = randInt(8);
+ let knight1Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+ randIndex = randInt(7);
+ let knight2Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // Get random square for queen
+ randIndex = randInt(6);
+ let queenPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // ...random square for marshall
+ randIndex = randInt(5);
+ let marshallPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // ...random square for cardinal
+ randIndex = randInt(4);
+ let cardinalPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // Rooks and king positions are now fixed, because of the ordering rook-king-rook
+ let rook1Pos = positions[0];
+ let kingPos = positions[1];
+ let rook2Pos = positions[2];
+
+ // Finally put the shuffled pieces in the board array
+ pieces[c][rook1Pos] = 'r';
+ pieces[c][knight1Pos] = 'n';
+ pieces[c][bishop1Pos] = 'b';
+ pieces[c][queenPos] = 'q';
+ pieces[c][marshallPos] = 'm';
+ pieces[c][cardinalPos] = 'c';
+ pieces[c][kingPos] = 'k';
+ pieces[c][bishop2Pos] = 'b';
+ pieces[c][knight2Pos] = 'n';
+ pieces[c][rook2Pos] = 'r';
+ }
+ return pieces["b"].join("") +
+ "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
+ pieces["w"].join("").toUpperCase() +
+ " w 0 1111 - 00000000000000";
+ }
}
export const VariantRules = class LosersRules extends ChessRules
{
- static get HasFlags() { return false; }
-
- getPotentialPawnMoves([x,y])
- {
- let moves = super.getPotentialPawnMoves([x,y]);
-
- // Complete with promotion(s) into king, if possible
- const color = this.turn;
- const shift = (color == "w" ? -1 : 1);
- const lastRank = (color == "w" ? 0 : V.size.x-1);
- if (x+shift == lastRank)
- {
- // Normal move
- if (this.board[x+shift][y] == V.EMPTY)
- moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
- // Captures
- if (y>0 && this.canTake([x,y], [x+shift,y-1])
- && this.board[x+shift][y-1] != V.EMPTY)
- {
- moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
- }
- if (y<V.size.y-1 && this.canTake([x,y], [x+shift,y+1])
- && this.board[x+shift][y+1] != V.EMPTY)
- {
- moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
- }
- }
-
- return moves;
- }
-
- getPotentialKingMoves(sq)
- {
- // No castle:
- return this.getSlideNJumpMoves(sq,
- V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
- }
-
- // Stop at the first capture found (if any)
- atLeastOneCapture()
- {
- const color = this.turn;
- const oppCol = V.GetOppCol(color);
- for (let i=0; i<V.size.x; i++)
- {
- for (let j=0; j<V.size.y; j++)
- {
- if (this.board[i][j] != V.EMPTY && this.getColor(i,j) != oppCol)
- {
- const moves = this.getPotentialMovesFrom([i,j]);
- if (moves.length > 0)
- {
- for (let k=0; k<moves.length; k++)
- {
- if (moves[k].vanish.length==2 && this.filterValid([moves[k]]).length > 0)
- return true;
- }
- }
- }
- }
- }
- return false;
- }
-
- // Trim all non-capturing moves
- static KeepCaptures(moves)
- {
- return moves.filter(m => { return m.vanish.length == 2; });
- }
-
- getPossibleMovesFrom(sq)
- {
- let moves = this.filterValid( this.getPotentialMovesFrom(sq) );
- // This is called from interface: we need to know if a capture is possible
- if (this.atLeastOneCapture())
- moves = V.KeepCaptures(moves);
- return moves;
- }
-
- getAllValidMoves()
- {
- let moves = super.getAllValidMoves();
- if (moves.some(m => { return m.vanish.length == 2; }))
- moves = V.KeepCaptures(moves);
- return moves;
- }
-
- underCheck(color)
- {
- return false; //No notion of check
- }
-
- getCheckSquares(move)
- {
- return [];
- }
-
- // No variables update because no royal king + no castling
- updateVariables(move) { }
- unupdateVariables(move) { }
+ static get HasFlags() { return false; }
+
+ getPotentialPawnMoves([x,y])
+ {
+ let moves = super.getPotentialPawnMoves([x,y]);
+
+ // Complete with promotion(s) into king, if possible
+ const color = this.turn;
+ const shift = (color == "w" ? -1 : 1);
+ const lastRank = (color == "w" ? 0 : V.size.x-1);
+ if (x+shift == lastRank)
+ {
+ // Normal move
+ if (this.board[x+shift][y] == V.EMPTY)
+ moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
+ // Captures
+ if (y>0 && this.canTake([x,y], [x+shift,y-1])
+ && this.board[x+shift][y-1] != V.EMPTY)
+ {
+ moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
+ }
+ if (y<V.size.y-1 && this.canTake([x,y], [x+shift,y+1])
+ && this.board[x+shift][y+1] != V.EMPTY)
+ {
+ moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
+ }
+ }
+
+ return moves;
+ }
+
+ getPotentialKingMoves(sq)
+ {
+ // No castle:
+ return this.getSlideNJumpMoves(sq,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+ }
+
+ // Stop at the first capture found (if any)
+ atLeastOneCapture()
+ {
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ for (let i=0; i<V.size.x; i++)
+ {
+ for (let j=0; j<V.size.y; j++)
+ {
+ if (this.board[i][j] != V.EMPTY && this.getColor(i,j) != oppCol)
+ {
+ const moves = this.getPotentialMovesFrom([i,j]);
+ if (moves.length > 0)
+ {
+ for (let k=0; k<moves.length; k++)
+ {
+ if (moves[k].vanish.length==2 && this.filterValid([moves[k]]).length > 0)
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ // Trim all non-capturing moves
+ static KeepCaptures(moves)
+ {
+ return moves.filter(m => { return m.vanish.length == 2; });
+ }
+
+ getPossibleMovesFrom(sq)
+ {
+ let moves = this.filterValid( this.getPotentialMovesFrom(sq) );
+ // This is called from interface: we need to know if a capture is possible
+ if (this.atLeastOneCapture())
+ moves = V.KeepCaptures(moves);
+ return moves;
+ }
+
+ getAllValidMoves()
+ {
+ let moves = super.getAllValidMoves();
+ if (moves.some(m => { return m.vanish.length == 2; }))
+ moves = V.KeepCaptures(moves);
+ return moves;
+ }
+
+ underCheck(color)
+ {
+ return false; //No notion of check
+ }
+
+ getCheckSquares(move)
+ {
+ return [];
+ }
+
+ // No variables update because no royal king + no castling
+ updateVariables(move) { }
+ unupdateVariables(move) { }
getCurrentScore()
{
return "*";
// No valid move: the side who cannot move wins
- return (this.turn == "w" ? "1-0" : "0-1");
- }
-
- static get VALUES()
- {
- // Experimental...
- return {
- 'p': 1,
- 'r': 7,
- 'n': 3,
- 'b': 3,
- 'q': 5,
- 'k': 5
- };
- }
-
- static get SEARCH_DEPTH() { return 4; }
-
- evalPosition()
- {
- return - super.evalPosition(); //better with less material
- }
-
- static GenRandInitFen()
- {
- let pieces = { "w": new Array(8), "b": new Array(8) };
- // Shuffle pieces on first and last rank
- for (let c of ["w","b"])
- {
- let positions = ArrayFun.range(8);
-
- // Get random squares for bishops
- let randIndex = 2 * randInt(4);
- let bishop1Pos = positions[randIndex];
- // The second bishop must be on a square of different color
- let randIndex_tmp = 2 * randInt(4) + 1;
- let bishop2Pos = positions[randIndex_tmp];
- // Remove chosen squares
- positions.splice(Math.max(randIndex,randIndex_tmp), 1);
- positions.splice(Math.min(randIndex,randIndex_tmp), 1);
-
- // Get random squares for knights
- randIndex = randInt(6);
- let knight1Pos = positions[randIndex];
- positions.splice(randIndex, 1);
- randIndex = randInt(5);
- let knight2Pos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- // Get random square for queen
- randIndex = randInt(4);
- let queenPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- // Random square for king (no castle)
- randIndex = randInt(3);
- let kingPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- // Rooks positions are now fixed
- let rook1Pos = positions[0];
- let rook2Pos = positions[1];
-
- // Finally put the shuffled pieces in the board array
- pieces[c][rook1Pos] = 'r';
- pieces[c][knight1Pos] = 'n';
- pieces[c][bishop1Pos] = 'b';
- pieces[c][queenPos] = 'q';
- pieces[c][kingPos] = 'k';
- pieces[c][bishop2Pos] = 'b';
- pieces[c][knight2Pos] = 'n';
- pieces[c][rook2Pos] = 'r';
- }
- return pieces["b"].join("") +
- "/pppppppp/8/8/8/8/PPPPPPPP/" +
- pieces["w"].join("").toUpperCase() +
- " w 0 -"; //en-passant allowed, but no flags
- }
+ return (this.turn == "w" ? "1-0" : "0-1");
+ }
+
+ static get VALUES()
+ {
+ // Experimental...
+ return {
+ 'p': 1,
+ 'r': 7,
+ 'n': 3,
+ 'b': 3,
+ 'q': 5,
+ 'k': 5
+ };
+ }
+
+ static get SEARCH_DEPTH() { return 4; }
+
+ evalPosition()
+ {
+ return - super.evalPosition(); //better with less material
+ }
+
+ static GenRandInitFen()
+ {
+ let pieces = { "w": new Array(8), "b": new Array(8) };
+ // Shuffle pieces on first and last rank
+ for (let c of ["w","b"])
+ {
+ let positions = ArrayFun.range(8);
+
+ // Get random squares for bishops
+ let randIndex = 2 * randInt(4);
+ let bishop1Pos = positions[randIndex];
+ // The second bishop must be on a square of different color
+ let randIndex_tmp = 2 * randInt(4) + 1;
+ let bishop2Pos = positions[randIndex_tmp];
+ // Remove chosen squares
+ positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+ positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+ // Get random squares for knights
+ randIndex = randInt(6);
+ let knight1Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+ randIndex = randInt(5);
+ let knight2Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // Get random square for queen
+ randIndex = randInt(4);
+ let queenPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // Random square for king (no castle)
+ randIndex = randInt(3);
+ let kingPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // Rooks positions are now fixed
+ let rook1Pos = positions[0];
+ let rook2Pos = positions[1];
+
+ // Finally put the shuffled pieces in the board array
+ pieces[c][rook1Pos] = 'r';
+ pieces[c][knight1Pos] = 'n';
+ pieces[c][bishop1Pos] = 'b';
+ pieces[c][queenPos] = 'q';
+ pieces[c][kingPos] = 'k';
+ pieces[c][bishop2Pos] = 'b';
+ pieces[c][knight2Pos] = 'n';
+ pieces[c][rook2Pos] = 'r';
+ }
+ return pieces["b"].join("") +
+ "/pppppppp/8/8/8/8/PPPPPPPP/" +
+ pieces["w"].join("").toUpperCase() +
+ " w 0 -"; //en-passant allowed, but no flags
+ }
}
export const VariantRules = class MagneticRules extends ChessRules
{
- static get HasEnpassant() { return false; }
+ static get HasEnpassant() { return false; }
- getPotentialMovesFrom([x,y])
- {
- let standardMoves = super.getPotentialMovesFrom([x,y]);
- let moves = [];
- standardMoves.forEach(m => {
- let newMove_s = this.applyMagneticLaws(m);
- if (newMove_s.length == 1)
- moves.push(newMove_s[0]);
- else //promotion
- moves = moves.concat(newMove_s);
- });
- return moves;
- }
+ getPotentialMovesFrom([x,y])
+ {
+ let standardMoves = super.getPotentialMovesFrom([x,y]);
+ let moves = [];
+ standardMoves.forEach(m => {
+ let newMove_s = this.applyMagneticLaws(m);
+ if (newMove_s.length == 1)
+ moves.push(newMove_s[0]);
+ else //promotion
+ moves = moves.concat(newMove_s);
+ });
+ return moves;
+ }
- // Complete a move with magnetic actions
- // TODO: job is done multiple times for (normal) promotions.
- applyMagneticLaws(move)
- {
- if (move.appear[0].p == V.KING && move.appear.length==1)
- return [move]; //kings are not charged
- const aIdx = (move.appear[0].p != V.KING ? 0 : 1); //if castling, rook is charged
- const [x,y] = [move.appear[aIdx].x, move.appear[aIdx].y];
- const color = this.turn;
- const lastRank = (color=="w" ? 0 : 7);
- const standardMove = JSON.parse(JSON.stringify(move));
- this.play(standardMove);
- for (let step of [[-1,0],[1,0],[0,-1],[0,1]])
- {
- let [i,j] = [x+step[0],y+step[1]];
- while (V.OnBoard(i,j))
- {
- if (this.board[i][j] != V.EMPTY)
- {
- // Found something. Same color or not?
- if (this.getColor(i,j) != color)
- {
- // Attraction
- if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2) && this.getPiece(i,j) != V.KING)
- {
- move.vanish.push(
- new PiPo({
- p:this.getPiece(i,j),
- c:this.getColor(i,j),
- x:i,
- y:j
- })
- );
- move.appear.push(
- new PiPo({
- p:this.getPiece(i,j),
- c:this.getColor(i,j),
- x:x+step[0],
- y:y+step[1]
- })
- );
- }
- }
- else
- {
- // Repulsion
- if (this.getPiece(i,j) != V.KING)
- {
- // Push it until we meet an obstacle or edge of the board
- let [ii,jj] = [i+step[0],j+step[1]];
- while (V.OnBoard(ii,jj))
- {
- if (this.board[ii][jj] != V.EMPTY)
- break;
- ii += step[0];
- jj += step[1];
- }
- ii -= step[0];
- jj -= step[1];
- if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1)
- {
- move.vanish.push(
- new PiPo({
- p:this.getPiece(i,j),
- c:this.getColor(i,j),
- x:i,
- y:j
- })
- );
- move.appear.push(
- new PiPo({
- p:this.getPiece(i,j),
- c:this.getColor(i,j),
- x:ii,
- y:jj
- })
- );
- }
- }
- }
- break;
- }
- i += step[0];
- j += step[1];
- }
- }
- this.undo(standardMove);
- let moves = [];
- // Scan move for pawn (max 1) on 8th rank
- for (let i=1; i<move.appear.length; i++)
- {
- if (move.appear[i].p==V.PAWN && move.appear[i].c==color
- && move.appear[i].x==lastRank)
- {
- move.appear[i].p = V.ROOK;
- moves.push(move);
- for (let piece of [V.KNIGHT, V.BISHOP, V.QUEEN])
- {
- let cmove = JSON.parse(JSON.stringify(move));
- cmove.appear[i].p = piece;
- moves.push(cmove);
- }
- // Swap appear[i] and appear[0] for moves presentation (TODO: this is awkward)
- moves.forEach(m => {
- let tmp = m.appear[0];
- m.appear[0] = m.appear[i];
- m.appear[i] = tmp;
- });
- break;
- }
- }
- if (moves.length == 0) //no pawn on 8th rank
- moves.push(move);
- return moves;
- }
+ // Complete a move with magnetic actions
+ // TODO: job is done multiple times for (normal) promotions.
+ applyMagneticLaws(move)
+ {
+ if (move.appear[0].p == V.KING && move.appear.length==1)
+ return [move]; //kings are not charged
+ const aIdx = (move.appear[0].p != V.KING ? 0 : 1); //if castling, rook is charged
+ const [x,y] = [move.appear[aIdx].x, move.appear[aIdx].y];
+ const color = this.turn;
+ const lastRank = (color=="w" ? 0 : 7);
+ const standardMove = JSON.parse(JSON.stringify(move));
+ this.play(standardMove);
+ for (let step of [[-1,0],[1,0],[0,-1],[0,1]])
+ {
+ let [i,j] = [x+step[0],y+step[1]];
+ while (V.OnBoard(i,j))
+ {
+ if (this.board[i][j] != V.EMPTY)
+ {
+ // Found something. Same color or not?
+ if (this.getColor(i,j) != color)
+ {
+ // Attraction
+ if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2) && this.getPiece(i,j) != V.KING)
+ {
+ move.vanish.push(
+ new PiPo({
+ p:this.getPiece(i,j),
+ c:this.getColor(i,j),
+ x:i,
+ y:j
+ })
+ );
+ move.appear.push(
+ new PiPo({
+ p:this.getPiece(i,j),
+ c:this.getColor(i,j),
+ x:x+step[0],
+ y:y+step[1]
+ })
+ );
+ }
+ }
+ else
+ {
+ // Repulsion
+ if (this.getPiece(i,j) != V.KING)
+ {
+ // Push it until we meet an obstacle or edge of the board
+ let [ii,jj] = [i+step[0],j+step[1]];
+ while (V.OnBoard(ii,jj))
+ {
+ if (this.board[ii][jj] != V.EMPTY)
+ break;
+ ii += step[0];
+ jj += step[1];
+ }
+ ii -= step[0];
+ jj -= step[1];
+ if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1)
+ {
+ move.vanish.push(
+ new PiPo({
+ p:this.getPiece(i,j),
+ c:this.getColor(i,j),
+ x:i,
+ y:j
+ })
+ );
+ move.appear.push(
+ new PiPo({
+ p:this.getPiece(i,j),
+ c:this.getColor(i,j),
+ x:ii,
+ y:jj
+ })
+ );
+ }
+ }
+ }
+ break;
+ }
+ i += step[0];
+ j += step[1];
+ }
+ }
+ this.undo(standardMove);
+ let moves = [];
+ // Scan move for pawn (max 1) on 8th rank
+ for (let i=1; i<move.appear.length; i++)
+ {
+ if (move.appear[i].p==V.PAWN && move.appear[i].c==color
+ && move.appear[i].x==lastRank)
+ {
+ move.appear[i].p = V.ROOK;
+ moves.push(move);
+ for (let piece of [V.KNIGHT, V.BISHOP, V.QUEEN])
+ {
+ let cmove = JSON.parse(JSON.stringify(move));
+ cmove.appear[i].p = piece;
+ moves.push(cmove);
+ }
+ // Swap appear[i] and appear[0] for moves presentation (TODO: this is awkward)
+ moves.forEach(m => {
+ let tmp = m.appear[0];
+ m.appear[0] = m.appear[i];
+ m.appear[i] = tmp;
+ });
+ break;
+ }
+ }
+ if (moves.length == 0) //no pawn on 8th rank
+ moves.push(move);
+ return moves;
+ }
- atLeastOneMove()
- {
- if (this.kingPos[this.turn][0] < 0)
- return false;
- return true; //TODO: is it right?
- }
+ atLeastOneMove()
+ {
+ if (this.kingPos[this.turn][0] < 0)
+ return false;
+ return true; //TODO: is it right?
+ }
- underCheck(color)
- {
- return false; //there is no check
- }
+ underCheck(color)
+ {
+ return false; //there is no check
+ }
- getCheckSquares(move)
- {
- return [];
- }
+ getCheckSquares(move)
+ {
+ return [];
+ }
- updateVariables(move)
- {
- super.updateVariables(move);
- const c = move.vanish[0].c;
- if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
- {
- // We took opponent king !
- const oppCol = V.GetOppCol(c);
- this.kingPos[oppCol] = [-1,-1];
- this.castleFlags[oppCol] = [false,false];
- }
- // Did we magnetically move our (init) rooks or opponents' ones ?
- const firstRank = (c == "w" ? 7 : 0);
- const oppFirstRank = 7 - firstRank;
- const oppCol = V.GetOppCol(c);
- move.vanish.forEach(psq => {
- if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
- this.castleFlags[c][psq.y==this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
- else if (psq.x == oppFirstRank && this.INIT_COL_ROOK[oppCol].includes(psq.y))
- this.castleFlags[oppCol][psq.y==this.INIT_COL_ROOK[oppCol][0] ? 0 : 1] = false;
- });
- }
+ updateVariables(move)
+ {
+ super.updateVariables(move);
+ const c = move.vanish[0].c;
+ if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
+ {
+ // We took opponent king !
+ const oppCol = V.GetOppCol(c);
+ this.kingPos[oppCol] = [-1,-1];
+ this.castleFlags[oppCol] = [false,false];
+ }
+ // Did we magnetically move our (init) rooks or opponents' ones ?
+ const firstRank = (c == "w" ? 7 : 0);
+ const oppFirstRank = 7 - firstRank;
+ const oppCol = V.GetOppCol(c);
+ move.vanish.forEach(psq => {
+ if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
+ this.castleFlags[c][psq.y==this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
+ else if (psq.x == oppFirstRank && this.INIT_COL_ROOK[oppCol].includes(psq.y))
+ this.castleFlags[oppCol][psq.y==this.INIT_COL_ROOK[oppCol][0] ? 0 : 1] = false;
+ });
+ }
- unupdateVariables(move)
- {
- super.unupdateVariables(move);
- const c = move.vanish[0].c;
- const oppCol = V.GetOppCol(c);
- if (this.kingPos[oppCol][0] < 0)
- {
- // Last move took opponent's king
- for (let psq of move.vanish)
- {
- if (psq.p == 'k')
- {
- this.kingPos[oppCol] = [psq.x, psq.y];
- break;
- }
- }
- }
- }
+ unupdateVariables(move)
+ {
+ super.unupdateVariables(move);
+ const c = move.vanish[0].c;
+ const oppCol = V.GetOppCol(c);
+ if (this.kingPos[oppCol][0] < 0)
+ {
+ // Last move took opponent's king
+ for (let psq of move.vanish)
+ {
+ if (psq.p == 'k')
+ {
+ this.kingPos[oppCol] = [psq.x, psq.y];
+ break;
+ }
+ }
+ }
+ }
- getCurrentScore()
- {
- const color = this.turn;
- const kp = this.kingPos[color];
- if (kp[0] < 0) //king disappeared
- return (color == "w" ? "0-1" : "1-0");
+ getCurrentScore()
+ {
+ const color = this.turn;
+ const kp = this.kingPos[color];
+ if (kp[0] < 0) //king disappeared
+ return (color == "w" ? "0-1" : "1-0");
if (this.atLeastOneMove()) // game not over
return "*";
return "1/2"; //no moves but kings still there
- }
+ }
- static get THRESHOLD_MATE()
- {
- return 500; //checkmates evals may be slightly below 1000
- }
+ static get THRESHOLD_MATE()
+ {
+ return 500; //checkmates evals may be slightly below 1000
+ }
}
export const VariantRules = class MarseilleRules extends ChessRules
{
- static IsGoodEnpassant(enpassant)
- {
- if (enpassant != "-")
- {
- const squares = enpassant.split(",");
- if (squares.length > 2)
- return false;
- for (let sq of squares)
- {
- const ep = V.SquareToCoords(sq);
- if (isNaN(ep.x) || !V.OnBoard(ep))
- return false;
- }
- }
- return true;
- }
+ static IsGoodEnpassant(enpassant)
+ {
+ if (enpassant != "-")
+ {
+ const squares = enpassant.split(",");
+ if (squares.length > 2)
+ return false;
+ for (let sq of squares)
+ {
+ const ep = V.SquareToCoords(sq);
+ if (isNaN(ep.x) || !V.OnBoard(ep))
+ return false;
+ }
+ }
+ return true;
+ }
- getTurnFen()
- {
- return this.turn + this.subTurn;
- }
+ getTurnFen()
+ {
+ return this.turn + this.subTurn;
+ }
- // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
- getEnpassantFen()
- {
- const L = this.epSquares.length;
- if (this.epSquares[L-1].every(epsq => epsq === undefined))
- return "-"; //no en-passant
- let res = "";
- this.epSquares[L-1].forEach(epsq => {
- if (!!epsq)
- res += V.CoordsToSquare(epsq) + ",";
- });
- return res.slice(0,-1); //remove last comma
- }
+ // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
+ getEnpassantFen()
+ {
+ const L = this.epSquares.length;
+ if (this.epSquares[L-1].every(epsq => epsq === undefined))
+ return "-"; //no en-passant
+ let res = "";
+ this.epSquares[L-1].forEach(epsq => {
+ if (!!epsq)
+ res += V.CoordsToSquare(epsq) + ",";
+ });
+ return res.slice(0,-1); //remove last comma
+ }
- setOtherVariables(fen)
- {
- const parsedFen = V.ParseFen(fen);
- this.setFlags(parsedFen.flags);
- if (parsedFen.enpassant == "-")
- this.epSquares = [ [undefined] ];
- else
- {
- let res = [];
- const squares = parsedFen.enpassant.split(",");
- for (let sq of squares)
- res.push(V.SquareToCoords(sq));
- this.epSquares = [ res ];
- }
- this.scanKingsRooks(fen);
- // Extract subTurn from turn indicator: "w" (first move), or
- // "w1" or "w2" white subturn 1 or 2, and same for black
- const fullTurn = V.ParseFen(fen).turn;
- this.turn = fullTurn[0];
- this.subTurn = (fullTurn[1] || 0); //"w0" = special code for first move in game
- }
+ setOtherVariables(fen)
+ {
+ const parsedFen = V.ParseFen(fen);
+ this.setFlags(parsedFen.flags);
+ if (parsedFen.enpassant == "-")
+ this.epSquares = [ [undefined] ];
+ else
+ {
+ let res = [];
+ const squares = parsedFen.enpassant.split(",");
+ for (let sq of squares)
+ res.push(V.SquareToCoords(sq));
+ this.epSquares = [ res ];
+ }
+ this.scanKingsRooks(fen);
+ // Extract subTurn from turn indicator: "w" (first move), or
+ // "w1" or "w2" white subturn 1 or 2, and same for black
+ const fullTurn = V.ParseFen(fen).turn;
+ this.turn = fullTurn[0];
+ this.subTurn = (fullTurn[1] || 0); //"w0" = special code for first move in game
+ }
- getPotentialPawnMoves([x,y])
- {
- const color = this.turn;
- let moves = [];
- const [sizeX,sizeY] = [V.size.x,V.size.y];
- const shiftX = (color == "w" ? -1 : 1);
- const firstRank = (color == 'w' ? sizeX-1 : 0);
- const startRank = (color == "w" ? sizeX-2 : 1);
- const lastRank = (color == "w" ? 0 : sizeX-1);
- const finalPieces = x + shiftX == lastRank
- ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
- : [V.PAWN];
+ getPotentialPawnMoves([x,y])
+ {
+ const color = this.turn;
+ let moves = [];
+ const [sizeX,sizeY] = [V.size.x,V.size.y];
+ const shiftX = (color == "w" ? -1 : 1);
+ const firstRank = (color == 'w' ? sizeX-1 : 0);
+ const startRank = (color == "w" ? sizeX-2 : 1);
+ const lastRank = (color == "w" ? 0 : sizeX-1);
+ const finalPieces = x + shiftX == lastRank
+ ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+ : [V.PAWN];
- // One square forward
- if (this.board[x+shiftX][y] == V.EMPTY)
- {
- for (let piece of finalPieces)
- {
- moves.push(this.getBasicMove([x,y], [x+shiftX,y],
- {c:color,p:piece}));
- }
- // Next condition because pawns on 1st rank can generally jump
- if ([startRank,firstRank].includes(x)
- && this.board[x+2*shiftX][y] == V.EMPTY)
- {
- // Two squares jump
- moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
- }
- }
- // Captures
- for (let shiftY of [-1,1])
- {
- if (y + shiftY >= 0 && y + shiftY < sizeY
- && this.board[x+shiftX][y+shiftY] != V.EMPTY
- && this.canTake([x,y], [x+shiftX,y+shiftY]))
- {
- for (let piece of finalPieces)
- {
- moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
- {c:color,p:piece}));
- }
- }
- }
+ // One square forward
+ if (this.board[x+shiftX][y] == V.EMPTY)
+ {
+ for (let piece of finalPieces)
+ {
+ moves.push(this.getBasicMove([x,y], [x+shiftX,y],
+ {c:color,p:piece}));
+ }
+ // Next condition because pawns on 1st rank can generally jump
+ if ([startRank,firstRank].includes(x)
+ && this.board[x+2*shiftX][y] == V.EMPTY)
+ {
+ // Two squares jump
+ moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+ }
+ }
+ // Captures
+ for (let shiftY of [-1,1])
+ {
+ if (y + shiftY >= 0 && y + shiftY < sizeY
+ && this.board[x+shiftX][y+shiftY] != V.EMPTY
+ && this.canTake([x,y], [x+shiftX,y+shiftY]))
+ {
+ for (let piece of finalPieces)
+ {
+ moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+ {c:color,p:piece}));
+ }
+ }
+ }
- // En passant: always OK if subturn 1,
- // OK on subturn 2 only if enPassant was played at subturn 1
- // (and if there are two e.p. squares available).
- const Lep = this.epSquares.length;
- const epSquares = this.epSquares[Lep-1]; //always at least one element
- let epSqs = [];
- epSquares.forEach(sq => {
- if (!!sq)
- epSqs.push(sq);
- });
- if (epSqs.length == 0)
- return moves;
- const oppCol = V.GetOppCol(color);
- for (let sq of epSqs)
- {
- if (this.subTurn == 1 || (epSqs.length == 2 &&
- // Was this en-passant capture already played at subturn 1 ?
- // (Or maybe the opponent filled the en-passant square with a piece)
- this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY))
- {
- if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1
- // Add condition "enemy pawn must be present"
- && this.getPiece(x,sq.y) == V.PAWN && this.getColor(x,sq.y) == oppCol)
- {
- let epMove = this.getBasicMove([x,y], [sq.x,sq.y]);
- epMove.vanish.push({
- x: x,
- y: sq.y,
- p: 'p',
- c: oppCol
- });
- moves.push(epMove);
- }
- }
- }
+ // En passant: always OK if subturn 1,
+ // OK on subturn 2 only if enPassant was played at subturn 1
+ // (and if there are two e.p. squares available).
+ const Lep = this.epSquares.length;
+ const epSquares = this.epSquares[Lep-1]; //always at least one element
+ let epSqs = [];
+ epSquares.forEach(sq => {
+ if (!!sq)
+ epSqs.push(sq);
+ });
+ if (epSqs.length == 0)
+ return moves;
+ const oppCol = V.GetOppCol(color);
+ for (let sq of epSqs)
+ {
+ if (this.subTurn == 1 || (epSqs.length == 2 &&
+ // Was this en-passant capture already played at subturn 1 ?
+ // (Or maybe the opponent filled the en-passant square with a piece)
+ this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY))
+ {
+ if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1
+ // Add condition "enemy pawn must be present"
+ && this.getPiece(x,sq.y) == V.PAWN && this.getColor(x,sq.y) == oppCol)
+ {
+ let epMove = this.getBasicMove([x,y], [sq.x,sq.y]);
+ epMove.vanish.push({
+ x: x,
+ y: sq.y,
+ p: 'p',
+ c: oppCol
+ });
+ moves.push(epMove);
+ }
+ }
+ }
- return moves;
- }
+ return moves;
+ }
- play(move)
- {
- move.flags = JSON.stringify(this.aggregateFlags());
- move.turn = this.turn + this.subTurn;
- V.PlayOnBoard(this.board, move);
- const epSq = this.getEpSquare(move);
- if (this.subTurn == 0) //first move in game
- {
- this.turn = "b";
+ play(move)
+ {
+ move.flags = JSON.stringify(this.aggregateFlags());
+ move.turn = this.turn + this.subTurn;
+ V.PlayOnBoard(this.board, move);
+ const epSq = this.getEpSquare(move);
+ if (this.subTurn == 0) //first move in game
+ {
+ this.turn = "b";
this.subTurn = 1;
- this.epSquares.push([epSq]);
- }
- // Does this move give check on subturn 1? If yes, skip subturn 2
- else if (this.subTurn==1 && this.underCheck(V.GetOppCol(this.turn)))
- {
- this.turn = V.GetOppCol(this.turn);
- this.epSquares.push([epSq]);
- move.checkOnSubturn1 = true;
- }
- else
- {
- if (this.subTurn == 2)
- {
- this.turn = V.GetOppCol(this.turn);
- let lastEpsq = this.epSquares[this.epSquares.length-1];
- lastEpsq.push(epSq);
- }
- else
- this.epSquares.push([epSq]);
- this.subTurn = 3 - this.subTurn;
- }
- this.updateVariables(move);
- }
+ this.epSquares.push([epSq]);
+ }
+ // Does this move give check on subturn 1? If yes, skip subturn 2
+ else if (this.subTurn==1 && this.underCheck(V.GetOppCol(this.turn)))
+ {
+ this.turn = V.GetOppCol(this.turn);
+ this.epSquares.push([epSq]);
+ move.checkOnSubturn1 = true;
+ }
+ else
+ {
+ if (this.subTurn == 2)
+ {
+ this.turn = V.GetOppCol(this.turn);
+ let lastEpsq = this.epSquares[this.epSquares.length-1];
+ lastEpsq.push(epSq);
+ }
+ else
+ this.epSquares.push([epSq]);
+ this.subTurn = 3 - this.subTurn;
+ }
+ this.updateVariables(move);
+ }
- undo(move)
- {
- this.disaggregateFlags(JSON.parse(move.flags));
- V.UndoOnBoard(this.board, move);
- if (move.turn[1] == '0' || move.checkOnSubturn1 || this.subTurn == 2)
- this.epSquares.pop();
- else //this.subTurn == 1
- {
- let lastEpsq = this.epSquares[this.epSquares.length-1];
- lastEpsq.pop();
- }
- this.turn = move.turn[0];
- this.subTurn = parseInt(move.turn[1]);
- this.unupdateVariables(move);
- }
+ undo(move)
+ {
+ this.disaggregateFlags(JSON.parse(move.flags));
+ V.UndoOnBoard(this.board, move);
+ if (move.turn[1] == '0' || move.checkOnSubturn1 || this.subTurn == 2)
+ this.epSquares.pop();
+ else //this.subTurn == 1
+ {
+ let lastEpsq = this.epSquares[this.epSquares.length-1];
+ lastEpsq.pop();
+ }
+ this.turn = move.turn[0];
+ this.subTurn = parseInt(move.turn[1]);
+ this.unupdateVariables(move);
+ }
- // NOTE: GenRandInitFen() is OK,
- // since at first move turn indicator is just "w"
+ // NOTE: GenRandInitFen() is OK,
+ // since at first move turn indicator is just "w"
- static get VALUES()
- {
- return {
- 'p': 1,
- 'r': 5,
- 'n': 3,
- 'b': 3,
- 'q': 7, //slightly less than in orthodox game
- 'k': 1000
- };
- }
+ static get VALUES()
+ {
+ return {
+ 'p': 1,
+ 'r': 5,
+ 'n': 3,
+ 'b': 3,
+ 'q': 7, //slightly less than in orthodox game
+ 'k': 1000
+ };
+ }
- // No alpha-beta here, just adapted min-max at depth 2(+1)
- getComputerMove()
- {
- if (this.subTurn == 2)
- return null; //TODO: imperfect interface setup
+ // No alpha-beta here, just adapted min-max at depth 2(+1)
+ getComputerMove()
+ {
+ if (this.subTurn == 2)
+ return null; //TODO: imperfect interface setup
- const maxeval = V.INFINITY;
- const color = this.turn;
- const oppCol = V.GetOppCol(this.turn);
+ const maxeval = V.INFINITY;
+ const color = this.turn;
+ const oppCol = V.GetOppCol(this.turn);
- // Search best (half) move for opponent turn
- const getBestMoveEval = () => {
- const turnBefore = this.turn + this.subTurn;
- let score = this.getCurrentScore();
- if (score != "*")
- {
- if (score == "1/2")
- return 0;
- return maxeval * (score == "1-0" ? 1 : -1);
- }
- let moves = this.getAllValidMoves();
- let res = (oppCol == "w" ? -maxeval : maxeval);
- for (let m of moves)
- {
- this.play(m);
- score = this.getCurrentScore();
- // Now turn is oppCol,2 if m doesn't give check
- // Otherwise it's color,1. In both cases the next test makes sense
- if (score != "*")
- {
- if (score == "1/2")
- res = (oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0));
- else
- {
- // Found a mate
- this.undo(m);
- return maxeval * (score == "1-0" ? 1 : -1);
- }
- }
- const evalPos = this.evalPosition();
- res = (oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos));
- this.undo(m);
- }
- return res;
- };
+ // Search best (half) move for opponent turn
+ const getBestMoveEval = () => {
+ const turnBefore = this.turn + this.subTurn;
+ let score = this.getCurrentScore();
+ if (score != "*")
+ {
+ if (score == "1/2")
+ return 0;
+ return maxeval * (score == "1-0" ? 1 : -1);
+ }
+ let moves = this.getAllValidMoves();
+ let res = (oppCol == "w" ? -maxeval : maxeval);
+ for (let m of moves)
+ {
+ this.play(m);
+ score = this.getCurrentScore();
+ // Now turn is oppCol,2 if m doesn't give check
+ // Otherwise it's color,1. In both cases the next test makes sense
+ if (score != "*")
+ {
+ if (score == "1/2")
+ res = (oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0));
+ else
+ {
+ // Found a mate
+ this.undo(m);
+ return maxeval * (score == "1-0" ? 1 : -1);
+ }
+ }
+ const evalPos = this.evalPosition();
+ res = (oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos));
+ this.undo(m);
+ }
+ return res;
+ };
- let moves11 = this.getAllValidMoves();
- let doubleMoves = [];
- // Rank moves using a min-max at depth 2
- for (let i=0; i<moves11.length; i++)
- {
- this.play(moves11[i]);
- if (this.turn != color)
- {
- // We gave check with last move: search the best opponent move
- doubleMoves.push({moves:[moves11[i]], eval:getBestMoveEval()});
- }
- else
- {
- let moves12 = this.getAllValidMoves();
- for (let j=0; j<moves12.length; j++)
- {
- this.play(moves12[j]);
- doubleMoves.push({
- moves:[moves11[i],moves12[j]],
- eval:getBestMoveEval()});
- this.undo(moves12[j]);
- }
- }
- this.undo(moves11[i]);
- }
+ let moves11 = this.getAllValidMoves();
+ let doubleMoves = [];
+ // Rank moves using a min-max at depth 2
+ for (let i=0; i<moves11.length; i++)
+ {
+ this.play(moves11[i]);
+ if (this.turn != color)
+ {
+ // We gave check with last move: search the best opponent move
+ doubleMoves.push({moves:[moves11[i]], eval:getBestMoveEval()});
+ }
+ else
+ {
+ let moves12 = this.getAllValidMoves();
+ for (let j=0; j<moves12.length; j++)
+ {
+ this.play(moves12[j]);
+ doubleMoves.push({
+ moves:[moves11[i],moves12[j]],
+ eval:getBestMoveEval()});
+ this.undo(moves12[j]);
+ }
+ }
+ this.undo(moves11[i]);
+ }
- doubleMoves.sort( (a,b) => {
- return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
- let candidates = [0]; //indices of candidates moves
- for (let i=1;
- i<doubleMoves.length && doubleMoves[i].eval == doubleMoves[0].eval;
- i++)
- {
- candidates.push(i);
- }
+ doubleMoves.sort( (a,b) => {
+ return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
+ let candidates = [0]; //indices of candidates moves
+ for (let i=1;
+ i<doubleMoves.length && doubleMoves[i].eval == doubleMoves[0].eval;
+ i++)
+ {
+ candidates.push(i);
+ }
- const selected = doubleMoves[randInt(candidates.length)].moves;
- if (selected.length == 1)
- return selected[0];
- return selected;
- }
+ const selected = doubleMoves[randInt(candidates.length)].moves;
+ if (selected.length == 1)
+ return selected[0];
+ return selected;
+ }
}
export const VariantRules = class UpsidedownRules extends ChessRules
{
- static get HasFlags() { return false; }
+ static get HasFlags() { return false; }
- static get HasEnpassant() { return false; }
+ static get HasEnpassant() { return false; }
- getPotentialKingMoves(sq)
- {
- // No castle
- return this.getSlideNJumpMoves(sq,
- V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
- }
+ getPotentialKingMoves(sq)
+ {
+ // No castle
+ return this.getSlideNJumpMoves(sq,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+ }
- static GenRandInitFen()
- {
- let pieces = { "w": new Array(8), "b": new Array(8) };
- for (let c of ["w","b"])
- {
- let positions = ArrayFun.range(8);
+ static GenRandInitFen()
+ {
+ let pieces = { "w": new Array(8), "b": new Array(8) };
+ for (let c of ["w","b"])
+ {
+ let positions = ArrayFun.range(8);
- let randIndex = randInt(8);
- const kingPos = positions[randIndex];
- positions.splice(randIndex, 1);
+ let randIndex = randInt(8);
+ const kingPos = positions[randIndex];
+ positions.splice(randIndex, 1);
- // At least a knight must be next to the king:
- let knight1Pos = undefined;
- if (kingPos == 0)
- knight1Pos = 1;
- else if (kingPos == V.size.y-1)
- knight1Pos = V.size.y-2;
- else
- knight1Pos = kingPos + (Math.random() < 0.5 ? 1 : -1);
- // Search for knight1Pos index in positions and remove it
- const knight1Index = positions.indexOf(knight1Pos);
- positions.splice(knight1Index, 1);
+ // At least a knight must be next to the king:
+ let knight1Pos = undefined;
+ if (kingPos == 0)
+ knight1Pos = 1;
+ else if (kingPos == V.size.y-1)
+ knight1Pos = V.size.y-2;
+ else
+ knight1Pos = kingPos + (Math.random() < 0.5 ? 1 : -1);
+ // Search for knight1Pos index in positions and remove it
+ const knight1Index = positions.indexOf(knight1Pos);
+ positions.splice(knight1Index, 1);
- // King+knight1 are on two consecutive squares: one light, one dark
- randIndex = 2 * randInt(3);
- const bishop1Pos = positions[randIndex];
- let randIndex_tmp = 2 * randInt(3) + 1;
- const bishop2Pos = positions[randIndex_tmp];
- positions.splice(Math.max(randIndex,randIndex_tmp), 1);
- positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+ // King+knight1 are on two consecutive squares: one light, one dark
+ randIndex = 2 * randInt(3);
+ const bishop1Pos = positions[randIndex];
+ let randIndex_tmp = 2 * randInt(3) + 1;
+ const bishop2Pos = positions[randIndex_tmp];
+ positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+ positions.splice(Math.min(randIndex,randIndex_tmp), 1);
- randIndex = randInt(4);
- const knight2Pos = positions[randIndex];
- positions.splice(randIndex, 1);
+ randIndex = randInt(4);
+ const knight2Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
- randIndex = randInt(3);
- const queenPos = positions[randIndex];
- positions.splice(randIndex, 1);
+ randIndex = randInt(3);
+ const queenPos = positions[randIndex];
+ positions.splice(randIndex, 1);
- const rook1Pos = positions[0];
- const rook2Pos = positions[1];
+ const rook1Pos = positions[0];
+ const rook2Pos = positions[1];
- pieces[c][rook1Pos] = 'r';
- pieces[c][knight1Pos] = 'n';
- pieces[c][bishop1Pos] = 'b';
- pieces[c][queenPos] = 'q';
- pieces[c][kingPos] = 'k';
- pieces[c][bishop2Pos] = 'b';
- pieces[c][knight2Pos] = 'n';
- pieces[c][rook2Pos] = 'r';
- }
- return pieces["w"].join("").toUpperCase() +
- "/PPPPPPPP/8/8/8/8/pppppppp/" +
- pieces["b"].join("") +
- " w 0"; //no castle, no en-passant
- }
+ pieces[c][rook1Pos] = 'r';
+ pieces[c][knight1Pos] = 'n';
+ pieces[c][bishop1Pos] = 'b';
+ pieces[c][queenPos] = 'q';
+ pieces[c][kingPos] = 'k';
+ pieces[c][bishop2Pos] = 'b';
+ pieces[c][knight2Pos] = 'n';
+ pieces[c][rook2Pos] = 'r';
+ }
+ return pieces["w"].join("").toUpperCase() +
+ "/PPPPPPPP/8/8/8/8/pppppppp/" +
+ pieces["b"].join("") +
+ " w 0"; //no castle, no en-passant
+ }
}
export const VariantRules = class WildebeestRules extends ChessRules
{
- static getPpath(b)
- {
- return ([V.CAMEL,V.WILDEBEEST].includes(b[1]) ? "Wildebeest/" : "") + b;
- }
+ static getPpath(b)
+ {
+ return ([V.CAMEL,V.WILDEBEEST].includes(b[1]) ? "Wildebeest/" : "") + b;
+ }
- static get size() { return {x:10,y:11}; }
+ static get size() { return {x:10,y:11}; }
- static get CAMEL() { return 'c'; }
- static get WILDEBEEST() { return 'w'; }
+ static get CAMEL() { return 'c'; }
+ static get WILDEBEEST() { return 'w'; }
- static get PIECES()
- {
- return ChessRules.PIECES.concat([V.CAMEL,V.WILDEBEEST]);
- }
+ static get PIECES()
+ {
+ return ChessRules.PIECES.concat([V.CAMEL,V.WILDEBEEST]);
+ }
- static get steps()
- {
- return Object.assign(
- ChessRules.steps, //add camel moves:
- {'c': [ [-3,-1],[-3,1],[-1,-3],[-1,3],[1,-3],[1,3],[3,-1],[3,1] ]}
- );
- }
+ static get steps()
+ {
+ return Object.assign(
+ ChessRules.steps, //add camel moves:
+ {'c': [ [-3,-1],[-3,1],[-1,-3],[-1,3],[1,-3],[1,3],[3,-1],[3,1] ]}
+ );
+ }
- static IsGoodEnpassant(enpassant)
- {
- if (enpassant != "-")
- {
- const squares = enpassant.split(",");
- if (squares.length > 2)
- return false;
- for (let sq of squares)
- {
- const ep = V.SquareToCoords(sq);
- if (isNaN(ep.x) || !V.OnBoard(ep))
- return false;
- }
- }
- return true;
- }
+ static IsGoodEnpassant(enpassant)
+ {
+ if (enpassant != "-")
+ {
+ const squares = enpassant.split(",");
+ if (squares.length > 2)
+ return false;
+ for (let sq of squares)
+ {
+ const ep = V.SquareToCoords(sq);
+ if (isNaN(ep.x) || !V.OnBoard(ep))
+ return false;
+ }
+ }
+ return true;
+ }
- // There may be 2 enPassant squares (if pawn jump 3 squares)
- getEnpassantFen()
- {
- const L = this.epSquares.length;
- if (!this.epSquares[L-1])
- return "-"; //no en-passant
- let res = "";
- this.epSquares[L-1].forEach(sq => {
- res += V.CoordsToSquare(sq) + ",";
- });
- return res.slice(0,-1); //remove last comma
- }
+ // There may be 2 enPassant squares (if pawn jump 3 squares)
+ getEnpassantFen()
+ {
+ const L = this.epSquares.length;
+ if (!this.epSquares[L-1])
+ return "-"; //no en-passant
+ let res = "";
+ this.epSquares[L-1].forEach(sq => {
+ res += V.CoordsToSquare(sq) + ",";
+ });
+ return res.slice(0,-1); //remove last comma
+ }
- // En-passant after 2-sq or 3-sq jumps
- getEpSquare(moveOrSquare)
- {
- if (!moveOrSquare)
- return undefined;
- if (typeof moveOrSquare === "string")
- {
- const square = moveOrSquare;
- if (square == "-")
- return undefined;
- let res = [];
- square.split(",").forEach(sq => {
- res.push(V.SquareToCoords(sq));
- });
- return res;
- }
- // 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)
- {
- const step = (ex-sx) / Math.abs(ex-sx);
- let res = [{
- x: sx + step,
- y: sy
- }];
- if (sx + 2*step != ex) //3-squares move
- {
- res.push({
- x: sx + 2*step,
- y: sy
- });
- }
- return res;
- }
- return undefined; //default
- }
+ // En-passant after 2-sq or 3-sq jumps
+ getEpSquare(moveOrSquare)
+ {
+ if (!moveOrSquare)
+ return undefined;
+ if (typeof moveOrSquare === "string")
+ {
+ const square = moveOrSquare;
+ if (square == "-")
+ return undefined;
+ let res = [];
+ square.split(",").forEach(sq => {
+ res.push(V.SquareToCoords(sq));
+ });
+ return res;
+ }
+ // 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)
+ {
+ const step = (ex-sx) / Math.abs(ex-sx);
+ let res = [{
+ x: sx + step,
+ y: sy
+ }];
+ if (sx + 2*step != ex) //3-squares move
+ {
+ res.push({
+ x: sx + 2*step,
+ y: sy
+ });
+ }
+ return res;
+ }
+ return undefined; //default
+ }
- getPotentialMovesFrom([x,y])
- {
- switch (this.getPiece(x,y))
- {
- case V.CAMEL:
- return this.getPotentialCamelMoves([x,y]);
- case V.WILDEBEEST:
- return this.getPotentialWildebeestMoves([x,y]);
- default:
- return super.getPotentialMovesFrom([x,y])
- }
- }
+ getPotentialMovesFrom([x,y])
+ {
+ switch (this.getPiece(x,y))
+ {
+ case V.CAMEL:
+ return this.getPotentialCamelMoves([x,y]);
+ case V.WILDEBEEST:
+ return this.getPotentialWildebeestMoves([x,y]);
+ default:
+ return super.getPotentialMovesFrom([x,y])
+ }
+ }
- // Pawns jump 2 or 3 squares, and promote to queen or wildebeest
- getPotentialPawnMoves([x,y])
- {
- const color = this.turn;
- let moves = [];
- const [sizeX,sizeY] = [V.size.x,V.size.y];
- const shiftX = (color == "w" ? -1 : 1);
- const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
- const lastRank = (color == "w" ? 0 : sizeX-1);
- const finalPieces = x + shiftX == lastRank
- ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
- : [V.PAWN];
+ // Pawns jump 2 or 3 squares, and promote to queen or wildebeest
+ getPotentialPawnMoves([x,y])
+ {
+ const color = this.turn;
+ let moves = [];
+ const [sizeX,sizeY] = [V.size.x,V.size.y];
+ const shiftX = (color == "w" ? -1 : 1);
+ const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
+ const lastRank = (color == "w" ? 0 : sizeX-1);
+ const finalPieces = x + shiftX == lastRank
+ ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+ : [V.PAWN];
- if (this.board[x+shiftX][y] == V.EMPTY)
- {
- // One square forward
- for (let piece of finalPieces)
- moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
- if (startRanks.includes(x))
- {
- if (this.board[x+2*shiftX][y] == V.EMPTY)
- {
- // Two squares jump
- moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
- if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
- {
- // Three squares jump
- moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
- }
- }
- }
- }
- // Captures
- for (let shiftY of [-1,1])
- {
- if (y + shiftY >= 0 && y + shiftY < sizeY
- && this.board[x+shiftX][y+shiftY] != V.EMPTY
- && this.canTake([x,y], [x+shiftX,y+shiftY]))
- {
- for (let piece of finalPieces)
- {
- moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
- {c:color,p:piece}));
- }
- }
- }
+ if (this.board[x+shiftX][y] == V.EMPTY)
+ {
+ // One square forward
+ for (let piece of finalPieces)
+ moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
+ if (startRanks.includes(x))
+ {
+ if (this.board[x+2*shiftX][y] == V.EMPTY)
+ {
+ // Two squares jump
+ moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+ if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
+ {
+ // Three squares jump
+ moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
+ }
+ }
+ }
+ }
+ // Captures
+ for (let shiftY of [-1,1])
+ {
+ if (y + shiftY >= 0 && y + shiftY < sizeY
+ && this.board[x+shiftX][y+shiftY] != V.EMPTY
+ && this.canTake([x,y], [x+shiftX,y+shiftY]))
+ {
+ for (let piece of finalPieces)
+ {
+ moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+ {c:color,p:piece}));
+ }
+ }
+ }
- // En passant
- const Lep = this.epSquares.length;
- const epSquare = this.epSquares[Lep-1];
- if (!!epSquare)
- {
- for (let epsq of epSquare)
- {
- // TODO: some redundant checks
- if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
- {
- var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
- // WARNING: the captured pawn may be diagonally behind us,
- // if it's a 3-squares jump and we take on 1st passing square
- const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
- enpassantMove.vanish.push({
- x: px,
- y: epsq.y,
- p: 'p',
- c: this.getColor(px,epsq.y)
- });
- moves.push(enpassantMove);
- }
- }
- }
+ // En passant
+ const Lep = this.epSquares.length;
+ const epSquare = this.epSquares[Lep-1];
+ if (!!epSquare)
+ {
+ for (let epsq of epSquare)
+ {
+ // TODO: some redundant checks
+ if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
+ {
+ var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
+ // WARNING: the captured pawn may be diagonally behind us,
+ // if it's a 3-squares jump and we take on 1st passing square
+ const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
+ enpassantMove.vanish.push({
+ x: px,
+ y: epsq.y,
+ p: 'p',
+ c: this.getColor(px,epsq.y)
+ });
+ moves.push(enpassantMove);
+ }
+ }
+ }
- return moves;
- }
+ return moves;
+ }
- // TODO: wildebeest castle
+ // TODO: wildebeest castle
- getPotentialCamelMoves(sq)
- {
- return this.getSlideNJumpMoves(sq, V.steps[V.CAMEL], "oneStep");
- }
+ getPotentialCamelMoves(sq)
+ {
+ return this.getSlideNJumpMoves(sq, V.steps[V.CAMEL], "oneStep");
+ }
- getPotentialWildebeestMoves(sq)
- {
- return this.getSlideNJumpMoves(
- sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
- }
+ getPotentialWildebeestMoves(sq)
+ {
+ return this.getSlideNJumpMoves(
+ sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
+ }
- isAttacked(sq, colors)
- {
- return super.isAttacked(sq, colors)
- || this.isAttackedByCamel(sq, colors)
- || this.isAttackedByWildebeest(sq, colors);
- }
+ isAttacked(sq, colors)
+ {
+ return super.isAttacked(sq, colors)
+ || this.isAttackedByCamel(sq, colors)
+ || this.isAttackedByWildebeest(sq, colors);
+ }
- isAttackedByCamel(sq, colors)
- {
- return this.isAttackedBySlideNJump(sq, colors,
- V.CAMEL, V.steps[V.CAMEL], "oneStep");
- }
+ isAttackedByCamel(sq, colors)
+ {
+ return this.isAttackedBySlideNJump(sq, colors,
+ V.CAMEL, V.steps[V.CAMEL], "oneStep");
+ }
- isAttackedByWildebeest(sq, colors)
- {
- return this.isAttackedBySlideNJump(sq, colors, V.WILDEBEEST,
- V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
- }
+ isAttackedByWildebeest(sq, colors)
+ {
+ return this.isAttackedBySlideNJump(sq, colors, V.WILDEBEEST,
+ V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
+ }
getCurrentScore()
{
if (this.atLeastOneMove()) // game not over
return "*";
- // No valid move: game is lost (stalemate is a win)
- return (this.turn == "w" ? "0-1" : "1-0");
- }
+ // No valid move: game is lost (stalemate is a win)
+ return (this.turn == "w" ? "0-1" : "1-0");
+ }
- static get VALUES() {
- return Object.assign(
- ChessRules.VALUES,
- {'c': 3, 'w': 7} //experimental
- );
- }
+ static get VALUES() {
+ return Object.assign(
+ ChessRules.VALUES,
+ {'c': 3, 'w': 7} //experimental
+ );
+ }
- static get SEARCH_DEPTH() { return 2; }
+ static get SEARCH_DEPTH() { return 2; }
- static GenRandInitFen()
- {
- let pieces = { "w": new Array(10), "b": new Array(10) };
- for (let c of ["w","b"])
- {
- let positions = ArrayFun.range(11);
+ static GenRandInitFen()
+ {
+ let pieces = { "w": new Array(10), "b": new Array(10) };
+ for (let c of ["w","b"])
+ {
+ let positions = ArrayFun.range(11);
- // Get random squares for bishops + camels (different colors)
- let randIndexes = sample(ArrayFun.range(6), 2)
+ // Get random squares for bishops + camels (different colors)
+ let randIndexes = sample(ArrayFun.range(6), 2)
.map(i => { return 2*i; });
- let bishop1Pos = positions[randIndexes[0]];
- let camel1Pos = positions[randIndexes[1]];
- // The second bishop (camel) must be on a square of different color
- let randIndexes_tmp = sample(ArrayFun.range(5), 2)
+ let bishop1Pos = positions[randIndexes[0]];
+ let camel1Pos = positions[randIndexes[1]];
+ // The second bishop (camel) must be on a square of different color
+ let randIndexes_tmp = sample(ArrayFun.range(5), 2)
.map(i => { return 2*i+1; });
- let bishop2Pos = positions[randIndexes_tmp[0]];
- let camel2Pos = positions[randIndexes_tmp[1]];
- for (let idx of randIndexes.concat(randIndexes_tmp)
- .sort((a,b) => { return b-a; })) //largest indices first
- {
- positions.splice(idx, 1);
- }
+ let bishop2Pos = positions[randIndexes_tmp[0]];
+ let camel2Pos = positions[randIndexes_tmp[1]];
+ for (let idx of randIndexes.concat(randIndexes_tmp)
+ .sort((a,b) => { return b-a; })) //largest indices first
+ {
+ positions.splice(idx, 1);
+ }
- let randIndex = randInt(7);
- let knight1Pos = positions[randIndex];
- positions.splice(randIndex, 1);
- randIndex = randInt(6);
- let knight2Pos = positions[randIndex];
- positions.splice(randIndex, 1);
+ let randIndex = randInt(7);
+ let knight1Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
+ randIndex = randInt(6);
+ let knight2Pos = positions[randIndex];
+ positions.splice(randIndex, 1);
- randIndex = randInt(5);
- let queenPos = positions[randIndex];
- positions.splice(randIndex, 1);
+ randIndex = randInt(5);
+ let queenPos = positions[randIndex];
+ positions.splice(randIndex, 1);
- // Random square for wildebeest
- randIndex = randInt(4);
- let wildebeestPos = positions[randIndex];
- positions.splice(randIndex, 1);
+ // Random square for wildebeest
+ randIndex = randInt(4);
+ let wildebeestPos = positions[randIndex];
+ positions.splice(randIndex, 1);
- let rook1Pos = positions[0];
- let kingPos = positions[1];
- let rook2Pos = positions[2];
+ let rook1Pos = positions[0];
+ let kingPos = positions[1];
+ let rook2Pos = positions[2];
- pieces[c][rook1Pos] = 'r';
- pieces[c][knight1Pos] = 'n';
- pieces[c][bishop1Pos] = 'b';
- pieces[c][queenPos] = 'q';
- pieces[c][camel1Pos] = 'c';
- pieces[c][camel2Pos] = 'c';
- pieces[c][wildebeestPos] = 'w';
- pieces[c][kingPos] = 'k';
- pieces[c][bishop2Pos] = 'b';
- pieces[c][knight2Pos] = 'n';
- pieces[c][rook2Pos] = 'r';
- }
- return pieces["b"].join("") +
- "/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
- pieces["w"].join("").toUpperCase() +
- " w 0 1111 -";
- }
+ pieces[c][rook1Pos] = 'r';
+ pieces[c][knight1Pos] = 'n';
+ pieces[c][bishop1Pos] = 'b';
+ pieces[c][queenPos] = 'q';
+ pieces[c][camel1Pos] = 'c';
+ pieces[c][camel2Pos] = 'c';
+ pieces[c][wildebeestPos] = 'w';
+ pieces[c][kingPos] = 'k';
+ pieces[c][bishop2Pos] = 'b';
+ pieces[c][knight2Pos] = 'n';
+ pieces[c][rook2Pos] = 'r';
+ }
+ return pieces["b"].join("") +
+ "/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
+ pieces["w"].join("").toUpperCase() +
+ " w 0 1111 -";
+ }
}
export const VariantRules = class ZenRules extends ChessRules
{
- // NOTE: enPassant, if enabled, would need to redefine carefully getEpSquare
- static get HasEnpassant() { return false; }
-
- // TODO(?): some duplicated code in 2 next functions
- getSlideNJumpMoves([x,y], steps, oneStep)
- {
- const color = this.getColor(x,y);
- let moves = [];
- outerLoop:
- for (let loop=0; loop<steps.length; loop++)
- {
- const step = steps[loop];
- let i = x + step[0];
- let j = y + step[1];
- while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
- {
- moves.push(this.getBasicMove([x,y], [i,j]));
- if (!!oneStep)
- continue outerLoop;
- i += step[0];
- j += step[1];
- }
- // No capture check: handled elsewhere (next method)
- }
- return moves;
- }
-
- // follow steps from x,y until something is met.
- // if met piece is opponent and same movement (asA): eat it!
- findCaptures_aux([x,y], asA)
- {
- const color = this.getColor(x,y);
- var moves = [];
- const steps = asA != V.PAWN
- ? (asA==V.QUEEN ? V.steps[V.ROOK].concat(V.steps[V.BISHOP]) : V.steps[asA])
- : color=='w' ? [[-1,-1],[-1,1]] : [[1,-1],[1,1]];
- const oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king
- const lastRank = (color == 'w' ? 0 : V.size.x-1);
- const promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
- outerLoop:
- for (let loop=0; loop<steps.length; loop++)
- {
- const step = steps[loop];
- let i = x + step[0];
- let j = y + step[1];
- while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
- {
- if (oneStep)
- continue outerLoop;
- i += step[0];
- j += step[1];
- }
- if (V.OnBoard(i,j) && this.getColor(i,j) == V.GetOppCol(color)
- && this.getPiece(i,j) == asA)
- {
- // eat!
- if (this.getPiece(x,y) == V.PAWN && i == lastRank)
- {
- // Special case of promotion:
- promotionPieces.forEach(p => {
- moves.push(this.getBasicMove([x,y], [i,j], {c:color,p:p}));
- });
- }
- else
- {
- // All other cases
- moves.push(this.getBasicMove([x,y], [i,j]));
- }
- }
- }
- return moves;
- }
-
- // Find possible captures from a square: look in every direction!
- findCaptures(sq)
- {
- let moves = [];
-
- Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.PAWN));
- Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.ROOK));
- Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.KNIGHT));
- Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.BISHOP));
- Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.QUEEN));
-
- return moves;
- }
-
- getPotentialPawnMoves([x,y])
- {
- const color = this.getColor(x,y);
- let moves = [];
- const [sizeX,sizeY] = [V.size.x,V.size.y];
- const shift = (color == 'w' ? -1 : 1);
- const startRank = (color == 'w' ? sizeY-2 : 1);
- const firstRank = (color == 'w' ? sizeY-1 : 0);
- const lastRank = (color == "w" ? 0 : sizeY-1);
-
- if (x+shift != lastRank)
- {
- // Normal moves
- if (this.board[x+shift][y] == V.EMPTY)
- {
- moves.push(this.getBasicMove([x,y], [x+shift,y]));
- if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
- {
- //two squares jump
- moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
- }
- }
- }
-
- else //promotion
- {
- let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
- promotionPieces.forEach(p => {
- // Normal move
- if (this.board[x+shift][y] == V.EMPTY)
- moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
- });
- }
-
- // No en passant here
-
- // Add "zen" captures
- Array.prototype.push.apply(moves, this.findCaptures([x,y]));
-
- return moves;
- }
-
- getPotentialRookMoves(sq)
- {
- let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK]);
- let captures = this.findCaptures(sq);
- return noCaptures.concat(captures);
- }
-
- getPotentialKnightMoves(sq)
- {
- let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
- let captures = this.findCaptures(sq);
- return noCaptures.concat(captures);
- }
-
- getPotentialBishopMoves(sq)
- {
- let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]);
- let captures = this.findCaptures(sq);
- return noCaptures.concat(captures);
- }
-
- getPotentialQueenMoves(sq)
- {
- let noCaptures = this.getSlideNJumpMoves(
- sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
- let captures = this.findCaptures(sq);
- return noCaptures.concat(captures);
- }
-
- getPotentialKingMoves(sq)
- {
- // Initialize with normal moves
- let noCaptures = this.getSlideNJumpMoves(sq,
- V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
- let captures = this.findCaptures(sq);
- return noCaptures.concat(captures).concat(this.getCastleMoves(sq));
- }
-
- getNotation(move)
- {
- // Recognize special moves first
- if (move.appear.length == 2)
- {
- // castle
- if (move.end.y < move.start.y)
- return "0-0-0";
- else
- return "0-0";
- }
-
- // Translate initial square (because pieces may fly unusually in this variant!)
- const initialSquare = V.CoordsToSquare(move.start);
-
- // Translate final square
- const finalSquare = V.CoordsToSquare(move.end);
-
- let notation = "";
- const piece = this.getPiece(move.start.x, move.start.y);
- if (piece == V.PAWN)
- {
- // pawn move (TODO: enPassant indication)
- if (move.vanish.length > 1)
- {
- // capture
- notation = initialSquare + "x" + finalSquare;
- }
- else //no capture
- notation = finalSquare;
- if (piece != move.appear[0].p) //promotion
- notation += "=" + move.appear[0].p.toUpperCase();
- }
-
- else
- {
- // Piece movement
- notation = piece.toUpperCase();
- if (move.vanish.length > 1)
- notation += initialSquare + "x";
- notation += finalSquare;
- }
- return notation;
- }
-
- static get VALUES()
- {
- // TODO: experimental
- return {
- 'p': 1,
- 'r': 3,
- 'n': 2,
- 'b': 2,
- 'q': 5,
- 'k': 1000
- }
- }
+ // NOTE: enPassant, if enabled, would need to redefine carefully getEpSquare
+ static get HasEnpassant() { return false; }
+
+ // TODO(?): some duplicated code in 2 next functions
+ getSlideNJumpMoves([x,y], steps, oneStep)
+ {
+ const color = this.getColor(x,y);
+ let moves = [];
+ outerLoop:
+ for (let loop=0; loop<steps.length; loop++)
+ {
+ const step = steps[loop];
+ let i = x + step[0];
+ let j = y + step[1];
+ while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+ {
+ moves.push(this.getBasicMove([x,y], [i,j]));
+ if (!!oneStep)
+ continue outerLoop;
+ i += step[0];
+ j += step[1];
+ }
+ // No capture check: handled elsewhere (next method)
+ }
+ return moves;
+ }
+
+ // follow steps from x,y until something is met.
+ // if met piece is opponent and same movement (asA): eat it!
+ findCaptures_aux([x,y], asA)
+ {
+ const color = this.getColor(x,y);
+ var moves = [];
+ const steps = asA != V.PAWN
+ ? (asA==V.QUEEN ? V.steps[V.ROOK].concat(V.steps[V.BISHOP]) : V.steps[asA])
+ : color=='w' ? [[-1,-1],[-1,1]] : [[1,-1],[1,1]];
+ const oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king
+ const lastRank = (color == 'w' ? 0 : V.size.x-1);
+ const promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+ outerLoop:
+ for (let loop=0; loop<steps.length; loop++)
+ {
+ const step = steps[loop];
+ let i = x + step[0];
+ let j = y + step[1];
+ while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+ {
+ if (oneStep)
+ continue outerLoop;
+ i += step[0];
+ j += step[1];
+ }
+ if (V.OnBoard(i,j) && this.getColor(i,j) == V.GetOppCol(color)
+ && this.getPiece(i,j) == asA)
+ {
+ // eat!
+ if (this.getPiece(x,y) == V.PAWN && i == lastRank)
+ {
+ // Special case of promotion:
+ promotionPieces.forEach(p => {
+ moves.push(this.getBasicMove([x,y], [i,j], {c:color,p:p}));
+ });
+ }
+ else
+ {
+ // All other cases
+ moves.push(this.getBasicMove([x,y], [i,j]));
+ }
+ }
+ }
+ return moves;
+ }
+
+ // Find possible captures from a square: look in every direction!
+ findCaptures(sq)
+ {
+ let moves = [];
+
+ Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.PAWN));
+ Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.ROOK));
+ Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.KNIGHT));
+ Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.BISHOP));
+ Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.QUEEN));
+
+ return moves;
+ }
+
+ getPotentialPawnMoves([x,y])
+ {
+ const color = this.getColor(x,y);
+ let moves = [];
+ const [sizeX,sizeY] = [V.size.x,V.size.y];
+ const shift = (color == 'w' ? -1 : 1);
+ const startRank = (color == 'w' ? sizeY-2 : 1);
+ const firstRank = (color == 'w' ? sizeY-1 : 0);
+ const lastRank = (color == "w" ? 0 : sizeY-1);
+
+ if (x+shift != lastRank)
+ {
+ // Normal moves
+ if (this.board[x+shift][y] == V.EMPTY)
+ {
+ moves.push(this.getBasicMove([x,y], [x+shift,y]));
+ if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
+ {
+ //two squares jump
+ moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
+ }
+ }
+ }
+
+ else //promotion
+ {
+ let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+ promotionPieces.forEach(p => {
+ // Normal move
+ if (this.board[x+shift][y] == V.EMPTY)
+ moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
+ });
+ }
+
+ // No en passant here
+
+ // Add "zen" captures
+ Array.prototype.push.apply(moves, this.findCaptures([x,y]));
+
+ return moves;
+ }
+
+ getPotentialRookMoves(sq)
+ {
+ let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK]);
+ let captures = this.findCaptures(sq);
+ return noCaptures.concat(captures);
+ }
+
+ getPotentialKnightMoves(sq)
+ {
+ let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
+ let captures = this.findCaptures(sq);
+ return noCaptures.concat(captures);
+ }
+
+ getPotentialBishopMoves(sq)
+ {
+ let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]);
+ let captures = this.findCaptures(sq);
+ return noCaptures.concat(captures);
+ }
+
+ getPotentialQueenMoves(sq)
+ {
+ let noCaptures = this.getSlideNJumpMoves(
+ sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
+ let captures = this.findCaptures(sq);
+ return noCaptures.concat(captures);
+ }
+
+ getPotentialKingMoves(sq)
+ {
+ // Initialize with normal moves
+ let noCaptures = this.getSlideNJumpMoves(sq,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+ let captures = this.findCaptures(sq);
+ return noCaptures.concat(captures).concat(this.getCastleMoves(sq));
+ }
+
+ getNotation(move)
+ {
+ // Recognize special moves first
+ if (move.appear.length == 2)
+ {
+ // castle
+ if (move.end.y < move.start.y)
+ return "0-0-0";
+ else
+ return "0-0";
+ }
+
+ // Translate initial square (because pieces may fly unusually in this variant!)
+ const initialSquare = V.CoordsToSquare(move.start);
+
+ // Translate final square
+ const finalSquare = V.CoordsToSquare(move.end);
+
+ let notation = "";
+ const piece = this.getPiece(move.start.x, move.start.y);
+ if (piece == V.PAWN)
+ {
+ // pawn move (TODO: enPassant indication)
+ if (move.vanish.length > 1)
+ {
+ // capture
+ notation = initialSquare + "x" + finalSquare;
+ }
+ else //no capture
+ notation = finalSquare;
+ if (piece != move.appear[0].p) //promotion
+ notation += "=" + move.appear[0].p.toUpperCase();
+ }
+
+ else
+ {
+ // Piece movement
+ notation = piece.toUpperCase();
+ if (move.vanish.length > 1)
+ notation += initialSquare + "x";
+ notation += finalSquare;
+ }
+ return notation;
+ }
+
+ static get VALUES()
+ {
+ // TODO: experimental
+ return {
+ 'p': 1,
+ 'r': 3,
+ 'n': 2,
+ 'b': 2,
+ 'q': 5,
+ 'k': 1000
+ }
+ }
}
},
newChallenge: async function() {
if (this.newchallenge.vid == "")
- return alert("Please select a variant");
+ return alert("Please select a variant");
const vname = this.getVname(this.newchallenge.vid);
const vModule = await import("@/variants/" + vname + ".js");
window.V = vModule.VariantRules;
data: function() {
return {
st: store.state,
- display: "live",
+ display: "live",
games: [],
};
},
if (app.get('env') === 'development')
{
- // Full logging in development mode
- app.use(logger('dev'));
+ // Full logging in development mode
+ app.use(logger('dev'));
}
else
{
- // http://dev.rdybarra.com/2016/06/23/Production-Logging-With-Morgan-In-Express/
- app.set('trust proxy', true);
- // In prod, only log error responses (https://github.com/expressjs/morgan)
- app.use(logger('combined', {
- skip: function (req, res) { return res.statusCode < 400 }
- }));
+ // http://dev.rdybarra.com/2016/06/23/Production-Logging-With-Morgan-In-Express/
+ app.set('trust proxy', true);
+ // In prod, only log error responses (https://github.com/expressjs/morgan)
+ app.use(logger('combined', {
+ skip: function (req, res) { return res.statusCode < 400 }
+ }));
}
app.use(express.json());
// In development stage the client side has its own server
if (params.cors.enable)
{
- app.use(function(req, res, next) {
- res.header("Access-Control-Allow-Origin", params.cors.allowedOrigin);
- res.header("Access-Control-Allow-Credentials", true); //for cookies
+ app.use(function(req, res, next) {
+ res.header("Access-Control-Allow-Origin", params.cors.allowedOrigin);
+ res.header("Access-Control-Allow-Credentials", true); //for cookies
res.header("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept");
- res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
+ res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
next();
- });
+ });
}
// Routing (AJAX-only)
// render the error page
res.status(err.status || 500);
res.send(`
- <!doctype html>
- <h1>= message</h1>
- <h2>= error.status</h2>
- <pre>#{error.stack}</pre>
- `);
+ <!doctype html>
+ <h1>= message</h1>
+ <h2>= error.status</h2>
+ <pre>#{error.stack}</pre>
+ `);
});
module.exports = app;
var ChallengeModel = require("../models/Challenge");
var GameModel = require("../models/Game");
cron.schedule('0 0 0 * * *', function() {
- // Remove some old users, challenges and games every 24h
- UserModel.cleanUsersDb();
- ChallengeModel.removeOld();
- GameModel.cleanGamesDb();
+ // Remove some old users, challenges and games every 24h
+ UserModel.cleanUsersDb();
+ ChallengeModel.removeOld();
+ GameModel.cleanGamesDb();
});
/**
module.exports =
{
- // For mail sending. NOTE: *no trailing slash*
- siteURL: "http://localhost:8080",
+ // For mail sending. NOTE: *no trailing slash*
+ siteURL: "http://localhost:8080",
- // To know in which environment the code run
- env: process.env.NODE_ENV || 'development',
-
- // CORS: cross-origin resource sharing options
- cors: {
- enable: true,
- allowedOrigin: "http://localhost:8080",
- },
+ // To know in which environment the code run
+ env: process.env.NODE_ENV || 'development',
+
+ // CORS: cross-origin resource sharing options
+ cors: {
+ enable: true,
+ allowedOrigin: "http://localhost:8080",
+ },
- // Lifespan of a (login) cookie
- cookieExpire: 183*24*60*60*1000, //6 months in milliseconds
+ // Lifespan of a (login) cookie
+ cookieExpire: 183*24*60*60*1000, //6 months in milliseconds
- // Characters in a login token, and period of validity (in milliseconds)
- token: {
- length: 16,
- expire: 30*60*1000, //30 minutes in milliseconds
- },
+ // Characters in a login token, and period of validity (in milliseconds)
+ token: {
+ length: 16,
+ expire: 30*60*1000, //30 minutes in milliseconds
+ },
- // Email settings
- mail: {
- host: "mail_host_address",
- port: 465, //if secure; otherwise use 587
- secure: true, //...or false
- user: "mail_user_name",
- pass: "mail_password",
- noreply: "some_noreply_email",
- contact: "some_contact_email",
- },
+ // Email settings
+ mail: {
+ host: "mail_host_address",
+ port: 465, //if secure; otherwise use 587
+ secure: true, //...or false
+ user: "mail_user_name",
+ pass: "mail_password",
+ noreply: "some_noreply_email",
+ contact: "some_contact_email",
+ },
};
-- Re-run this script after variants are added
insert or ignore into Variants (name,description) values
- ('Alice', 'Both sides of the mirror'),
- ('Antiking', 'Keep antiking in check'),
- ('Atomic', 'Explosive captures'),
- ('Baroque', 'Exotic captures'),
- ('Berolina', 'Pawns move diagonally'),
- ('Checkered', 'Shared pieces'),
- ('Chess960', 'Standard rules'),
- ('Crazyhouse', 'Captures reborn'),
- ('Dark', 'In the shadow'),
- ('Extinction', 'Capture all of a kind'),
- ('Grand', 'Big board'),
- ('Losers', 'Lose all pieces'),
- ('Magnetic', 'Laws of attraction'),
- ('Marseille', 'Move twice'),
- ('Switching', 'Exchange pieces positions'),
- ('Upsidedown', 'Head upside down'),
- ('Wildebeest', 'Balanced sliders & leapers'),
- ('Zen', 'Reverse captures');
+ ('Alice', 'Both sides of the mirror'),
+ ('Antiking', 'Keep antiking in check'),
+ ('Atomic', 'Explosive captures'),
+ ('Baroque', 'Exotic captures'),
+ ('Berolina', 'Pawns move diagonally'),
+ ('Checkered', 'Shared pieces'),
+ ('Chess960', 'Standard rules'),
+ ('Crazyhouse', 'Captures reborn'),
+ ('Dark', 'In the shadow'),
+ ('Extinction', 'Capture all of a kind'),
+ ('Grand', 'Big board'),
+ ('Losers', 'Lose all pieces'),
+ ('Magnetic', 'Laws of attraction'),
+ ('Marseille', 'Move twice'),
+ ('Switching', 'Exchange pieces positions'),
+ ('Upsidedown', 'Head upside down'),
+ ('Wildebeest', 'Balanced sliders & leapers'),
+ ('Zen', 'Reverse captures');
var nodemon = require('gulp-nodemon'); //reload server on changes
var nodemonOptions = {
- script: 'bin/www',
- ext: 'js',
- env: { 'NODE_ENV': 'development' },
- verbose: true,
- watch: ['./','routes','bin']
+ script: 'bin/www',
+ ext: 'js',
+ env: { 'NODE_ENV': 'development' },
+ verbose: true,
+ watch: ['./','routes','bin']
};
gulp.task('start', function () {
- nodemon(nodemonOptions)
- .on('restart', function () {
- console.log('restarted!')
- });
+ nodemon(nodemonOptions)
+ .on('restart', function () {
+ console.log('restarted!')
+ });
});
return "";
},
- create: function(vid, fen, timeControl, players, cb)
- {
- db.serialize(function() {
- let query =
- "INSERT INTO Games"
+ create: function(vid, fen, timeControl, players, cb)
+ {
+ db.serialize(function() {
+ let query =
+ "INSERT INTO Games"
+ " (vid, fenStart, fen, score, timeControl, created, drawOffer)"
+ " VALUES (" + vid + ",'" + fen + "','" + fen + "','*','"
+ timeControl + "'," + Date.now() + "," + false + ")";
db.run(query);
});
cb(null, {gid: this.lastID});
- });
- });
- },
+ });
+ });
+ },
- // TODO: queries here could be async, and wait for all to complete
- getOne: function(id, cb)
- {
- db.serialize(function() {
+ // TODO: queries here could be async, and wait for all to complete
+ getOne: function(id, cb)
+ {
+ db.serialize(function() {
// TODO: optimize queries?
- let query =
+ let query =
// NOTE: g.scoreMsg can be NULL
// (in this case score = "*" and no reason to look at it)
- "SELECT g.id, g.vid, g.fen, g.fenStart, g.timeControl, g.score, " +
+ "SELECT g.id, g.vid, g.fen, g.fenStart, g.timeControl, g.score, " +
"g.scoreMsg, v.name AS vname " +
- "FROM Games g " +
+ "FROM Games g " +
"JOIN Variants v " +
" ON g.vid = v.id " +
- "WHERE g.id = " + id;
- db.get(query, (err,gameInfo) => {
- if (!!err)
- return cb(err);
- query =
- "SELECT p.uid, p.color, u.name " +
- "FROM Players p " +
+ "WHERE g.id = " + id;
+ db.get(query, (err,gameInfo) => {
+ if (!!err)
+ return cb(err);
+ query =
+ "SELECT p.uid, p.color, u.name " +
+ "FROM Players p " +
"JOIN Users u " +
" ON p.uid = u.id " +
- "WHERE p.gid = " + id;
- db.all(query, (err2,players) => {
- if (!!err2)
- return cb(err2);
- query =
- "SELECT squares, played, idx " +
- "FROM Moves " +
- "WHERE gid = " + id;
- db.all(query, (err3,moves) => {
- if (!!err3)
- return cb(err3);
- query =
+ "WHERE p.gid = " + id;
+ db.all(query, (err2,players) => {
+ if (!!err2)
+ return cb(err2);
+ query =
+ "SELECT squares, played, idx " +
+ "FROM Moves " +
+ "WHERE gid = " + id;
+ db.all(query, (err3,moves) => {
+ if (!!err3)
+ return cb(err3);
+ query =
"SELECT msg, name, added " +
"FROM Chats " +
"WHERE gid = " + id;
- db.all(query, (err4,chats) => {
- if (!!err4)
- return cb(err4);
- const game = Object.assign({},
+ db.all(query, (err4,chats) => {
+ if (!!err4)
+ return cb(err4);
+ const game = Object.assign({},
gameInfo,
{
players: players,
chats: chats,
}
);
- return cb(null, game);
+ return cb(null, game);
});
- });
- });
- });
- });
- },
+ });
+ });
+ });
+ });
+ },
- getByUser: function(uid, excluded, cb)
- {
- db.serialize(function() {
- // Next query is fine because a player appear at most once in a game
- const query =
- "SELECT gid " +
- "FROM Players " +
- "WHERE uid " + (excluded ? "<>" : "=") + " " + uid;
- db.all(query, (err,gameIds) => {
- if (!!err)
- return cb(err);
+ getByUser: function(uid, excluded, cb)
+ {
+ db.serialize(function() {
+ // Next query is fine because a player appear at most once in a game
+ const query =
+ "SELECT gid " +
+ "FROM Players " +
+ "WHERE uid " + (excluded ? "<>" : "=") + " " + uid;
+ db.all(query, (err,gameIds) => {
+ if (!!err)
+ return cb(err);
gameIds = gameIds || []; //might be empty
- let gameArray = [];
- for (let i=0; i<gameIds.length; i++)
- {
- GameModel.getOne(gameIds[i]["gid"], (err2,game) => {
- if (!!err2)
- return cb(err2);
- gameArray.push(game);
- // Call callback function only when gameArray is complete:
- if (i == gameIds.length - 1)
- return cb(null, gameArray);
- });
- }
- });
- });
- },
+ let gameArray = [];
+ for (let i=0; i<gameIds.length; i++)
+ {
+ GameModel.getOne(gameIds[i]["gid"], (err2,game) => {
+ if (!!err2)
+ return cb(err2);
+ gameArray.push(game);
+ // Call callback function only when gameArray is complete:
+ if (i == gameIds.length - 1)
+ return cb(null, gameArray);
+ });
+ }
+ });
+ });
+ },
getPlayers: function(id, cb)
{
// obj can have fields move, chat, fen, drawOffer and/or score
update: function(id, obj)
{
- db.parallelize(function() {
+ db.parallelize(function() {
let query =
"UPDATE Games " +
"SET ";
}
if (!!obj.chat)
{
- query =
- "INSERT INTO Chats (gid, msg, name, added) VALUES ("
+ query =
+ "INSERT INTO Chats (gid, msg, name, added) VALUES ("
+ id + ",?,'" + obj.chat.name + "'," + Date.now() + ")";
db.run(query, obj.chat.msg);
}
});
},
- remove: function(id)
- {
- db.parallelize(function() {
- let query =
- "DELETE FROM Games " +
- "WHERE id = " + id;
- db.run(query);
- query =
- "DELETE FROM Players " +
- "WHERE gid = " + id;
- db.run(query);
- query =
- "DELETE FROM Moves " +
- "WHERE gid = " + id;
- db.run(query);
- query =
- "DELETE FROM Chats " +
- "WHERE gid = " + id;
- db.run(query);
- });
- },
+ remove: function(id)
+ {
+ db.parallelize(function() {
+ let query =
+ "DELETE FROM Games " +
+ "WHERE id = " + id;
+ db.run(query);
+ query =
+ "DELETE FROM Players " +
+ "WHERE gid = " + id;
+ db.run(query);
+ query =
+ "DELETE FROM Moves " +
+ "WHERE gid = " + id;
+ db.run(query);
+ query =
+ "DELETE FROM Chats " +
+ "WHERE gid = " + id;
+ db.run(query);
+ });
+ },
cleanGamesDb: function()
{
const UserModel =
{
- checkNameEmail: function(o)
- {
- if (typeof o.name === "string")
- {
- if (o.name.length == 0)
- return "Empty name";
- if (!o.name.match(/^[\w]+$/))
- return "Bad characters in name";
- }
- if (typeof o.email === "string")
- {
- if (o.email.length == 0)
- return "Empty email";
- if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
- return "Bad characters in email";
- }
+ checkNameEmail: function(o)
+ {
+ if (typeof o.name === "string")
+ {
+ if (o.name.length == 0)
+ return "Empty name";
+ if (!o.name.match(/^[\w]+$/))
+ return "Bad characters in name";
+ }
+ if (typeof o.email === "string")
+ {
+ if (o.email.length == 0)
+ return "Empty email";
+ if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
+ return "Bad characters in email";
+ }
return ""; //NOTE: not required, but more consistent... (?!)
- },
+ },
- // NOTE: parameters are already cleaned (in controller), thus no sanitization here
- create: function(name, email, notify, callback)
- {
- db.serialize(function() {
- const insertQuery =
- "INSERT INTO Users " +
- "(name, email, notify, created) VALUES " +
- "('" + name + "', '" + email + "', " + notify + "," + Date.now() + ")";
- db.run(insertQuery, err => {
- if (!!err)
- return callback(err);
- db.get("SELECT last_insert_rowid() AS rowid", callback);
- });
- });
- },
+ // NOTE: parameters are already cleaned (in controller), thus no sanitization here
+ create: function(name, email, notify, callback)
+ {
+ db.serialize(function() {
+ const insertQuery =
+ "INSERT INTO Users " +
+ "(name, email, notify, created) VALUES " +
+ "('" + name + "', '" + email + "', " + notify + "," + Date.now() + ")";
+ db.run(insertQuery, err => {
+ if (!!err)
+ return callback(err);
+ db.get("SELECT last_insert_rowid() AS rowid", callback);
+ });
+ });
+ },
- // Find one user (by id, name, email, or token)
- getOne: function(by, value, cb)
- {
- const delimiter = (typeof value === "string" ? "'" : "");
- db.serialize(function() {
- const query =
- "SELECT * " +
- "FROM Users " +
- "WHERE " + by + " = " + delimiter + value + delimiter;
- db.get(query, cb);
- });
- },
+ // Find one user (by id, name, email, or token)
+ getOne: function(by, value, cb)
+ {
+ const delimiter = (typeof value === "string" ? "'" : "");
+ db.serialize(function() {
+ const query =
+ "SELECT * " +
+ "FROM Users " +
+ "WHERE " + by + " = " + delimiter + value + delimiter;
+ db.get(query, cb);
+ });
+ },
getByIds: function(ids, cb) {
db.serialize(function() {
});
},
- /////////
- // MODIFY
+ /////////
+ // MODIFY
- setLoginToken: function(token, uid, cb)
- {
- db.serialize(function() {
- const query =
- "UPDATE Users " +
- "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
- "WHERE id = " + uid;
- db.run(query, cb);
- });
- },
+ setLoginToken: function(token, uid, cb)
+ {
+ db.serialize(function() {
+ const query =
+ "UPDATE Users " +
+ "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
+ "WHERE id = " + uid;
+ db.run(query, cb);
+ });
+ },
- // Set session token only if empty (first login)
- // NOTE: weaker security (but avoid to re-login everywhere after each logout)
- // TODO: option would be to reset all tokens periodically, e.g. every 3 months
+ // Set session token only if empty (first login)
+ // NOTE: weaker security (but avoid to re-login everywhere after each logout)
+ // TODO: option would be to reset all tokens periodically, e.g. every 3 months
trySetSessionToken: function(uid, cb)
- {
- // Also empty the login token to invalidate future attempts
- db.serialize(function() {
- const querySessionToken =
- "SELECT sessionToken " +
- "FROM Users " +
- "WHERE id = " + uid;
- db.get(querySessionToken, (err,ret) => {
- if (!!err)
- return cb(err);
- const token = ret.sessionToken || genToken(params.token.length);
- const queryUpdate =
- "UPDATE Users " +
- "SET loginToken = NULL" +
- (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
- "WHERE id = " + uid;
- db.run(queryUpdate);
- cb(null, token);
- });
- });
- },
+ {
+ // Also empty the login token to invalidate future attempts
+ db.serialize(function() {
+ const querySessionToken =
+ "SELECT sessionToken " +
+ "FROM Users " +
+ "WHERE id = " + uid;
+ db.get(querySessionToken, (err,ret) => {
+ if (!!err)
+ return cb(err);
+ const token = ret.sessionToken || genToken(params.token.length);
+ const queryUpdate =
+ "UPDATE Users " +
+ "SET loginToken = NULL" +
+ (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
+ "WHERE id = " + uid;
+ db.run(queryUpdate);
+ cb(null, token);
+ });
+ });
+ },
- updateSettings: function(user, cb)
- {
- db.serialize(function() {
- const query =
- "UPDATE Users " +
- "SET name = '" + user.name + "'" +
- ", email = '" + user.email + "'" +
- ", notify = " + user.notify + " " +
- "WHERE id = " + user.id;
- db.run(query, cb);
- });
- },
+ updateSettings: function(user, cb)
+ {
+ db.serialize(function() {
+ const query =
+ "UPDATE Users " +
+ "SET name = '" + user.name + "'" +
+ ", email = '" + user.email + "'" +
+ ", notify = " + user.notify + " " +
+ "WHERE id = " + user.id;
+ db.run(query, cb);
+ });
+ },
/////////////////
// NOTIFICATIONS
tryNotify: function(oppId, message)
{
- UserModel.getOne("id", oppId, (err,opp) => {
+ UserModel.getOne("id", oppId, (err,opp) => {
if (!err || !opp.notify)
return; //error is ignored here (TODO: should be logged)
const subject = "vchess.club - notification";
const VariantModel =
{
- getAll: function(callback)
- {
- db.serialize(function() {
- const query =
- "SELECT * " +
- "FROM Variants";
- db.all(query, callback);
- });
- },
+ getAll: function(callback)
+ {
+ db.serialize(function() {
+ const query =
+ "SELECT * " +
+ "FROM Variants";
+ db.all(query, callback);
+ });
+ },
- //create, update, delete: directly in DB
+ //create, update, delete: directly in DB
}
module.exports = VariantModel;
// From main hall, start game between players 0 and 1
router.post("/games", access.logged, access.ajax, (req,res) => {
const gameInfo = req.body.gameInfo;
- if (!Array.isArray(gameInfo.players) ||
+ if (!Array.isArray(gameInfo.players) ||
!gameInfo.players.some(p => p.id == req.userId))
{
- return res.json({errmsg: "Cannot start someone else's game"});
+ return res.json({errmsg: "Cannot start someone else's game"});
}
const cid = req.body.cid;
// Check all entries of gameInfo + cid:
if (!!error)
return res.json({errmsg:error});
ChallengeModel.remove(cid);
- GameModel.create(
+ GameModel.create(
gameInfo.vid, gameInfo.fen, gameInfo.timeControl, gameInfo.players,
- (err,ret) => {
- access.checkRequest(res, err, ret, "Cannot create game", () => {
+ (err,ret) => {
+ access.checkRequest(res, err, ret, "Cannot create game", () => {
const oppIdx = (gameInfo.players[0].id == req.userId ? 1 : 0);
const oppId = gameInfo.players[oppIdx].id;
UserModel.tryNotify(oppId,
"New game: " + params.siteURL + "/game/" + ret.gid);
- res.json({gameId: ret.gid});
- });
- }
- );
+ res.json({gameId: ret.gid});
+ });
+ }
+ );
});
router.get("/games", access.ajax, (req,res) => {
- const gameId = req.query["gid"];
- if (!!gameId)
+ const gameId = req.query["gid"];
+ if (!!gameId)
{
GameModel.getOne(gameId, (err,game) => {
- access.checkRequest(res, err, game, "Game not found", () => {
+ access.checkRequest(res, err, game, "Game not found", () => {
res.json({game: game});
- });
- });
+ });
+ });
}
else
{
const userId = req.query["uid"];
const excluded = !!req.query["excluded"];
GameModel.getByUser(userId, excluded, (err,games) => {
- if (!!err)
+ if (!!err)
return res.json({errmsg: err.errmsg || err.toString()});
- res.json({games: games});
- });
+ res.json({games: games});
+ });
}
});
if (!gid.toString().match(/^[0-9]+$/))
error = "Wrong game ID";
const obj = req.body.newObj;
- error = GameModel.checkGameUpdate(obj);
+ error = GameModel.checkGameUpdate(obj);
if (!!error)
return res.json({errmsg: error});
- GameModel.update(gid, obj, (err) => {
- if (!!err)
+ GameModel.update(gid, obj, (err) => {
+ if (!!err)
return res.json(err);
// Notify opponent if he enabled notifications:
GameModel.getPlayers(gid, (err2,players) => {
"New move in game: " + params.siteURL + "/game/" + gid);
});
res.json({});
- });
+ });
});
module.exports = router;
// Send a message through contact form
router.post("/messages", (req,res,next) => {
- if (!req.xhr)
- return res.json({errmsg: "Unauthorized access"});
+ if (!req.xhr)
+ return res.json({errmsg: "Unauthorized access"});
const from = req.body["email"];
- const subject = req.body["subject"];
- const body = req.body["content"];
+ const subject = req.body["subject"];
+ const body = req.body["content"];
- // TODO: sanitize ?
- mailer(from, params.mail.contact, subject, body, err => {
- if (!!err)
- return res.json({errmsg:err});
- // OK, everything fine
- res.json({}); //ignored
- });
+ // TODO: sanitize ?
+ mailer(from, params.mail.contact, subject, body, err => {
+ if (!!err)
+ return res.json({errmsg:err});
+ // OK, everything fine
+ res.json({}); //ignored
+ });
});
module.exports = router;
});
};
const anonymous = {name:"", email:"", id:0, notify:false};
- if (!req.cookies.token)
+ if (!req.cookies.token)
return callback(anonymous);
UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
if (!!err || !user)
// to: object user (to who we send an email)
function setAndSendLoginToken(subject, to, res)
{
- // Set login token and send welcome(back) email with auth link
- const token = genToken(params.token.length);
- UserModel.setLoginToken(token, to.id, err => {
- if (!!err)
- return res.json({errmsg: err.toString()});
- const body =
- "Hello " + to.name + "!\\n" +
- "Access your account here: " +
- params.siteURL + "/#/authenticate/" + token + "\\n" +
- "Token will expire in " + params.token.expire/(1000*60) + " minutes."
- sendEmail(params.mail.noreply, to.email, subject, body, err => {
- res.json(err || {});
- });
- });
+ // Set login token and send welcome(back) email with auth link
+ const token = genToken(params.token.length);
+ UserModel.setLoginToken(token, to.id, err => {
+ if (!!err)
+ return res.json({errmsg: err.toString()});
+ const body =
+ "Hello " + to.name + "!\\n" +
+ "Access your account here: " +
+ params.siteURL + "/#/authenticate/" + token + "\\n" +
+ "Token will expire in " + params.token.expire/(1000*60) + " minutes."
+ sendEmail(params.mail.noreply, to.email, subject, body, err => {
+ res.json(err || {});
+ });
+ });
}
router.post('/register', access.unlogged, access.ajax, (req,res) => {
- const name = req.body.name;
- const email = req.body.email;
- const notify = !!req.body.notify;
- const error = UserModel.checkNameEmail({name: name, email: email});
- if (!!error)
- return res.json({errmsg: error});
- UserModel.create(name, email, notify, (err,uid) => {
- if (!!err)
- return res.json({errmsg: err.toString()});
- const user = {
- id: uid["rowid"],
- name: name,
- email: email,
- };
- setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
- });
+ const name = req.body.name;
+ const email = req.body.email;
+ const notify = !!req.body.notify;
+ const error = UserModel.checkNameEmail({name: name, email: email});
+ if (!!error)
+ return res.json({errmsg: error});
+ UserModel.create(name, email, notify, (err,uid) => {
+ if (!!err)
+ return res.json({errmsg: err.toString()});
+ const user = {
+ id: uid["rowid"],
+ name: name,
+ email: email,
+ };
+ setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
+ });
});
router.get('/sendtoken', access.unlogged, access.ajax, (req,res) => {
- const nameOrEmail = decodeURIComponent(req.query.nameOrEmail);
- const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
- const error = UserModel.checkNameEmail({[type]: nameOrEmail});
- if (!!error)
- return res.json({errmsg: error});
- UserModel.getOne(type, nameOrEmail, (err,user) => {
- access.checkRequest(res, err, user, "Unknown user", () => {
- setAndSendLoginToken("Token for " + params.siteURL, user, res);
- });
- });
+ const nameOrEmail = decodeURIComponent(req.query.nameOrEmail);
+ const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
+ const error = UserModel.checkNameEmail({[type]: nameOrEmail});
+ if (!!error)
+ return res.json({errmsg: error});
+ UserModel.getOne(type, nameOrEmail, (err,user) => {
+ access.checkRequest(res, err, user, "Unknown user", () => {
+ setAndSendLoginToken("Token for " + params.siteURL, user, res);
+ });
+ });
});
router.get('/authenticate', access.unlogged, access.ajax, (req,res) => {
UserModel.getOne("loginToken", req.query.token, (err,user) => {
- access.checkRequest(res, err, user, "Invalid token", () => {
+ access.checkRequest(res, err, user, "Invalid token", () => {
// If token older than params.tokenExpire, do nothing
- if (Date.now() > user.loginTime + params.token.expire)
- return res.json({errmsg: "Token expired"});
- // Generate session token (if not exists) + destroy login token
- UserModel.trySetSessionToken(user.id, (err,token) => {
- if (!!err)
- return res.json({errmsg: err.toString()});
- // Set cookie
+ if (Date.now() > user.loginTime + params.token.expire)
+ return res.json({errmsg: "Token expired"});
+ // Generate session token (if not exists) + destroy login token
+ UserModel.trySetSessionToken(user.id, (err,token) => {
+ if (!!err)
+ return res.json({errmsg: err.toString()});
+ // Set cookie
res.cookie("token", token, {
- httpOnly: true,
- secure: !!params.siteURL.match(/^https/),
- maxAge: params.cookieExpire,
- });
- res.json({
+ httpOnly: true,
+ secure: !!params.siteURL.match(/^https/),
+ maxAge: params.cookieExpire,
+ });
+ res.json({
id: user.id,
name: user.name,
email: user.email,
notify: user.notify,
});
- });
- });
- });
+ });
+ });
+ });
});
router.put('/update', access.logged, access.ajax, (req,res) => {
- const name = req.body.name;
- const email = req.body.email;
- const error = UserModel.checkNameEmail({name: name, email: email});
- if (!!error)
- return res.json({errmsg: error});
- const user = {
- id: req.userId,
- name: name,
- email: email,
- notify: !!req.body.notify,
- };
- UserModel.updateSettings(user, err => {
- res.json(err ? {errmsg: err.toString()} : {});
- });
+ const name = req.body.name;
+ const email = req.body.email;
+ const error = UserModel.checkNameEmail({name: name, email: email});
+ if (!!error)
+ return res.json({errmsg: error});
+ const user = {
+ id: req.userId,
+ name: name,
+ email: email,
+ notify: !!req.body.notify,
+ };
+ UserModel.updateSettings(user, err => {
+ res.json(err ? {errmsg: err.toString()} : {});
+ });
});
router.get('/logout', access.logged, access.ajax, (req,res) => {
- res.clearCookie("token");
- res.json({});
+ res.clearCookie("token");
+ res.json({});
});
module.exports = router;
const access = require("../utils/access");
router.get('/variants', access.ajax, function(req, res, next) {
- VariantModel.getAll((err,variants) => {
- if (!!err)
- return next(err);
- res.json({variantArray:variants});
- });
+ VariantModel.getAll((err,variants) => {
+ if (!!err)
+ return next(err);
+ res.json({variantArray:variants});
+ });
});
module.exports = router;
module.exports =
{
- // Prevent access to "users pages"
- logged: function(req, res, next) {
- const callback = () => {
- if (!loggedIn)
- return res.json({errmsg: "Not logged in"});
- next();
- };
- let loggedIn = undefined;
- if (!req.cookies.token)
- {
- loggedIn = false;
- callback();
- }
- else
- {
- UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
- if (!!user)
- {
- req.userId = user.id;
- req.userName = user.name;
- loggedIn = true;
- }
- else
- {
- // Token in cookies presumably wrong: erase it
- res.clearCookie("token");
- loggedIn = false;
- }
- callback();
- });
- }
- },
+ // Prevent access to "users pages"
+ logged: function(req, res, next) {
+ const callback = () => {
+ if (!loggedIn)
+ return res.json({errmsg: "Not logged in"});
+ next();
+ };
+ let loggedIn = undefined;
+ if (!req.cookies.token)
+ {
+ loggedIn = false;
+ callback();
+ }
+ else
+ {
+ UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
+ if (!!user)
+ {
+ req.userId = user.id;
+ req.userName = user.name;
+ loggedIn = true;
+ }
+ else
+ {
+ // Token in cookies presumably wrong: erase it
+ res.clearCookie("token");
+ loggedIn = false;
+ }
+ callback();
+ });
+ }
+ },
- // Prevent access to "anonymous pages"
- unlogged: function(req, res, next) {
- // Just a quick heuristic, which should be enough
- const loggedIn = !!req.cookies.token;
- if (loggedIn)
- return res.json({errmsg: "Already logged in"});
- next();
- },
+ // Prevent access to "anonymous pages"
+ unlogged: function(req, res, next) {
+ // Just a quick heuristic, which should be enough
+ const loggedIn = !!req.cookies.token;
+ if (loggedIn)
+ return res.json({errmsg: "Already logged in"});
+ next();
+ },
- // Prevent direct access to AJAX results
- ajax: function(req, res, next) {
+ // Prevent direct access to AJAX results
+ ajax: function(req, res, next) {
if (!req.xhr)
- return res.json({errmsg: "Unauthorized access"});
- next();
- },
+ return res.json({errmsg: "Unauthorized access"});
+ next();
+ },
- // Check for errors before callback (continue page loading). TODO: better name.
- checkRequest: function(res, err, out, msg, cb) {
- if (!!err)
- return res.json({errmsg: err.errmsg || err.toString()});
- if (!out
- || (Array.isArray(out) && out.length == 0)
- || (typeof out === "object" && Object.keys(out).length == 0))
- {
- return res.json({errmsg: msg});
- }
- cb();
- },
+ // Check for errors before callback (continue page loading). TODO: better name.
+ checkRequest: function(res, err, out, msg, cb) {
+ if (!!err)
+ return res.json({errmsg: err.errmsg || err.toString()});
+ if (!out
+ || (Array.isArray(out) && out.length == 0)
+ || (typeof out === "object" && Object.keys(out).length == 0))
+ {
+ return res.json({errmsg: msg});
+ }
+ cb();
+ },
}
const params = require("../config/parameters")
if (params.env == "development")
- sqlite3.verbose();
+ sqlite3.verbose();
const DbPath = __dirname.replace("/utils", "/db/vchess.sqlite");
const db = new sqlite3.Database(DbPath);
module.exports = function(from, to, subject, body, cb)
{
- // Avoid the actual sending in development mode
- if (params.env === 'development')
- {
- console.log("New mail: from " + from + " / to " + to);
- console.log("Subject: " + subject);
- let msgText = body.split('\\n');
- msgText.forEach(msg => { console.log(msg); });
- return cb();
- }
+ // Avoid the actual sending in development mode
+ if (params.env === 'development')
+ {
+ console.log("New mail: from " + from + " / to " + to);
+ console.log("Subject: " + subject);
+ let msgText = body.split('\\n');
+ msgText.forEach(msg => { console.log(msg); });
+ return cb();
+ }
// Create reusable transporter object using the default SMTP transport
- const transporter = nodemailer.createTransport({
- host: params.mail.host,
- port: params.mail.port,
- secure: params.mail.secure,
- auth: {
- user: params.mail.user,
- pass: params.mail.pass
- }
- });
+ const transporter = nodemailer.createTransport({
+ host: params.mail.host,
+ port: params.mail.port,
+ secure: params.mail.secure,
+ auth: {
+ user: params.mail.user,
+ pass: params.mail.pass
+ }
+ });
- // Setup email data with unicode symbols
- const mailOptions = {
- from: params.mail.noreply,
- to: to,
- subject: subject,
- text: body,
+ // Setup email data with unicode symbols
+ const mailOptions = {
+ from: params.mail.noreply,
+ to: to,
+ subject: subject,
+ text: body,
replyTo: from,
};
- // Send mail with the defined transport object
- transporter.sendMail(mailOptions, (error, info) => {
- if (!!error)
- return cb(error);
+ // Send mail with the defined transport object
+ transporter.sendMail(mailOptions, (error, info) => {
+ if (!!error)
+ return cb(error);
// Ignore info. Option:
- //console.log('Message sent: %s', info.messageId);
- return cb();
+ //console.log('Message sent: %s', info.messageId);
+ return cb();
});
}
function randString()
{
- return Math.random().toString(36).substr(2); // remove `0.`
+ return Math.random().toString(36).substr(2); // remove `0.`
}
module.exports = function(tlen)
{
- let res = "";
- let nbRands = Math.ceil(tlen/10); //10 = min length of a rand() string
- for (let i = 0; i < nbRands; i++)
- res += randString();
- return res.substr(0, tlen);
+ let res = "";
+ let nbRands = Math.ceil(tlen/10); //10 = min length of a rand() string
+ for (let i = 0; i < nbRands; i++)
+ res += randString();
+ return res.substr(0, tlen);
}