From f54357573d4fdf87a05b19f78506c11f16bb3a26 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 21 Jun 2022 09:45:23 +0200 Subject: [PATCH] Add Refusal --- app.js | 6 +- base_rules.js | 6 +- variants.js | 2 +- variants/Absorption/rules.html | 5 +- variants/Atomic/rules.html | 5 +- variants/Balanced/rules.html | 5 +- variants/Chakart/class.js | 95 ++++++++++---------- variants/Chakart/complete_rules.html | 1 + variants/Chakart/rules.html | 7 +- variants/Chess960/rules.html | 4 +- variants/Cylinder/rules.html | 5 +- variants/Hex/rules.html | 4 +- variants/Progressive/rules.html | 5 +- variants/Refusal/class.js | 128 +++++++++++++++++++++++++++ variants/Refusal/rules.html | 6 ++ variants/Refusal/style.css | 1 + variants/Suction/class.js | 2 +- variants/Suction/rules.html | 5 +- variants/Zen/rules.html | 5 +- 19 files changed, 235 insertions(+), 62 deletions(-) create mode 100644 variants/Chakart/complete_rules.html create mode 100644 variants/Refusal/class.js create mode 100644 variants/Refusal/rules.html create mode 100644 variants/Refusal/style.css diff --git a/app.js b/app.js index bb16537..7d387c5 100644 --- a/app.js +++ b/app.js @@ -191,7 +191,9 @@ function getGameLink() { 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")) { @@ -229,7 +231,7 @@ function fillGameInfos(gameInfos, oppIndex) { if (j == options.length) break; const opt = options[j]; - if (!opt[1]) + if (!opt[1]) //includes 0 and false (lighter display) continue; htmlContent += '' + diff --git a/base_rules.js b/base_rules.js index 8b07059..1b22ae5 100644 --- a/base_rules.js +++ b/base_rules.js @@ -1908,10 +1908,12 @@ export default class ChessRules { 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: diff --git a/variants.js b/variants.js index eb7203e..aafd02a 100644 --- a/variants.js +++ b/variants.js @@ -120,7 +120,7 @@ const variants = [ // {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'}, diff --git a/variants/Absorption/rules.html b/variants/Absorption/rules.html index 53d5e45..1149295 100644 --- a/variants/Absorption/rules.html +++ b/variants/Absorption/rules.html @@ -1 +1,4 @@ -

The capturer absorbs abilities of the captured piece (except for pawns and king).

+

+ The capturer absorbs the abilities of the captured piece + (except for pawns and king). +

diff --git a/variants/Atomic/rules.html b/variants/Atomic/rules.html index 05829a5..c46cf22 100644 --- a/variants/Atomic/rules.html +++ b/variants/Atomic/rules.html @@ -1,3 +1,6 @@ -

The capturer explodes after each capture, as well as all pieces standing on the adjacent squares - pawns excepted.

+

+ The capturer explodes after each capture, as well as all pieces standing + on the adjacent squares - pawns excepted. +

Win by checkmate or by exploding the enemy king.

diff --git a/variants/Balanced/rules.html b/variants/Balanced/rules.html index 6d5fa49..e373943 100644 --- a/variants/Balanced/rules.html +++ b/variants/Balanced/rules.html @@ -1,3 +1,6 @@ -

White plays first, then Black plays two moves, then White plays two moves, and after that the game proceeds normally.

+

+ White plays first, then Black plays two moves, then White plays two moves, + and after that the game proceeds normally. +

See the (draft) article.

diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js index c527b8c..29fdee9 100644 --- a/variants/Chakart/class.js +++ b/variants/Chakart/class.js @@ -112,7 +112,22 @@ export default class ChakartRules extends ChessRules { '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) { @@ -255,7 +270,7 @@ export default class ChakartRules extends ChessRules { 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; } } @@ -295,33 +310,39 @@ export default class ChakartRules extends ChessRules { } } 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; } @@ -360,30 +381,15 @@ export default class ChakartRules extends ChessRules { 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; } @@ -619,7 +625,6 @@ export default class ChakartRules extends ChessRules { p: this.getPiece(move.start.x, move.start.y) })); } - em.koopa = true; //to cancel mushroom effect break; case "chomp": // Eat piece @@ -640,7 +645,7 @@ export default class ChakartRules extends ChessRules { } 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)) { diff --git a/variants/Chakart/complete_rules.html b/variants/Chakart/complete_rules.html new file mode 100644 index 0000000..c65158e --- /dev/null +++ b/variants/Chakart/complete_rules.html @@ -0,0 +1 @@ +

TODO

diff --git a/variants/Chakart/rules.html b/variants/Chakart/rules.html index f2e5068..e48a4f7 100644 --- a/variants/Chakart/rules.html +++ b/variants/Chakart/rules.html @@ -1,4 +1,7 @@ -

Pawn, Knight, Bishop and Rook add an object on the board every time they move...

+

+ Pawn, Knight, Bishop and Rook add an object on the board + every time they move. King and Queen have special powers. +

-Full rules description. +Full rules description.

Charlotte Blard & Benjamin Auder (2020).

diff --git a/variants/Chess960/rules.html b/variants/Chess960/rules.html index 46751e1..776fa4a 100644 --- a/variants/Chess960/rules.html +++ b/variants/Chess960/rules.html @@ -1 +1,3 @@ -Orthodox chess rules. + + Orthodox chess rules. + diff --git a/variants/Cylinder/rules.html b/variants/Cylinder/rules.html index 6a9db5b..49ea15f 100644 --- a/variants/Cylinder/rules.html +++ b/variants/Cylinder/rules.html @@ -1 +1,4 @@ -

Columns 'a' and 'h' communicate: a king on h3 can also go to a2, a3 and a4.

+

+ Columns 'a' and 'h' communicate: + a king on h3 can also go to a2, a3 and a4. +

diff --git a/variants/Hex/rules.html b/variants/Hex/rules.html index 33be5a2..04e700d 100644 --- a/variants/Hex/rules.html +++ b/variants/Hex/rules.html @@ -1,5 +1,7 @@

Win by connecting both edges of your color.

-Detailed rules. + + Detailed rules. +

Piet Hein (1942).

diff --git a/variants/Progressive/rules.html b/variants/Progressive/rules.html index ee9e9b0..51ec7f2 100644 --- a/variants/Progressive/rules.html +++ b/variants/Progressive/rules.html @@ -1 +1,4 @@ -

White play one move, then Black play two in a row, then White play 3, and so on.

+

+ White play one move, then Black play two in a row, + then White play 3, and so on. +

diff --git a/variants/Refusal/class.js b/variants/Refusal/class.js new file mode 100644 index 0000000..2cd9ca6 --- /dev/null +++ b/variants/Refusal/class.js @@ -0,0 +1,128 @@ +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= 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(); + } + +}; diff --git a/variants/Refusal/rules.html b/variants/Refusal/rules.html new file mode 100644 index 0000000..8efc3ce --- /dev/null +++ b/variants/Refusal/rules.html @@ -0,0 +1,6 @@ +

