const vname = $.getElementById("selectVariant").value;
const color = $.getElementById("selectColor").value;
for (const select of $.querySelectorAll("#gameOptions select")) {
- const value = parseInt(select.value, 10) || select.value;
+ let value = parseInt(select.value, 10);
+ if (isNaN(value)) //not an integer
+ value = select.value;
options[ select.id.split("_")[1] ] = value;
}
for (const input of $.querySelectorAll("#gameOptions input")) {
if (j == options.length)
break;
const opt = options[j];
- if (!opt[1])
+ if (!opt[1]) //includes 0 and false (lighter display)
continue;
htmlContent +=
'<span class="option">' +
return [-1, -1]; //king not found
}
- filterValid(moves) {
+ // Some variants (e.g. Refusal) may need to check opponent moves too
+ filterValid(moves, color) {
if (moves.length == 0)
return [];
- const color = this.turn;
+ if (!color)
+ color = this.turn;
const oppCol = C.GetOppCol(color);
if (this.options["balance"] && [1, 3].includes(this.movesCount)) {
// Forbid moves either giving check or exploding opponent's king:
// {name: 'Relayup', desc: 'Upgrade pieces', disp: 'Relay-up'},
{name: 'Rifle', desc: 'Shoot pieces'},
{name: 'Recycle', desc: 'Reuse pieces'},
-// {name: 'Refusal', desc: 'Do not play that!'},
+ {name: 'Refusal', desc: 'Do not play that!'},
// {name: 'Rollerball', desc: 'As in the movie'},
// {name: 'Rococo', desc: 'Capture on the edge'},
// {name: 'Royalrace', desc: 'Kings cross the 11x11 board', disp: 'Royal Race'},
-<p>The capturer absorbs abilities of the captured piece (except for pawns and king).</p>
+<p>
+ The capturer absorbs the abilities of the captured piece
+ (except for pawns and king).
+</p>
-<p>The capturer explodes after each capture, as well as all pieces standing on the adjacent squares - pawns excepted.</p>
+<p>
+ The capturer explodes after each capture, as well as all pieces standing
+ on the adjacent squares - pawns excepted.
+</p>
<p>Win by checkmate or by exploding the enemy king.</p>
-<p>White plays first, then Black plays two moves, then White plays two moves, and after that the game proceeds normally.</p>
+<p>
+ White plays first, then Black plays two moves, then White plays two moves,
+ and after that the game proceeds normally.
+</p>
<p>See <a href="https://arxiv.org/abs/2108.02547">the (draft) article</a>.</p>
't': {"class": ["immobilized", "queen"]},
'l': {"class": ["immobilized", "king"]}
};
- return Object.assign({}, specials, bowsered, super.pieces(color, x, y));
+ return Object.assign(
+ {
+ 'y': {
+ // Virtual piece for "king remote shell captures"
+ moves: [],
+ attack: [
+ {
+ steps: [
+ [0, 1], [0, -1], [1, 0], [-1, 0],
+ [1, 1], [1, -1], [-1, 1], [-1, -1]
+ ]
+ }
+ ]
+ }
+ },
+ specials, bowsered, super.pieces(color, x, y));
}
genRandInitFen(seed) {
case 'b':
case 'r':
// Explicitely listing types to avoid moving immobilized piece
- moves = super.getPotentialMovesOf(piece, [x, y]);
+ moves = this.getPotentialMovesOf(piece, [x, y]);
break;
}
}
}
}
for (let shiftY of [-1, 1]) {
+ const nextY = this.getY(y + shiftY);
if (
- y + shiftY >= 0 &&
- y + shiftY < this.size.y &&
- this.board[x + shiftX][y + shiftY] != "" &&
+ nextY >= 0 &&
+ nextY < this.size.y &&
+ this.board[x + shiftX][nextY] != "" &&
// Pawns cannot capture invisible queen this way!
- this.getPiece(x + shiftX, y + shiftY) != 'i' &&
- ['a', oppCol].includes(this.getColor(x + shiftX, y + shiftY))
+ this.getPiece(x + shiftX, nextY) != 'i' &&
+ ['a', oppCol].includes(this.getColor(x + shiftX, nextY))
) {
- moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
+ moves.push(this.getBasicMove([x, y], [x + shiftX, nextY]));
}
}
- super.pawnPostProcess(moves, color, oppCol);
- // Add mushroom on before-last square
+ this.pawnPostProcess(moves, color, oppCol);
+ // Add mushroom on before-last square (+ potential segments)
moves.forEach(m => {
- let revStep = [m.start.x - m.end.x, m.start.y - m.end.y];
- for (let i of [0, 1])
- revStep[i] = revStep[i] / Math.abs(revStep[i]) || 0;
- const [blx, bly] = [m.end.x + revStep[0], m.end.y + revStep[1]];
- m.appear.push(new PiPo({x: blx, y: bly, c: 'a', p: 'm'}));
- if (blx != x && this.board[blx][bly] != "") {
+ let [mx, my] = [x, y];
+ if (Math.abs(m.end.x - m.start.x) == 2)
+ mx = (m.start.x + m.end.x) / 2;
+ m.appear.push(new PiPo({x: mx, y: my, c: 'a', p: 'm'}));
+ if (mx != x && this.board[mx][my] != "") {
m.vanish.push(new PiPo({
- x: blx,
- y: bly,
- c: this.getColor(blx, bly),
- p: this.getPiece(blx, bly)
+ x: mx,
+ y: my,
+ c: this.getColor(mx, my),
+ p: this.getPiece(mx, my)
}));
}
+ if (Math.abs(m.end.y - m.start.y) > 1) {
+ m.segments = [
+ [[x, y], [x, y]],
+ [[m.end.x, m.end.y], [m.end.x, m.end.y]]
+ ];
+ }
});
return moves;
}
let moves = this.getPotentialMovesOf('k', [x, y]);
// If flag allows it, add 'remote shell captures'
if (this.powerFlags[this.turn]['k']) {
- super.pieces()['k'].moves[0].steps.forEach(step => {
- let [i, j] = [x + step[0], y + step[1]];
- while (this.onBoard(i, j) && this.canStepOver(i, j)) {
- i += step[0];
- j += step[1];
- }
- if (this.onBoard(i, j)) {
- const colIJ = this.getColor(i, j);
- if (colIJ != this.turn) {
- // May just destroy a bomb or banana:
- let shellCapture = new Move({
- start: {x: x, y: y},
- end: {x: i, y: j},
- appear: [],
- vanish: [
- new PiPo({x: i, y: j, c: colIJ, p: this.getPiece(i, j)})
- ]
- });
- shellCapture.shell = true; //easier play()
- shellCapture.choice = 'z'; //to display in showChoices()
- moves.push(shellCapture);
- }
- }
+ let shellCaptures = this.getPotentialMovesOf('y', [x, y]);
+ shellCaptures.forEach(sc => {
+ sc.shell = true; //easier play()
+ sc.choice = 'z'; //to display in showChoices()
+ // Fix move (Rifle style):
+ sc.vanish.shift();
+ sc.appear.shift();
});
+ Array.prototype.push.apply(moves, shellCaptures);
}
return moves;
}
p: this.getPiece(move.start.x, move.start.y)
}));
}
- em.koopa = true; //to cancel mushroom effect
break;
case "chomp":
// Eat piece
}
getMushroomEffect(move) {
- if (move.koopa || typeof move.start.x == "string")
+ if (typeof move.start.x == "string") //drop move (toadette)
return null;
let step = [move.end.x - move.start.x, move.end.y - move.start.y];
if ([0, 1].some(i => Math.abs(step[i]) >= 2 && Math.abs(step[1-i]) != 1)) {
--- /dev/null
+<p>TODO</p>
-<p>Pawn, Knight, Bishop and Rook add an object on the board every time they move...</p>
+<p>
+ Pawn, Knight, Bishop and Rook add an object on the board
+ every time they move. King and Queen have special powers.
+</p>
<ul>
<li>Mushrooms speed-up your pieces.</li>
<li>Eggs hide either a bonus or malus: see full description.</li>
</ul>
-<a href="">Full rules description</a>.
+<a href="/variants/Chakart/complete_rules.html">Full rules description</a>.
<p class="author">Charlotte Blard & Benjamin Auder (2020).</p>
-<a href="https://en.wikipedia.org/wiki/Rules_of_chess">Orthodox chess rules</a>.
+<a href="https://en.wikipedia.org/wiki/Rules_of_chess">
+ Orthodox chess rules.
+</a>
-<p>Columns 'a' and 'h' communicate: a king on h3 can also go to a2, a3 and a4.</p>
+<p>
+ Columns 'a' and 'h' communicate:
+ a king on h3 can also go to a2, a3 and a4.
+</p>
<p>Win by connecting both edges of your color.</p>
-<a href="https://www.maths.ed.ac.uk/~csangwin/hex/index.html">Detailed rules.</a>
+<a href="https://www.maths.ed.ac.uk/~csangwin/hex/index.html">
+ Detailed rules.
+</a>
<p class="author">Piet Hein (1942).</p>
-<p>White play one move, then Black play two in a row, then White play 3, and so on.</p>
+<p>
+ White play one move, then Black play two in a row,
+ then White play 3, and so on.
+</p>
--- /dev/null
+import ChessRules from "/base_rules.js";
+
+export default class RefusalRules extends ChessRules {
+
+ static get Options() {
+ return {
+ select: C.Options.select,
+ input: [
+ {
+ label: "Refuse any",
+ variable: "refuseany",
+ type: "checkbox",
+ defaut: true
+ }
+ ],
+ styles: ["cylinder"]
+ };
+ }
+
+ get hasFlags() {
+ return false;
+ }
+
+ genRandInitFen(seed) {
+ return super.genRandInitFen(seed).slice(0, -1) + ',"lastmove":"null"}';
+ }
+
+ getFen() {
+ return (
+ super.getFen().slice(0, -1) + ',"lastmove":"' +
+ JSON.stringify(this.lastMove) + '"}');
+ }
+
+ setOtherVariables(fenParsed) {
+ super.setOtherVariables(fenParsed);
+ this.lastMove = JSON.parse(fenParsed.lastmove);
+ if (!this.lastMove) {
+ // Fill with empty values to avoid checking lastMove != null
+ this.lastMove = {
+ start: {x: -1, y: -1}, end: {x: -1, y: -1}, vanish: [{c: ''}]
+ };
+ }
+ }
+
+ canIplay(x, y) {
+ if (super.canIplay(x, y))
+ return true;
+ // Check if playing last move, reversed:
+ const lm = this.lastMove;
+ return (!lm.noRef && x == lm.end.x && y == lm.end.y);
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ const moveColor = this.getColor(x, y);
+ if (moveColor != this.turn) {
+ let revLm = JSON.parse(JSON.stringify(this.lastMove));
+ [revLm.appear, revLm.vanish] = [revLm.vanish, revLm.appear];
+ [revLm.start, revLm.end] = [revLm.end, revLm.start];
+ if (!this.options["refuseany"]) {
+ // After refusing this move, can my opponent play a different move?
+ this.playOnBoard(revLm);
+ let totOppMoves = 0;
+ outerLoop: for (let i=0; i<this.size.x; i++) {
+ for (let j=0; j<this.size.y; j++) {
+ if (this.getColor(i, j) == moveColor) {
+ const potentialOppMoves = super.getPotentialMovesFrom([i, j]);
+ totOppMoves +=
+ super.filterValid(potentialOppMoves, moveColor).length;
+ if (totOppMoves >= 2)
+ break outerLoop;
+ }
+ }
+ }
+ this.undoOnBoard(revLm);
+ if (totOppMoves <= 1)
+ return [];
+ }
+ // Also reverse segments in Cylinder mode:
+ if (this.options["cylinder"])
+ revLm.segments = revLm.segments.map(seg => [seg[1], seg[0]]);
+ else
+ delete revLm["segments"];
+ revLm.refusal = true;
+ revLm.noRef = true; //cannot refuse a refusal move :)
+ return [revLm];
+ }
+ return super.getPotentialMovesFrom([x, y]);
+ }
+
+ getEpSquare(move) {
+ if (!move.refusal)
+ return super.getEpSquare(move);
+ return null;
+ }
+
+ filterValid(moves) {
+ const color = this.turn;
+ const lm = this.lastMove;
+ let rMoves = moves.filter(m => {
+ return (
+ !lm.refusal || //it's my first move attempt on this turn
+ m.start.x != lm.end.x || m.start.y != lm.end.y ||
+ m.end.x != lm.start.x || m.end.y != lm.start.y ||
+ // Doing the same move again: maybe pawn promotion?
+ (m.vanish[0].p == 'p' && m.appear[0].p != lm.appear[0].p)
+ );
+ });
+ return super.filterValid(rMoves);
+ }
+
+ prePlay(move) {
+ if (!move.noRef)
+ // My previous move was already refused?
+ move.noRef = this.lastMove.vanish[0].c == this.turn;
+ }
+
+ postPlay(move) {
+ this.lastMove = move;
+ super.postPlay(move);
+ }
+
+ atLeastOneMove() {
+ if (!this.lastMove.noRef)
+ return true;
+ return super.atLeastOneMove();
+ }
+
+};
--- /dev/null
+<p>
+ At each turn, you can refuse one opponent move.
+ Different pawn promotions count as different moves.
+</p>
+
+<p class="author">Fred Galvin (1958).</p>
--- /dev/null
+@import url("/base_pieces.css");
const cmoveFen = !this.cmove
? "-"
: C.CoordsToSquare(this.cmove.start) + C.CoordsToSquare(this.cmove.end);
- return super.getFen().slice(0, -1) + ',"' + cmoveFen + '"}';
+ return super.getFen().slice(0, -1) + ',"cmove":"' + cmoveFen + '"}';
}
getBasicMove([sx, sy], [ex, ey]) {
-<p>Pieces are swapped after captures. Kings cannot move except by being captured.</p>
+<p>
+ Pieces are swapped after captures.
+ Kings cannot move except by being captured.
+</p>
<p>Win by bringing the enemy king on your first rank.</p>
-<p>Pieces capture enemy units which threaten them (normal captures are disabled).</p>
+<p>
+ Pieces capture enemy units which threaten them
+ (normal captures are disabled).
+</p>
<p>Exception: the king is attacked as usual.</p>