## Installation (for developers)
0. Install git-fat https://github.com/jedbrown/git-fat
- 1. Rename public/javascripts/utils/socket\_url.js.dist into socket\_url.js and adjust its content.
+ 1. Rename public/javascripts/utils/socket\_url.js.dist into socket\_url.js
+ and adjust its content.
2. git fat init && git fat pull
3. npm i && npm start
}
else
{
- app.set('trust proxy', true); //http://dev.rdybarra.com/2016/06/23/Production-Logging-With-Morgan-In-Express/
+ // 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 }
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
+<!-- Online source: https://en.wikipedia.org/wiki/List_of_chess_variants#/media/File:Hexagonal_chess.svg -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
</g>
</g>
</g>
-</svg>
\ No newline at end of file
+</svg>
--- /dev/null
+Initial PNG images designed by Patrick Bernier,
+from a model found online (cannot remember where, sorry)
+// (Orthodox) Chess rules are defined in ChessRules class.
+// Variants generally inherit from it, and modify some parts.
+
class PiPo //Piece+Position
{
// o: {piece[p], color[c], posX[x], posY[y]}
{
this.INIT_COL_KING = {'w':-1, 'b':-1};
this.INIT_COL_ROOK = {'w':[-1,-1], 'b':[-1,-1]};
- this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; //respective squares of white and black king
+ this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; //squares of white and black king
const fenParts = fen.split(" ");
const position = fenParts[0].split("/");
for (let i=0; i<position.length; i++)
// What are the knight moves from square x,y ?
getPotentialKnightMoves(sq)
{
- return this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.KNIGHT], "oneStep");
+ return this.getSlideNJumpMoves(
+ sq, VariantRules.steps[VariantRules.KNIGHT], "oneStep");
}
// What are the bishop moves from square x,y ?
canIplay(side, [x,y])
{
- return ((side=='w' && this.moves.length%2==0) || (side=='b' && this.moves.length%2==1))
+ return ((side=='w' && this.moves.length%2==0)
+ || (side=='b' && this.moves.length%2==1))
&& this.getColor(x,y) == side;
}
return this.filterValid( this.getPotentialMovesFrom(sq) );
}
- // TODO: once a promotion is filtered, the others results are same: useless computations
+ // TODO: promotions (into R,B,N,Q) should be filtered only once
filterValid(moves)
{
if (moves.length == 0)
{
for (let j=0; j<sizeY; j++)
{
- // Next condition ... != oppCol is a little HACK to work with checkered variant
+ // Next condition "!= oppCol" = harmless hack to work with checkered variant
if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) != oppCol)
Array.prototype.push.apply(potentialMoves, this.getPotentialMovesFrom([i,j]));
}
const score = this.checkGameEnd();
eval2 = (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval);
}
- if ((color=="w" && eval2 > moves1[i].eval) || (color=="b" && eval2 < moves1[i].eval))
+ if ((color=="w" && eval2 > moves1[i].eval)
+ || (color=="b" && eval2 < moves1[i].eval))
+ {
moves1[i].eval = eval2;
+ }
this.undo(moves1[i]);
}
moves1.sort( (a,b) => { return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
const d = new Date();
const opponent = mode=="human" ? "Anonymous" : "Computer";
pgn += '[Variant "' + variant + '"]<br>';
- pgn += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) + '-' + zeroPad(d.getDate()) + '"]<br>';
+ pgn += '[Date "' + d.getFullYear() + '-' + (d.getMonth()+1) +
+ '-' + zeroPad(d.getDate()) + '"]<br>';
pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]<br>';
pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]<br>';
pgn += '[FenStart "' + fenStart + '"]<br>';
+// Game logic on a variant page
Vue.component('my-game', {
data: function() {
return {
vr: null, //object to check moves, store them, FEN..
mycolor: "w",
possibleMoves: [], //filled after each valid click/dragstart
- choices: [], //promotion pieces, or checkered captures... (contain possible pieces)
+ choices: [], //promotion pieces, or checkered captures... (as moves)
start: {}, //pixels coordinates + id of starting square (click or drag)
selectedPiece: null, //moving piece (or clicked piece)
- conn: null, //socket messages
+ conn: null, //socket connection
score: "*", //'*' means 'unfinished'
mode: "idle", //human, friend, computer or idle (when not playing)
oppid: "", //opponent ID in case of HH game
fenStart: "",
incheck: [],
pgnTxt: "",
- expert: getCookie("expert") === "1" ? true : false,
+ expert: (getCookie("expert") === "1" ? true : false),
gameId: "", //used to limit computer moves' time
};
},
{
on: { click: this.toggleExpertMode },
attrs: { "aria-label": 'Toggle expert mode' },
- style: { "padding-top": "0", "margin-top": "0" },
'class': {
"tooltip":true,
"topindicator": true,
actionArray = actionArray.concat([
h('button',
{
- style: { "margin-left": "30px" },
on: { click: e => this.undo() },
attrs: { "aria-label": 'Undo' },
- "class": { "small": smallScreen },
+ "class": {
+ "small": smallScreen,
+ "marginleft": true,
+ },
},
[h('i', { 'class': { "material-icons": true } }, "fast_rewind")]),
h('button',
[
h('button',
{
- style: { "margin-left": "30px" },
on: { click: this.undoInGame },
attrs: { "aria-label": 'Undo' },
- "class": { "small": smallScreen },
+ "class": {
+ "small": smallScreen,
+ "marginleft": true,
+ },
},
[h('i', { 'class': { "material-icons": true } }, "undo")]
),
}
}),
h('sup',
- {style: { "padding-left":"40%"} },
+ {"class": { "reserve-count": true } },
[ this.vr.reserve[this.mycolor][VariantRules.RESERVE_PIECES[i]] ]
)
]));
}
}),
h('sup',
- {style: { "padding-left":"40%"} },
+ {"class": { "reserve-count": true } },
[ this.vr.reserve[oppCol][VariantRules.RESERVE_PIECES[i]] ]
)
]));
}
let reserves = h('div',
{
- 'class':{'game':true},
- style: {"margin-bottom": "20px"},
+ 'class':{
+ 'game': true,
+ "reserve-div": true,
+ },
},
[
h('div',
{
- 'class': { 'row': true },
- style: {"margin-bottom": "15px"},
+ 'class': {
+ 'row': true,
+ "reserve-row-1": true,
+ },
},
myReservePiecesArray
),
this.myid = continuation ? localStorage.getItem("myid") : getRandString();
if (!continuation)
{
- // HACK: play a small silent sound to allow "new game" sound later if tab not focused
+ // HACK: play a small silent sound to allow "new game" sound later
+ // if tab not focused (TODO: does it really work ?!)
new Audio("/sounds/silent.mp3").play().then(() => {}).catch(err => {});
}
this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant);
switch (data.code)
{
case "newgame": //opponent found
- this.newGame("human", data.fen, data.color, data.oppid); //oppid: opponent socket ID
+ // oppid: opponent socket ID
+ this.newGame("human", data.fen, data.color, data.oppid);
break;
case "newmove": //..he played!
this.play(data.move, "animate");
// Prepare and trigger download link
let downloadAnchor = document.getElementById("download");
downloadAnchor.setAttribute("download", "game.pgn");
- downloadAnchor.href = "data:text/plain;charset=utf-8," + encodeURIComponent(content);
+ downloadAnchor.href = "data:text/plain;charset=utf-8," +
+ encodeURIComponent(content);
downloadAnchor.click();
},
endGame: function(score) {
},
playComputerMove: function() {
const timeStart = Date.now();
- const nbMoves = this.vr.moves.length; //using played moves to know if search finished
+ // We use moves' count to know if search finished:
+ const nbMoves = this.vr.moves.length;
const gameId = this.gameId; //to know if game was reset before timer end
setTimeout(
() => {
const iCanPlay = this.mode!="idle"
&& (this.mode=="friend" || this.vr.canIplay(this.mycolor,startSquare));
this.possibleMoves = iCanPlay ? this.vr.getPossibleMovesFrom(startSquare) : [];
- // Next line add moving piece just after current image (required for Crazyhouse reserve)
+ // Next line add moving piece just after current image
+ // (required for Crazyhouse reserve)
e.target.parentNode.insertBefore(this.selectedPiece, e.target.nextSibling);
}
},
return;
e = e || window.event;
// Read drop target (or parentElement, parentNode... if type == "img")
- this.selectedPiece.style.zIndex = -3000; //HACK to find square from final coordinates
+ this.selectedPiece.style.zIndex = -3000; //HACK to find square from final coords
const [offsetX,offsetY] = !!e.clientX
? [e.clientX,e.clientY]
: [e.changedTouches[0].pageX, e.changedTouches[0].pageY];
let landing = document.elementFromPoint(offsetX, offsetY);
this.selectedPiece.style.zIndex = 3000;
- while (landing.tagName == "IMG") //classList.contains(piece) fails because of mark/highlight
+ // Next condition: classList.contains(piece) fails because of marks
+ while (landing.tagName == "IMG")
landing = landing.parentNode;
- if (this.start.id == landing.id) //a click: selectedPiece and possibleMoves already filled
+ if (this.start.id == landing.id)
+ {
+ // A click: selectedPiece and possibleMoves are already filled
return;
+ }
// OK: process move attempt
let endSquare = this.getSquareFromId(landing.id);
let moves = this.findMatchingMoves(endSquare);
let translation = {x:rectEnd.x-rectStart.x, y:rectEnd.y-rectStart.y};
let movingPiece =
document.querySelector("#" + this.getSquareId(move.start) + " > img.piece");
- // HACK for animation (with positive translate, image slides "under background"...)
+ // HACK for animation (with positive translate, image slides "under background")
// Possible improvement: just alter squares on the piece's way...
squares = document.getElementsByClassName("board");
for (let i=0; i<squares.length; i++)
if (square.id != this.getSquareId(move.start))
square.style.zIndex = "-1";
}
- movingPiece.style.transform = "translate(" + translation.x + "px," + translation.y + "px)";
+ movingPiece.style.transform = "translate(" + translation.x + "px," +
+ translation.y + "px)";
movingPiece.style.transitionDuration = "0.2s";
movingPiece.style.zIndex = "3000";
setTimeout( () => {
+// Load rules on variant page
Vue.component('my-rules', {
data: function() {
return { content: "" };
};
xhr.open("GET", "/rules/" + variant, true);
xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
- xhr.send(null); //TODO: or just xhr.send() ?
+ xhr.send();
},
methods: {
drawDiag: function(fen) {
boardDiv += "<div class='row'>";
for (let j=startY; j>=0 && j<sizeY; j+=inc)
{
- // NOTE: 'board' to distinguish from coords
boardDiv += "<div class='board board" + sizeY + " " +
((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") + "'>";
if (markArray.length>0 && markArray[i][j])
+// Show a variant summary on index
Vue.component('my-variant-summary', {
props: ['vobj'],
template: `
+// Javascript for index page: mostly counters updating
new Vue({
el: "#indexPage",
data: {
document.getElementById("modal-b4welcome").checked = false;
document.getElementById("modal-welcome").checked = true;
},
+ markAsVisited: function() {
+ setCookie('visited', '1');
+ document.getElementById('modal-welcome').checked = false;
+ },
},
});
// Source: https://www.quirksmode.org/js/cookies.html
-
function setCookie(name,value)
{
var date = new Date();
return null;
}
+// Random (enough) string for socket and game IDs
function getRandString()
{
- // Random enough (for socket and game IDs)
return (Date.now().toString(36) + Math.random().toString(36).substr(2, 7))
.toUpperCase();
}
if (this.board[i][j] != VariantRules.EMPTY && this.getColor(i,j) == color)
{
const mirrorSide =
- (Object.keys(VariantRules.ALICE_CODES).includes(this.getPiece(i,j)) ? 1 : 2);
+ Object.keys(VariantRules.ALICE_CODES).includes(this.getPiece(i,j))
+ ? 1
+ : 2;
Array.prototype.push.apply(potentialMoves,
this.getPotentialMovesFrom([i,j], sideBoard[mirrorSide-1]));
}
}
static get VALUES() {
- return {
- 'p': 1,
- 's': 1,
- 'r': 5,
- 'u': 5,
- 'n': 3,
- 'o': 3,
- 'b': 3,
- 'c': 3,
- 'q': 9,
- 't': 9,
- 'k': 1000,
- 'l': 1000
- };
+ return Object.assign(
+ ChessRules.VALUES,
+ {
+ 's': 1,
+ 'u': 5,
+ 'o': 3,
+ 'c': 3,
+ 't': 9,
+ 'l': 1000,
+ }
+ );
}
getNotation(move)
class AntikingRules extends ChessRules
{
- // Path to pieces
static getPpath(b)
{
return b[1]=='a' ? "Antiking/"+b : b;
return color == "w" ? "0-1" : "1-0";
}
- // Pieces values (TODO: use Object.assign() + ChessRules.VALUES ?)
static get VALUES() {
- return {
- 'p': 1,
- 'r': 5,
- 'n': 3,
- 'b': 3,
- 'q': 9,
- 'k': 1000,
- 'a': 1000
- };
+ return Object.assign(
+ ChessRules.VALUES,
+ { 'a': 1000 }
+ );
}
static GenRandInitFen()
let fen = pieces["b"].join("") + "/" + ranks23_black +
"/8/8/" +
ranks23_white + "/" + pieces["w"].join("").toUpperCase() +
- " 1111"; //add flags
+ " 1111";
return fen;
}
}
moves.forEach(m => {
if (m.vanish.length > 1 && m.appear.length <= 1) //avoid castles
{
- // Explosion! TODO: drop moves which explode our king here
+ // 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)
{
if (x>=0 && x<8 && y>=0 && y<8 && this.board[x][y] != VariantRules.EMPTY
&& this.getPiece(x,y) != VariantRules.PAWN)
{
- m.vanish.push(new PiPo({p:this.getPiece(x,y),c:this.getColor(x,y),x:x,y:y}));
+ 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};
isAttacked(sq, colors)
{
- if (this.getPiece(sq[0],sq[1]) == VariantRules.KING && this.isAttackedByKing(sq, colors))
+ if (this.getPiece(sq[0],sq[1]) == VariantRules.KING
+ && this.isAttackedByKing(sq, colors))
+ {
return false; //king cannot take...
+ }
return (this.isAttackedByPawn(sq, colors)
|| this.isAttackedByRook(sq, colors)
|| this.isAttackedByKnight(sq, colors)
return color == "w" ? "0-1" : "1-0";
if (!this.isAttacked(kp, [this.getOppCol(color)]))
return "1/2";
- // Checkmate
- return color == "w" ? "0-1" : "1-0";
+ return color == "w" ? "0-1" : "1-0"; //checkmate
}
}
canIplay(side, [x,y])
{
- return ((side=='w' && this.moves.length%2==0) || (side=='b' && this.moves.length%2==1))
+ return ((side=='w' && this.moves.length%2==0)
+ || (side=='b' && this.moves.length%2==1))
&& [side,'c'].includes(this.getColor(x,y));
}
this.play(move);
const color = this.turn;
this.moves.push(move); //artifically change turn, for checkered pawns (TODO)
- const kingAttacked = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c']);
+ const kingAttacked = this.isAttacked(
+ this.kingPos[color], [this.getOppCol(color),'c']);
let res = kingAttacked
? [ JSON.parse(JSON.stringify(this.kingPos[color])) ] //need to duplicate!
: [ ];
{
// Capture
let startColumn = String.fromCharCode(97 + move.start.y);
- notation = startColumn + "x" + finalSquare + "=" + move.appear[0].p.toUpperCase();
+ notation = startColumn + "x" + finalSquare +
+ "=" + move.appear[0].p.toUpperCase();
}
else //no capture
{
return color + VariantRules.RESERVE_PIECES[index];
}
- // Put an ordering on reserve pieces
+ // Ordering on reserve pieces
static get RESERVE_PIECES() {
const V = VariantRules;
return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
const sizeX = VariantRules.size[0];
if (x >= sizeX)
{
- // Reserves, outside of board: x == sizeX
+ // Reserves, outside of board: x == sizeX(+1)
return this.getReserveMoves([x,y]);
}
// Standard moves
return this.turn == "w" ? "0-1" : "1-0";
}
- // Very negative (resp. positive) if white (reps. black) pieces set is incomplete
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) * VariantRules.INFINITY;
}
return super.evalPosition();
-//https://www.chessvariants.com/large.dir/freeling.html
+// NOTE: initial setup differs from the original; see
+// https://www.chessvariants.com/large.dir/freeling.html
class GrandRules extends ChessRules
{
static getPpath(b)
}
}
// Captures
- if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
+ 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]));
- if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
+ }
+ if (y<sizeY-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]));
+ }
}
if (lastRanks.includes(x+shift))
if (this.board[x+shift][y] == V.EMPTY)
moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
// Captures
- if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
+ 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:p}));
- if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
+ }
+ if (y<sizeY-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:p}));
+ }
});
}
{
const V = VariantRules;
return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
- || this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
+ || this.isAttackedBySlideNJump(
+ sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
}
isAttackedByCardinal(sq, colors)
{
const V = VariantRules;
return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
- || this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
+ || this.isAttackedBySlideNJump(
+ sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
}
updateVariables(move)
// TODO: this function could be generalized and shared better
static GenRandInitFen()
{
- let pieces = [new Array(10), new Array(10)];
+ let pieces = { "w": new Array(10), "b": new Array(10) };
// Shuffle pieces on first and last rank
- for (let c = 0; c <= 1; c++)
+ for (let c of ["w","b"])
{
let positions = _.range(10);
pieces[c][knight2Pos] = 'n';
pieces[c][rook2Pos] = 'r';
}
- let fen = pieces[0].join("") +
+ let fen = pieces["b"].join("") +
"/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
- pieces[1].join("").toUpperCase() +
+ pieces["w"].join("").toUpperCase() +
" 1111";
return fen;
}
const lastRank = (color == "w" ? 0 : sizeX-1);
if (x+shift == lastRank)
{
- let p = V.KING;
// Normal move
if (this.board[x+shift][y] == V.EMPTY)
- moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
+ 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:p}));
- if (y<sizeY-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:p}));
+ 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<sizeY-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;
getFlagsFen()
{
- return "";
+ return "-";
}
checkGameEnd()
this.kingPos[oppCol] = [-1,-1];
this.castleFlags[oppCol] = [false,false];
}
- // Did we move our (init) rooks or opponents' ones ?
+ // Did we magnetically move our (init) rooks or opponents' ones ?
const firstRank = (c == "w" ? 7 : 0);
const oppFirstRank = 7 - firstRank;
const oppCol = this.getOppCol(c);
-//https://www.chessvariants.com/large.dir/wildebeest.html
class WildebeestRules extends ChessRules
{
static getPpath(b)
}
}
// Captures
- if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
+ 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]));
- if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
+ }
+ if (y<sizeY-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]));
+ }
}
if (x+shift == lastRank)
if (this.board[x+shift][y] == V.EMPTY)
moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
// Captures
- if (y>0 && this.canTake([x,y], [x+shift,y-1]) && this.board[x+shift][y-1] != V.EMPTY)
+ 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:p}));
- if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1]) && this.board[x+shift][y+1] != V.EMPTY)
+ }
+ if (y<sizeY-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:p}));
+ }
});
}
getPotentialCamelMoves(sq)
{
- return this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.CAMEL], "oneStep");
+ return this.getSlideNJumpMoves(
+ sq, VariantRules.steps[VariantRules.CAMEL], "oneStep");
}
getPotentialWildebeestMoves(sq)
{
const V = VariantRules;
- return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
+ return this.getSlideNJumpMoves(
+ sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
}
isAttacked(sq, colors)
static GenRandInitFen()
{
- let pieces = [new Array(10), new Array(10)];
- // Shuffle pieces on first and last rank
- for (let c = 0; c <= 1; c++)
+ let pieces = { "w": new Array(10), "b": new Array(10) };
+ for (let c of ["w","b"])
{
let positions = _.range(11);
let randIndexes_tmp = _.sample(_.range(5), 2).map(i => { return 2*i+1; });
let bishop2Pos = positions[randIndexes_tmp[0]];
let camel2Pos = positions[randIndexes_tmp[1]];
- // Remove chosen squares
- for (let idx of randIndexes.concat(randIndexes_tmp).sort((a,b) => { return b-a; }))
+ for (let idx of randIndexes.concat(randIndexes_tmp)
+ .sort((a,b) => { return b-a; })) //largest indices first
+ {
positions.splice(idx, 1);
+ }
- // Get random squares for knights
let randIndex = _.random(6);
let knight1Pos = positions[randIndex];
positions.splice(randIndex, 1);
let knight2Pos = positions[randIndex];
positions.splice(randIndex, 1);
- // Get random square for queen
randIndex = _.random(4);
let queenPos = positions[randIndex];
positions.splice(randIndex, 1);
- // ...random square for wildebeest
+ // Random square for wildebeest
randIndex = _.random(3);
let wildebeestPos = 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][knight2Pos] = 'n';
pieces[c][rook2Pos] = 'r';
}
- let fen = pieces[0].join("") +
+ let fen = pieces["b"].join("") +
"/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
- pieces[1].join("").toUpperCase() +
+ pieces["w"].join("").toUpperCase() +
" 1111";
return fen;
}
return undefined;
}
- // TODO: some duplicated code in 2 next functions
+ // TODO(?): some duplicated code in 2 next functions
getSlideNJumpMoves([x,y], steps, oneStep)
{
const color = this.getColor(x,y);
// Find possible captures from a square: look in every direction!
findCaptures(sq)
{
- var moves = [];
+ let moves = [];
- // PAWN
Array.prototype.push.apply(moves, this.findCaptures_aux(sq, VariantRules.PAWN));
-
- // ROOK
Array.prototype.push.apply(moves, this.findCaptures_aux(sq, VariantRules.ROOK));
-
- // KNIGHT
Array.prototype.push.apply(moves, this.findCaptures_aux(sq, VariantRules.KNIGHT));
-
- // BISHOP
Array.prototype.push.apply(moves, this.findCaptures_aux(sq, VariantRules.BISHOP));
-
- // QUEEN
Array.prototype.push.apply(moves, this.findCaptures_aux(sq, VariantRules.QUEEN));
return moves;
getPotentialRookMoves(sq)
{
- let noCaptures = this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.ROOK]);
+ let noCaptures = this.getSlideNJumpMoves(
+ sq, VariantRules.steps[VariantRules.ROOK]);
let captures = this.findCaptures(sq);
return noCaptures.concat(captures);
}
getPotentialKnightMoves(sq)
{
- let noCaptures = this.getSlideNJumpMoves(sq,
- VariantRules.steps[VariantRules.KNIGHT], "oneStep");
+ let noCaptures = this.getSlideNJumpMoves(
+ sq, VariantRules.steps[VariantRules.KNIGHT], "oneStep");
let captures = this.findCaptures(sq);
return noCaptures.concat(captures);
}
getPotentialBishopMoves(sq)
{
- let noCaptures = this.getSlideNJumpMoves(sq, VariantRules.steps[VariantRules.BISHOP]);
+ let noCaptures = this.getSlideNJumpMoves(
+ sq, VariantRules.steps[VariantRules.BISHOP]);
let captures = this.findCaptures(sq);
return noCaptures.concat(captures);
}
getPotentialQueenMoves(sq)
{
const V = VariantRules;
- let noCaptures =
- this.getSlideNJumpMoves(sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
+ let noCaptures = this.getSlideNJumpMoves(
+ sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
let captures = this.findCaptures(sq);
return noCaptures.concat(captures);
}
.card > h3.section.red
color: #cc3300
+.main-title
+ font-style: italic
+
#welcome, #help
max-height: 100vh
max-width: 90vw
#welcome ul > li
font-family: monospace
+.read-this
+ color: blue
+ text-decoration: underline
+ cursor: pointer
+
+.emphasis
+ font-style: italic
+ color: purple
+
+.disable-msg
+ cursor: pointer
+ color: darkred
+
+.smallfont
+ font-size: 0.8em
+
table.list-table
width: 300px
margin: 0 auto
float: right
margin: 0 20px 10px 0
+.marginleft
+ margin-left: 30px
+
+.reserve-count
+ padding-left: 40%
+
+.reserve-div
+ margin-bottom: 20px
+
+.reserve-row-1
+ margin-bottom: 15px
+
.connected
background-color: green
background-color: red
.expert-switch
- padding: 5px 10px
+ padding: 0 10px 5px 10px
+ margin: 0
.expert-mode, button.expert-mode:hover
background-color: #ffcc99
.container#indexPage
.row
.col-sm-12
- h1.text-center(style="font-style:italic") Welcome to v[ariant] chess club !
+ h1.text-center.main-title Welcome to v[ariant] chess club !
h2.text-center
- span.help(onClick="document.getElementById('modal-help').checked=true") Help ?
+ span.help(onClick="document.getElementById('modal-help').checked=true")
+ | Help ?
a(href="/demo.webm") Demo !
input#modal-help.modal(type="checkbox")
div(role="dialog")
label.modal-close(for="modal-help")
h3.blue.section Tips
p.section
- span.conditional-jump On a variant page, read the rules by clicking on the title "<Variant> rules".
- span.conditional-jump Try playing against a human: click the leftmost "new game" button :)
- | ...then, while waiting you can play against a (rather weak) bot or a friend.
+ span.conditional-jump
+ | On a variant page, read the rules by clicking on the title
+ | "<Variant> rules".
+ span.conditional-jump
+ | Try playing against a human: click the leftmost "new game" button :)
+ | ...then, while waiting you can play against a (rather weak) bot
+ | or a friend.
// TODO? On the index page, try typing the first letters of a variant.
h3.blue.section Comments
p.section.
Games are untimed, and played anonymously. #[br]
No chat, to rather focus on the moves :)
h3.red.section Bug report
- p.section.
- If you find a bug in a game, please follow this procedure: #[br]
- 1. stop playing: click on the resign button; #[br]
- 2. click on the PGN to download it; #[br]
- 3. send an email to
- #[a(href="mailto:contact@vchess.club?subject=[vchess.club] bug report") contact@vchess.club]
- with relevant comments and the PGN attached. Thank you!
+ p.section
+ | If you find a bug in a game, please follow this procedure: #[br]
+ | 1. stop playing: click on the resign button; #[br]
+ | 2. click on the PGN to download it; #[br]
+ | 3. send an email to
+ a(href="mailto:contact@vchess.club?subject=[vchess.club] bug report")
+ | contact@vchess.club
+ | with relevant comments and the PGN attached. Thank you!
input#modal-b4welcome.modal(type="checkbox")
div(role="dialog")
#b4welcome.card.text-center
label.modal-close(for="modal-b4welcome")
h3.blue.section First visit?
p Please
- span(style="color:blue;text-decoration:underline;cursor:pointer" @click="showWelcomeMsg") read this
+ span.read-this(@click="showWelcomeMsg") read this
span before playing ☺
input#modal-welcome.modal(type="checkbox")
div(role="dialog")
As suggested by the picture, a variant setup generally
looks more or less like a chessboard with regular pieces
(otherwise it's no longer a variant but a whole new game!).
- p(style="font-style:italic;color:purple") However...
+ p.emphasis However...
p Each variant has its own new rules, which can involve
table.list-table
tbody
td ...and so on
.section
p.
- Example: imagine that a capture is an atomic explosion, wiping all adjacent squares
- – except the pawns, which as cockroaches can resist this kind of event.
+ Example: imagine that a capture is an atomic explosion, wiping all
+ adjacent squares – except the pawns, which as cockroaches can
+ resist this kind of event.
p Also state a goal: make the opponent's king explode.
p → Congrats, you defined Atomic chess! (Playable here)
.section
- p(style="font-style:italic;color:purple") OK, this all sounds interesting, but why would that be fun?
+ p.emphasis OK, this all sounds interesting, but why would that be fun?
p.
Because all games here start with a random setup: no more boring
openings memorization, you have to rely on your chess skills only :)
p Moreover, I claim that the chosen variants here are fun to play :P
+ -
+ var wikipediaUrl = "https://en.wikipedia.org/wiki/" +
+ "List_of_chess_variants#/media/File:Hexagonal_chess.svg";
p.
For informations about hundreds (if not thousands!) of variants, you
- can visit the excellent #[a(href="https://www.chessvariants.com/") chessvariants] website.
- p(style="cursor:pointer;color:darkred" onClick="setCookie('visited','1');document.getElementById('modal-welcome').checked=false") Click here to not show this message next time
- p(style="font-size:0.8em") Image credit: #[a(href="https://en.wikipedia.org/wiki/List_of_chess_variants#/media/File:Hexagonal_chess.svg") Wikpedia]
+ can visit the excellent
+ #[a(href="https://www.chessvariants.com/") chessvariants] website.
+ p.disable-msg(@click="markAsVisited")
+ | Click here to not show this message next time
+ p.smallfont Image credit: #[a(href=wikipediaUrl) Wikipedia]
.row
my-variant-summary(
v-for="(v,idx) in sortedCounts",
head
meta(charset="UTF-8")
title vchess - #{title}
- meta(name="viewport", content="width=device-width, initial-scale=1")
- meta(name="msapplication-config", content="/images/favicon/browserconfig.xml")
- meta(name="theme-color", content="#ffffff")
- link(rel="stylesheet", href="//cdnjs.cloudflare.com/ajax/libs/mini.css/3.0.0/mini-default.min.css")
- link(rel="stylesheet", href="//fonts.googleapis.com/css?family=Open+Sans:400,700")
- link(rel="apple-touch-icon", sizes="180x180", href="/images/favicon/apple-touch-icon.png")
- link(rel="icon", type="image/png", sizes="32x32", href="/images/favicon/favicon-32x32.png")
- link(rel="icon", type="image/png", sizes="16x16", href="/images/favicon/favicon-16x16.png")
- link(rel="manifest", href="/images/favicon/manifest.json")
- link(rel="mask-icon", href="/images/favicon/safari-pinned-tab.svg", color="#5bbad5")
- link(rel="shortcut icon", href="/images/favicon/favicon.ico")
- link(rel="stylesheet", href="/stylesheets/layout.css")
+ meta(name="viewport" content="width=device-width, initial-scale=1")
+ meta(name="msapplication-config" content="/images/favicon/browserconfig.xml")
+ meta(name="theme-color" content="#ffffff")
+ link(rel="stylesheet"
+ href="//cdnjs.cloudflare.com/ajax/libs/mini.css/3.0.0/mini-default.min.css")
+ link(rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans:400,700")
+ link(rel="apple-touch-icon" sizes="180x180"
+ href="/images/favicon/apple-touch-icon.png")
+ link(rel="icon" type="image/png" sizes="32x32"
+ href="/images/favicon/favicon-32x32.png")
+ link(rel="icon" type="image/png" sizes="16x16"
+ href="/images/favicon/favicon-16x16.png")
+ link(rel="manifest" href="/images/favicon/manifest.json")
+ link(rel="mask-icon" href="/images/favicon/safari-pinned-tab.svg" color="#5bbad5")
+ link(rel="shortcut icon" href="/images/favicon/favicon.ico")
+ link(rel="stylesheet" href="/stylesheets/layout.css")
block css
body
p.boxed
- | Every move played ends up on another board (the "other side of the mirror"). So there are two boards. All pieces start on board 1.
+ | Every move played ends up on another board (the "other side of the mirror").
+ | So there are two boards. All pieces start on board 1.
h3 Specifications
h3 Basics
p
- | Two boards are used in this variant. Pieces from board 2 are represented on the main board, upside down.
+ | Two boards are used in this variant. Pieces from board 2 are represented on
+ | the main board, upside down.
| Any move played must be valid on the board it is played on.
| In addition, the final square should not be occupied by a piece from the other board
| (thus allowing to represent all on one board).
h3 End of the game
-p As in the orthodox game, win by checkmating the king. It shouldn't be able to escape the check, not even by moving to the other board.
+p
+ | As in the orthodox game, win by checkmating the king. It shouldn't be able to
+ | escape the check, not even by moving to the other board.
p Note: en-passant and castle occur as they do in the standard game.
| Alice chess pages on
a(href="https://www.chessvariants.com/other.dir/alice.html") chessvariants.com
| and on
- a(href="https://www.schemingmind.com/journalarticle.aspx?article_id=9") schemingmind.com
+ a(href="https://www.schemingmind.com/journalarticle.aspx?article_id=9")
+ | schemingmind.com
| .
p.boxed
- | You have a king and an antiking. King must stay away from checks, but antiking must always stay in check.
- | Antiking captures his own kind.
+ | You have a king and an antiking. King must stay away from checks, but antiking
+ | must always stay in check. Antiking captures his own kind.
h3 Specifications
p
| The additional piece is a royal figure, thus cannot be captured.
- | It captures the pieces of his color (to help checkmate opponent antiking, but by doing so it also make standard checkmate more difficult...).
+ | It captures the pieces of his color (to help checkmate opponent antiking,
+ | but by doing so it also make standard checkmate more difficult...).
| It should always remains under check (if it cannot, game is over).
figure.diagram-container
p Note 1: athough antiking captures his color, it doesn't check his king.
-p Note 2: since it would allow a basic tactic (keep antiking touching opponent's king), kings do not attack antikings.
+p
+ | Note 2: since it would allow a basic tactic (keep antiking touching opponent's
+ | king), kings do not attack antikings.
p Note 3: an antiking does not check opponent's antiking.
h3 Credits
p
- a(href="https://www.chessvariants.com/diffobjective.dir/anti-king-chess.html") Antiking chess
+ a(href="https://www.chessvariants.com/diffobjective.dir/anti-king-chess.html")
+ | Antiking chess
| on chessvariants.com.
p.boxed
- | All captures result in an "explosion" through which all surrounding white and black pieces other than pawns are removed from play.
+ | All captures result in an "explosion" through which all surrounding
+ | pieces other than pawns are removed from the board.
h3 Specifications
h3 Basics
p
- | When a piece captures an opponent figure on some square S, all pieces sitting on a square reachable by a king move from S are removed.
+ | When a piece captures an opponent figure on some square S, all pieces sitting
+ | on a square reachable by a king move from S are removed.
| The pawns, however, remain: they have to be taken directly to disappear.
figure.diagram-container
p Explosions have priority: a checkmate followed by a king explosion loses.
-p Note: since suicide is forbidden, a king can touch the opponent king - and become immune to checks.
+p
+ | Note: since suicide is forbidden, a king can touch the opponent king -
+ | and become immune to checks.
h3 Credits
p
- | Many resources can be found on the web (this variation is played on lichess and FICS, among others).
- | This game was played first in 1995 at the German Internet Chess Server (GICS) according to Wikipedia.
+ | Many resources can be found on the web (this variation is played on lichess and
+ | FICS, among others).
+ | This game was played first in 1995 at the German Internet Chess Server (GICS)
+ | according to Wikipedia.
p.boxed
- | The capture of an enemy piece produces a new "checkered" piece belonging to both players.
+ | The capture of an enemy piece produces a new "checkered" piece belonging
+ | to both players.
figure.showPieces.center-align
img(src="/images/tmp_checkered/cp.png")
h3 Basics
ol
- li Each capture produces a new piece, taking on nature of the capturing or captured one.
- li The new piece arising from a capture has a new color: "checkered", as illustrated above.
- li All checkered pieces belong to the player in turn and can capture the opponents pieces.
+ li
+ | Each capture produces a new piece, taking on nature of
+ | the capturing or captured one.
+ li
+ | The new piece arising from a capture has a new color:
+ | "checkered", as illustrated above.
+ li
+ | All checkered pieces belong to the player in turn and can
+ | capture the opponents pieces.
span Remarks:
ul
.diagram
| fen:2kr4/pp6/2p5/4ss1r/1P2ns1P/2Np4/P1P1P1BP/R2o1RK1:
figcaption.
- Black plays Rxh4=P. (Checkered pawn to) h5 is allowed then, because piece's nature changed.
+ Black plays Rxh4=P. (Checkered pawn to) h5 is allowed then,
+ because piece's nature changed.
h3 Pawn moves
p.warn This stage is not (and probably will never be) implemented.
p.
- During the game one of the two players can decide to take control of the checkered pieces.
+ During the game one of the two players can decide to take control of the
+ checkered pieces.
They thus become autonomous and vulnerable to being captured - stage 2 begins.
The other player is in charge of both the white and black pieces, and tries to
eliminate checkered pieces.
h4 Variant of stage 2
p.
- An observer could decide to join the game by taking the checkered pieces at any moment.
+ An observer could decide to join the game by taking the checkered pieces
+ at any moment.
It then becomes a chess game with three players, with some subtelties to be resolved.
It was tested in some (real life) games organised by the variant creator.
p.boxed
- | Every captured piece can be re-used by the capturer, landing it anywhere instead of moving a piece.
+ | Every captured piece can be re-used by the capturer,
+ | landing it anywhere instead of moving a piece.
h3 Specifications
p.boxed
- | Two new pieces: marshall and cardinal. Bigger board. Orthodox rules with a few adaptations.
+ | Two new pieces: marshall and cardinal. Bigger board.
+ | Orthodox rules with a few adaptations.
h3 Specifications
p.
Note: I changed the author's starting position, to facilitate random start.
- Thus the castling rule was introduced compared to the rules described on chessvariants.com.
+ Thus the castling rule was introduced compared to the rules described
+ on chessvariants.com.
h3 Credits
p.boxed
- | Each piece has a charge generating a magnetic field, attracting enemy pieces while repelling others.
+ | Each piece has a charge generating a magnetic field,
+ | attracting enemy pieces while repelling others.
h3 Specifications
h3 Basics
p
- | Every piece has a charge generating a magnetic field, except the two kings which have a neutral charge.
- | Pieces of the same color have let's say a positive charge, while the others have a negative charge.
+ | Every piece has a charge generating a magnetic field, except the two kings
+ | which have a neutral charge.
+ | Pieces of the same color have let's say a positive charge,
+ | while the others have a negative charge.
| So, after each move some pieces are attracted while others are repelled.
figure.diagram-container
p.boxed
- | In addition to standard moves, a piece can be exchanged with an adjacent friendly unit.
+ | In addition to standard moves, a piece can be exchanged
+ | with an adjacent friendly unit.
h3 Specifications
p.boxed
- | Two new pieces: camel and wildebeest. Bigger board. Orthodox rules with a few adaptations.
+ | Two new pieces: camel and wildebeest. Bigger board.
+ | Orthodox rules with a few adaptations.
h3 Specifications
p.boxed
| Zen chess only change one thing to the standard rules:
- | a piece A captures an opponent piece B if and only if B can take A according to the orthodox rules.
+ | a piece A captures an opponent piece B if and only if B can take A
+ | according to the orthodox rules.
figure.diagram-container
.diagram
| fen:8/8/8/3r1k2/8/8/3K4/8:
figcaption The white king can take the rook
-p However, the king is attacked in the same way as in regular chess - and it's the only exception.
+p.
+ However, the king is attacked in the same way as in regular chess -
+ and it's the only exception.
figure.diagram-container
.diagram
| fen:r7/2n5/1q6/5k2/8/8/K7/8:
- figcaption The king cannot take on a8 because it's guarded by the knight: it's checkmate
+ figcaption.
+ The king cannot take on a8 because it's guarded by the knight: it's checkmate
h3 Credits
p.
- Very few resources about this variation: #[a(href="http://play.chessvariants.org/erf/ZenChess.html") this webpage]
+ Very few resources about this variation:
+ #[a(href="http://play.chessvariants.org/erf/ZenChess.html") this webpage]
and #[a(href="http://www.pathguy.com/chess/ZenChess.htm") this one].
Ed Friedlander developed the Zen Chess applet from the link above.
extends layout
block css
- link(rel="stylesheet", href="//fonts.googleapis.com/icon?family=Material+Icons")
- link(rel="stylesheet", href="/stylesheets/variant.css")
+ link(rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons")
+ link(rel="stylesheet" href="/stylesheets/variant.css")
block content
.container#variantPage
.row
.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
- h4.rulesTitle.text-center(v-on:click="displayRules=!displayRules") #{variant} Rules
+ h4.rulesTitle.text-center(v-on:click="displayRules=!displayRules")
+ | #{variant} Rules
my-rules(v-show="displayRules")
.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
my-game