+ At each turn, you can refuse one opponent move. + Different pawn promotions count as different moves. +

+ +

Fred Galvin (1958).

diff --git a/variants/Refusal/style.css b/variants/Refusal/style.css new file mode 100644 index 0000000..a3550bc --- /dev/null +++ b/variants/Refusal/style.css @@ -0,0 +1 @@ +@import url("/base_pieces.css"); diff --git a/variants/Suction/class.js b/variants/Suction/class.js index db10e58..f9cf26d 100644 --- a/variants/Suction/class.js +++ b/variants/Suction/class.js @@ -53,7 +53,7 @@ export default class SuctionRules extends ChessRules { 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]) { diff --git a/variants/Suction/rules.html b/variants/Suction/rules.html index 09a70cb..4006ce0 100644 --- a/variants/Suction/rules.html +++ b/variants/Suction/rules.html @@ -1,4 +1,7 @@ -

Pieces are swapped after captures. Kings cannot move except by being captured.

+

+ Pieces are swapped after captures. + Kings cannot move except by being captured. +

Win by bringing the enemy king on your first rank.

diff --git a/variants/Zen/rules.html b/variants/Zen/rules.html index 1508496..0386371 100644 --- a/variants/Zen/rules.html +++ b/variants/Zen/rules.html @@ -1,4 +1,7 @@ -

Pieces capture enemy units which threaten them (normal captures are disabled).

+

+ Pieces capture enemy units which threaten them + (normal captures are disabled). +

Exception: the king is attacked as usual.

-- 2.44.0