From e8746dfab7a37f29384fcc567e5b24ded7c3a3d8 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 20 Jun 2022 10:18:06 +0200 Subject: [PATCH 01/16] TODO for Chakart --- TODO | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO b/TODO index a17d5c3..2aeb9ae 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,6 @@ +Chakart egg : afficher image du bonus sur la case d'arrivée, clignotante, pendant 1 seconde ? +Invisible queen : afficher "??" sur la case de départ jusqu'au coup adverse (qui l'efface du coup) ? + add variants : Dark Racing Kings ? Checkered-Teleport ? -- 2.44.0 From cc9fe4f154ec244cbec468d1a3b5845e56cb378d Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 20 Jun 2022 19:43:39 +0200 Subject: [PATCH 02/16] Chakart ready for testing --- TODO | 3 -- pieces/chakart_mystery_black.svg | 53 +++++++++++++++++++++++ pieces/chakart_mystery_white.svg | 54 +++++++++++++++++++++++ variants/Chakart/class.js | 74 +++++++++++++++----------------- variants/Chakart/style.css | 25 +++++++++++ 5 files changed, 167 insertions(+), 42 deletions(-) create mode 100644 pieces/chakart_mystery_black.svg create mode 100644 pieces/chakart_mystery_white.svg diff --git a/TODO b/TODO index 2aeb9ae..a17d5c3 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,3 @@ -Chakart egg : afficher image du bonus sur la case d'arrivée, clignotante, pendant 1 seconde ? -Invisible queen : afficher "??" sur la case de départ jusqu'au coup adverse (qui l'efface du coup) ? - add variants : Dark Racing Kings ? Checkered-Teleport ? diff --git a/pieces/chakart_mystery_black.svg b/pieces/chakart_mystery_black.svg new file mode 100644 index 0000000..4950f93 --- /dev/null +++ b/pieces/chakart_mystery_black.svg @@ -0,0 +1,53 @@ + + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/pieces/chakart_mystery_white.svg b/pieces/chakart_mystery_white.svg new file mode 100644 index 0000000..f2b7e06 --- /dev/null +++ b/pieces/chakart_mystery_white.svg @@ -0,0 +1,54 @@ + + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js index a9b1908..c987649 100644 --- a/variants/Chakart/class.js +++ b/variants/Chakart/class.js @@ -64,10 +64,6 @@ export default class ChakartRules extends ChessRules { }; } - static get INVISIBLE_QUEEN() { - return 'i'; - } - // Fictive color 'a', bomb banana mushroom egg static get BOMB() { return 'w'; //"Wario" @@ -101,6 +97,7 @@ export default class ChakartRules extends ChessRules { pieces(color, x, y) { const specials = { 'i': {"class": "invisible"}, //queen + '?': {"class": "mystery"}, //...initial square 'e': {"class": "egg"}, 'm': {"class": "mushroom"}, 'd': {"class": "banana"}, @@ -182,11 +179,7 @@ export default class ChakartRules extends ChessRules { for (let j = 0; j < this.size.y; j++) { const pieceIJ = this.getPiece(i, j); const colIJ = this.getColor(i, j); - if ( - this.board[i][j] == "" || - colIJ == 'a' || - pieceIJ == V.INVISIBLE_QUEEN - ) { + if (this.board[i][j] == "" || colIJ == 'a' || pieceIJ == 'i') { let m = new Move({ start: {x: c, y: p}, appear: [new PiPo({x: i, y: j, c: c, p: p})], @@ -272,7 +265,8 @@ export default class ChakartRules extends ChessRules { canStepOver(i, j) { return ( this.board[i][j] == "" || - [V.MUSHROOM, V.EGG].includes(this.getPiece(i, j))); + ['i', V.EGG, V.MUSHROOM].includes(this.getPiece(i, j)) + ); } getPawnMovesFrom([x, y]) { @@ -281,18 +275,20 @@ export default class ChakartRules extends ChessRules { const shiftX = (color == 'w' ? -1 : 1); const firstRank = (color == "w" ? this.size.x - 1 : 0); let moves = []; + const frontPiece = this.getPiece(x + shiftX, y); if ( this.board[x + shiftX][y] == "" || this.getColor(x + shiftX, y) == 'a' || - this.getPiece(x + shiftX, y) == V.INVISIBLE_QUEEN + frontPiece == 'i' ) { moves.push(this.getBasicMove([x, y], [x + shiftX, y])); if ( [firstRank, firstRank + shiftX].includes(x) && + ![V.BANANA, V.BOMB].includes(frontPiece) && ( this.board[x + 2 * shiftX][y] == "" || this.getColor(x + 2 * shiftX, y) == 'a' || - this.getPiece(x + 2 * shiftX, y) == V.INVISIBLE_QUEEN + this.getPiece(x + 2 * shiftX, y) == 'i' ) ) { moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); @@ -304,7 +300,7 @@ export default class ChakartRules extends ChessRules { y + shiftY < this.size.y && this.board[x + shiftX][y + shiftY] != "" && // Pawns cannot capture invisible queen this way! - this.getPiece(x + shiftX, y + shiftY) != V.INVISIBLE_QUEEN && + this.getPiece(x + shiftX, y + shiftY) != 'i' && ['a', oppCol].includes(this.getColor(x + shiftX, y + shiftY)) ) { moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY])); @@ -351,7 +347,7 @@ export default class ChakartRules extends ChessRules { m.vanish[0].c != 'a' ) { let im = JSON.parse(JSON.stringify(m)); - im.appear[0].p = V.INVISIBLE_QUEEN; + im.appear[0].p = 'i'; im.noAnimate = true; invisibleMoves.push(im); } @@ -366,17 +362,7 @@ export default class ChakartRules extends ChessRules { 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.board[i][j] == "" || - this.getPiece(i, j) == V.INVISIBLE_QUEEN || - ( - this.getColor(i, j) == 'a' && - [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j)) - ) - ) - ) { + while (this.onBoard(i, j) && this.canStepOver(i, j)) { i += step[0]; j += step[1]; } @@ -471,10 +457,12 @@ export default class ChakartRules extends ChessRules { } if (move.shell) this.powerFlags[color]['k'] = false; - else if (move.appear.length > 0 && move.appear[0].p == V.INVISIBLE_QUEEN) { + else if (move.appear.length > 0 && move.appear[0].p == 'i') { this.powerFlags[move.appear[0].c]['q'] = false; - if (color != this.playerColor) - alert("Invisible queen!"); + if (color == this.playerColor) { + move.appear.push( + new PiPo({x: move.start.x, y: move.start.y, c: color, p: '?'})); + } } if (color == this.playerColor) { // Look for an immobilized piece of my color: it can now move @@ -501,15 +489,15 @@ export default class ChakartRules extends ChessRules { for (let j=0; j<8; j++) { if ( this.board[i][j] != "" && - this.getColor(i, j) == oppCol && - this.getPiece(i, j) == V.INVISIBLE_QUEEN + this.getColor(i, j) == oppCol ) { - move.vanish.push(new PiPo({ - x: i, y: j, c: oppCol, p: V.INVISIBLE_QUEEN - })); - move.appear.push(new PiPo({ - x: i, y: j, c: oppCol, p: 'q' - })); + const pieceIJ = this.getPiece(i, j); + if (pieceIJ == 'i') { + move.vanish.push(new PiPo({x: i, y: j, c: oppCol, p: 'i'})); + move.appear.push(new PiPo({x: i, y: j, c: oppCol, p: 'q'})); + } + else if (pieceIJ == '?') + move.vanish.push(new PiPo({x: i, y: j, c: oppCol, p: '?'})); } } } @@ -519,7 +507,7 @@ export default class ChakartRules extends ChessRules { this.movesCount++; } if (move.egg) - this.displayBonus(move.egg); + this.displayBonus(move); this.playOnBoard(move); this.nextMove = move.next; } @@ -616,6 +604,7 @@ 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 @@ -636,6 +625,8 @@ export default class ChakartRules extends ChessRules { } getMushroomEffect(move) { + if (move.koopa) + 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)) { // Slider, multi-squares: normalize step @@ -678,8 +669,13 @@ export default class ChakartRules extends ChessRules { return res; } - displayBonus(egg) { - alert(egg); //TODO: nicer display + displayBonus(move) { + let divBonus = document.createElement("div"); + divBonus.classList.add("bonus-text"); + divBonus.innerHTML = move.egg; + let container = document.getElementById(this.containerId); + container.appendChild(divBonus); + setTimeout(() => container.removeChild(divBonus), 2000); } atLeastOneMove() { diff --git a/variants/Chakart/style.css b/variants/Chakart/style.css index 65a44fb..b3687ce 100644 --- a/variants/Chakart/style.css +++ b/variants/Chakart/style.css @@ -31,3 +31,28 @@ piece.immobilized { piece.remote-capture { background-image: url('/pieces/chakart_shell.svg'); } + +piece.mystery.white { + background-image: url('/pieces/chakart_mystery_white.svg'); +} +piece.mystery.black { + background-image: url('/pieces/chakart_mystery_black.svg'); +} + +div.bonus-text { + position: relative; + margin-top: 5%; + width: 100%; + text-align: center; + background-color: transparent; + color: darkred; + font-weight: bold; + font-size: 2em; + animation: blinker 0.5s linear infinite; +} + +@keyframes blinker { + 50% { + opacity: 0; + } +} -- 2.44.0 From a2bb7e0621f143f912ca505fed914fe0a5e6d611 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 20 Jun 2022 21:46:20 +0200 Subject: [PATCH 03/16] Fix Chakart promotions after effects --- base_rules.js | 2 ++ variants/Chakart/CREDITS | 1 + variants/Chakart/class.js | 28 ++++++++++++++++++++++------ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/base_rules.js b/base_rules.js index 8ad0894..8b07059 100644 --- a/base_rules.js +++ b/base_rules.js @@ -1007,6 +1007,8 @@ export default class ChessRules { let chessboard = container.querySelector(".chessboard"); let choices = document.createElement("div"); choices.id = "choices"; + if (!r) + r = chessboard.getBoundingClientRect(); choices.style.width = r.width + "px"; choices.style.height = r.height + "px"; choices.style.left = r.x + "px"; diff --git a/variants/Chakart/CREDITS b/variants/Chakart/CREDITS index e15c948..53a9181 100644 --- a/variants/Chakart/CREDITS +++ b/variants/Chakart/CREDITS @@ -5,3 +5,4 @@ https://commons.wikimedia.org/wiki/File:Tux_Paint_banana.svg https://www.onlinewebfonts.com/icon/425540 https://www.svgrepo.com/svg/264673/easter-egg-easter https://www.svgrepo.com/svg/321648/turtle-shell +https://svgsilh.com/image/40876.html diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js index c987649..a0c854b 100644 --- a/variants/Chakart/class.js +++ b/variants/Chakart/class.js @@ -306,7 +306,7 @@ export default class ChakartRules extends ChessRules { moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY])); } } - this.pawnPostProcess(moves, color, oppCol); + super.pawnPostProcess(moves, color, oppCol); // Add mushroom on before-last square moves.forEach(m => { let revStep = [m.start.x - m.end.x, m.start.y - m.end.y]; @@ -389,6 +389,22 @@ export default class ChakartRules extends ChessRules { } play(move) { + const color = this.turn; + const oppCol = C.GetOppCol(color); + if ( + move.appear.length > 0 && + move.appear[0].p == 'p' && + ( + (color == 'w' && move.end.x == 0) || + (color == 'b' && move.end.x == this.size.x - 1) + ) + ) { + // "Forgotten" promotion, which occurred after some effect + let moves = [move]; + super.pawnPostProcess(moves, color, oppCol); + super.showChoices(moves); + return false; + } if (!move.nextComputed) { // Set potential random effects, so that play() is deterministic // from opponent viewpoint: @@ -441,8 +457,6 @@ export default class ChakartRules extends ChessRules { move.nextComputed = true; } this.egg = move.egg; - const color = this.turn; - const oppCol = C.GetOppCol(color); if (move.egg == "toadette") { this.reserve = { w: {}, b: {} }; // Randomly select a piece in pawnPromotions @@ -510,6 +524,7 @@ export default class ChakartRules extends ChessRules { this.displayBonus(move); this.playOnBoard(move); this.nextMove = move.next; + return true; } // Helper to set and apply banana/bomb effect @@ -687,9 +702,10 @@ export default class ChakartRules extends ChessRules { } playPlusVisual(move, r) { - this.moveStack.push(move); const nextLines = () => { - this.play(move); + if (!this.play(move)) + return; + this.moveStack.push(move); this.playVisual(move, r); if (this.nextMove) this.playPlusVisual(this.nextMove, r); @@ -698,7 +714,7 @@ export default class ChakartRules extends ChessRules { this.moveStack = []; } }; - if (this.moveStack.length == 1) + if (this.moveStack.length == 0) nextLines(); else this.animate(move, nextLines); -- 2.44.0 From bc5d61a77f1b9f2b917221ec3f5e306818c67cf8 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 20 Jun 2022 22:33:32 +0200 Subject: [PATCH 04/16] Fix mushroom effect: more consistent --- variants/Chakart/class.js | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js index a0c854b..61a8ee2 100644 --- a/variants/Chakart/class.js +++ b/variants/Chakart/class.js @@ -640,7 +640,7 @@ export default class ChakartRules extends ChessRules { } getMushroomEffect(move) { - if (move.koopa) + if (move.koopa || typeof move.start.x == "string") 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)) { @@ -652,24 +652,11 @@ export default class ChakartRules extends ChessRules { const afterSquare = [nextSquare[0] + step[0], nextSquare[1] + step[1]]; let nextMove = null; - this.playOnBoard(move); //HACK for getBasicMove() below - if ( - this.onBoard(nextSquare[0], nextSquare[1]) && - ['k', 'p', 'n'].includes(move.vanish[0].p) && - !['w', 'b'].includes(this.getColor(nextSquare[0], nextSquare[1])) - ) { - // Speed up non-sliders + if (this.onBoard(nextSquare[0], nextSquare[1])) { + this.playOnBoard(move); //HACK for getBasicMove() nextMove = this.getBasicMove([move.end.x, move.end.y], nextSquare); + this.undoOnBoard(move); } - else if ( - this.onBoard(afterSquare[0], afterSquare[1]) && - this.board[nextSquare[0]][nextSquare[1]] != "" && - this.getColor(nextSquare[0], nextSquare[1]) != 'a' && - this.getColor(afterSquare[0], afterSquare[1]) != this.turn - ) { - nextMove = this.getBasicMove([move.end.x, move.end.y], afterSquare); - } - this.undoOnBoard(move); return nextMove; } -- 2.44.0 From fe234391b05ffef5e3236e82ca1391adcb784b45 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 20 Jun 2022 23:19:55 +0200 Subject: [PATCH 05/16] Add Suction --- TODO | 6 +- variants.js | 2 +- variants/Chakart/class.js | 2 +- variants/Suction/class.js | 113 ++++++++++++++++++++++++++++++++++++ variants/Suction/rules.html | 5 ++ variants/Suction/style.css | 1 + 6 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 variants/Suction/class.js create mode 100644 variants/Suction/rules.html create mode 100644 variants/Suction/style.css diff --git a/TODO b/TODO index a17d5c3..d879b34 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,7 @@ -add variants : Dark Racing Kings ? -Checkered-Teleport ? +add variants : +Ambiguous +Refusal +Dark Racing Kings ? Checkered-Teleport ? Otage, Emergo, Pacosako : fonction "buildPiece(arg1, arg2)" returns HTML element with 2 SVG or SVG + number réfléchir aux animations par variante (reversi, Chakart, Atomic, ...) diff --git a/variants.js b/variants.js index dc7540e..eb7203e 100644 --- a/variants.js +++ b/variants.js @@ -137,7 +137,7 @@ const variants = [ // {name: 'Spartan', desc: 'Spartan versus Persians'}, // {name: 'Squatter', desc: 'Squat last rank'}, // {name: 'Stealthbomb', desc: 'Beware the bomb'}, -// {name: 'Suction', desc: 'Attract opposite king'}, + {name: 'Suction', desc: 'Attract opposite king'}, // {name: 'Swap', desc: 'Dangerous captures'}, // {name: 'Switching', desc: "Exchange pieces' positions"}, // {name: 'Synchrone', desc: 'Play at the same time'}, diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js index 61a8ee2..c527b8c 100644 --- a/variants/Chakart/class.js +++ b/variants/Chakart/class.js @@ -117,7 +117,7 @@ export default class ChakartRules extends ChessRules { genRandInitFen(seed) { const gr = new GiveawayRules( - {mode: "suicide", options: {}, genFenOnly: true}); + {mode: "suicide", options: this.options, genFenOnly: true}); // Add Peach + mario flags return gr.genRandInitFen(seed).slice(0, -17) + '{"flags":"1111"}'; } diff --git a/variants/Suction/class.js b/variants/Suction/class.js new file mode 100644 index 0000000..db10e58 --- /dev/null +++ b/variants/Suction/class.js @@ -0,0 +1,113 @@ +import ChessRules from "/base_rules.js"; +import GiveawayRules from "/variants/Giveaway/class.js"; +import PiPo from "/utils/PiPo.js"; +import Move from "/utils/Move.js"; + +export default class SuctionRules extends ChessRules { + + static get Options() { + return { + select: C.Options.select, + styles: [ + "balance", + "capture", + "cylinder", + "dark", + "doublemove", + "madrasi", + "progressive", + "teleport" + ] + }; + } + + get pawnPromotions() { + return ['p']; //no promotions + } + + get hasFlags() { + return false; + } + + setOtherVariables(fenParsed) { + super.setOtherVariables(fenParsed); + this.cmove = null; + const cmove_str = fenParsed.cmove; + if (cmove_str != "-") { + this.cmove = { + start: C.SquareToCoords(cmove_str.substr(0, 2)), + end: C.SquareToCoords(cmove_str.substr(2)) + }; + } + } + + genRandInitFen(seed) { + const gr = new GiveawayRules( + {mode: "suicide", options: this.options, genFenOnly: true}); + // Add empty cmove: + return ( + gr.genRandInitFen(seed).slice(0, -17) + '{"enpassant":"-","cmove":"-"}'); + } + + getFen() { + const cmoveFen = !this.cmove + ? "-" + : C.CoordsToSquare(this.cmove.start) + C.CoordsToSquare(this.cmove.end); + return super.getFen().slice(0, -1) + ',"' + cmoveFen + '"}'; + } + + getBasicMove([sx, sy], [ex, ey]) { + let move = super.getBasicMove([sx, sy], [ex, ey]); + if (move.vanish.length == 2) { + move.appear.push( + new PiPo({ + x: sx, + y: sy, + c: move.vanish[1].c, + p: move.vanish[1].p + }) + ); + } + return move; + } + + canIplay(x, y) { + return this.getPiece(x, y) != 'k' && super.canIplay(x, y); + } + + // Does m2 un-do m1 ? (to disallow undoing captures) + oppositeMoves(m1, m2) { + return ( + !!m1 && + m2.vanish.length == 2 && + m1.start.x == m2.start.x && + m1.end.x == m2.end.x && + m1.start.y == m2.start.y && + m1.end.y == m2.end.y + ); + } + + filterValid(moves) { + return moves.filter(m => !this.oppositeMoves(this.cmove, m)); + } + + postPlay(move) { + super.postPlay(move); + this.cmove = + (move.vanish.length == 2 ? {start: move.start, end: move.end} : null); + } + + atLeastOneMove() { + return true; + } + + getCurrentScore() { + const color = this.turn; + const kingPos = super.searchKingPos(color); + if (color == "w" && kingPos[0] == 0) return "0-1"; + if (color == "b" && kingPos[0] == this.size.x - 1) return "1-0"; + // King is not on the opposite edge: game not over + return "*"; + } + +}; diff --git a/variants/Suction/rules.html b/variants/Suction/rules.html new file mode 100644 index 0000000..09a70cb --- /dev/null +++ b/variants/Suction/rules.html @@ -0,0 +1,5 @@ +

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

+ +

Win by bringing the enemy king on your first rank.

+ +

Nathaniel Virgo (2018).

diff --git a/variants/Suction/style.css b/variants/Suction/style.css new file mode 100644 index 0000000..a3550bc --- /dev/null +++ b/variants/Suction/style.css @@ -0,0 +1 @@ +@import url("/base_pieces.css"); -- 2.44.0 From f54357573d4fdf87a05b19f78506c11f16bb3a26 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 21 Jun 2022 09:45:23 +0200 Subject: [PATCH 06/16] 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. +

  • Mushrooms speed-up your pieces.
  • @@ -7,6 +10,6 @@
  • Eggs hide either a bonus or malus: see full description.
-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 From 0a36d31a3e4fd356624f70058cbafc135fbf6fba Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 21 Jun 2022 10:08:42 +0200 Subject: [PATCH 07/16] Start Ambiguous --- variants/Ambiguous/class.js | 166 ++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 variants/Ambiguous/class.js diff --git a/variants/Ambiguous/class.js b/variants/Ambiguous/class.js new file mode 100644 index 0000000..78d3d63 --- /dev/null +++ b/variants/Ambiguous/class.js @@ -0,0 +1,166 @@ +import ChessRules from "/base_rules.js"; +import { randInt, shuffle } from "@/utils/alea"; +import { ArrayFun } from "@/utils/array"; + +export default class AmbiguousRules extends ChessRules { + + // TODO: options + + get hasFlags() { + return false; + } + + setOtherVariables(fenParsed) { + super.setOtherVariables(fenParsed); + if (this.movesCount == 0) + this.subTurn = 2; + else + this.subTurn = 1; + } + + genRandInitFen(seed) { + const gr = new GiveawayRules( + {mode: "suicide", options: this.options, genFenOnly: true}); + return gr.genRandInitFen(seed); + } + + // Subturn 1: play a move for the opponent on the designated square. + // Subturn 2: play a move for me (which just indicate a square). + getPotentialMovesFrom([x, y]) { + const color = this.turn; + const oppCol = V.GetOppCol(color); + if (this.subTurn == 2) { + // Just play a normal move (which in fact only indicate a square) + let movesHash = {}; + return ( + super.getPotentialMovesFrom([x, y]) + .filter(m => { + // Filter promotions: keep only one, since no choice now. + if (m.appear[0].p != m.vanish[0].p) { + const hash = V.CoordsToSquare(m.start) + V.CoordsToSquare(m.end); + if (!movesHash[hash]) { + movesHash[hash] = true; + return true; + } + return false; + } + return true; + }) + .map(m => { + if (m.vanish.length == 1) m.appear[0].p = V.GOAL; + else m.appear[0].p = V.TARGET_CODE[m.vanish[1].p]; + m.appear[0].c = oppCol; + m.vanish.shift(); + return m; + }) + ); + } + // At subTurn == 1, play a targeted move for opponent + // Search for target (we could also have it in a stack...) + let target = { x: -1, y: -1 }; + outerLoop: 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 piece = this.board[i][j][1]; + if ( + piece == V.GOAL || + Object.keys(V.TARGET_DECODE).includes(piece) + ) { + target = { x: i, y: j}; + break outerLoop; + } + } + } + } + // TODO: could be more efficient than generating all moves. + this.turn = oppCol; + const emptyTarget = (this.board[target.x][target.y][1] == V.GOAL); + if (emptyTarget) this.board[target.x][target.y] = V.EMPTY; + let moves = super.getPotentialMovesFrom([x, y]); + if (emptyTarget) { + this.board[target.x][target.y] = color + V.GOAL; + moves.forEach(m => { + m.vanish.push({ + x: target.x, + y: target.y, + c: color, + p: V.GOAL + }); + }); + } + this.turn = color; + return moves.filter(m => m.end.x == target.x && m.end.y == target.y); + } + + canIplay(x, y) { + const color = this.getColor(x, y); + return ( + (this.subTurn == 1 && ![this.turn, this.playerColor].includes(color)) || + (this.subTurn == 2 && super.canIplay(x, y)) + ); + } + + // Code for empty square target + static get GOAL() { + return 'g'; + } + + static get TARGET_DECODE() { + return { + 's': 'p', + 't': 'q', + 'u': 'r', + 'o': 'n', + 'c': 'b', + 'l': 'k' + }; + } + + static get TARGET_CODE() { + return { + 'p': 's', + 'q': 't', + 'r': 'u', + 'n': 'o', + 'b': 'c', + 'k': 'l' + }; + } + + pieces() { + // ......... + } + + atLeastOneMove() { + // Since there are no checks this seems true (same as for Magnetic...) + return true; + } + + filterValid(moves) { + return moves; + } + + getCurrentScore() { + // This function is only called at subTurn 1 + const color = V.GetOppCol(this.turn); + if (this.kingPos[color][0] < 0) return (color == 'w' ? "0-1" : "1-0"); + return "*"; + } + + play(move) { + let kingCaptured = false; + if (this.subTurn == 1) { + this.prePlay(move); + this.epSquares.push(this.getEpSquare(move)); + kingCaptured = this.kingPos[this.turn][0] < 0; + } + if (kingCaptured) move.kingCaptured = true; + V.PlayOnBoard(this.board, move); + if (this.subTurn == 2 || kingCaptured) { + this.turn = V.GetOppCol(this.turn); + this.movesCount++; + } + if (!kingCaptured) this.subTurn = 3 - this.subTurn; + } + +}; -- 2.44.0 From 554e3ad3773a3123701bd894db1df4c1843283b8 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 21 Jun 2022 14:25:59 +0200 Subject: [PATCH 08/16] Add Ambiguous. Fix a few issues with FEN generation / options --- app.js | 5 +- pieces/ambiguous_target.svg | 191 ++++++++++++++++++++++++++++++++++ server.js | 16 ++- variants.js | 2 +- variants/Ambiguous/class.js | 107 ++++++++++--------- variants/Ambiguous/rules.html | 11 ++ variants/Ambiguous/style.css | 43 ++++++++ variants/Chakart/class.js | 6 +- variants/Suction/class.js | 4 +- 9 files changed, 316 insertions(+), 69 deletions(-) create mode 100644 pieces/ambiguous_target.svg create mode 100644 variants/Ambiguous/rules.html create mode 100644 variants/Ambiguous/style.css diff --git a/app.js b/app.js index 7d387c5..4c0b5e4 100644 --- a/app.js +++ b/app.js @@ -545,8 +545,9 @@ function initializeGame(obj) { break; } } - fillGameInfos(obj, playerColor == "w" ? 1 : 0); - if (obj.randvar) + const playerIndex = (playerColor == "w" ? 0 : 1); + fillGameInfos(obj, 1 - playerIndex); + if (obj.players[playerIndex].randvar) toggleVisible("gameInfos"); else toggleVisible("boardContainer"); diff --git a/pieces/ambiguous_target.svg b/pieces/ambiguous_target.svg new file mode 100644 index 0000000..53f0ccc --- /dev/null +++ b/pieces/ambiguous_target.svg @@ -0,0 +1,191 @@ + + + + Target + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + Target + 2012-02-15T07:37:04 + Target symbol + https://openclipart.org/detail/168253/target-by-fanda@cz + + + Fanda@CZ + + + + + target + + + + + + + + + + + diff --git a/server.js b/server.js index 7e1a99b..dc8bd97 100644 --- a/server.js +++ b/server.js @@ -37,15 +37,12 @@ function initializeGame(vname, players, options) { function launchGame(gid) { moveHash[gid] = {}; const gameInfo = Object.assign( - {seed: Math.floor(Math.random() * 1984), gid: gid}, + {seed: Math.floor(Math.random() * 19840), gid: gid}, games[gid] ); // players array is supposed to be full: - for (const p of games[gid].players) { - send(p.sid, - "gamestart", - Object.assign({randvar: p.randvar}, gameInfo)); - } + for (const p of games[gid].players) + send(p.sid, "gamestart", gameInfo); } function getRandomVariant() { @@ -137,11 +134,10 @@ wss.on("connection", (socket, req) => { const allrand = games[obj.gid].rematch.every(r => r == 2); if (allrand) vname = getRandomVariant(); - games[obj.gid].players.forEach(p => - p.randvar = allrand ? true : false); + games[obj.gid].players.forEach(p => p.randvar = allrand); const gid = initializeGame(vname, - games[obj.gid].players.reverse(), - games[obj.gid].options); + games[obj.gid].players.reverse(), + games[obj.gid].options); launchGame(gid); } } diff --git a/variants.js b/variants.js index aafd02a..c9af9e4 100644 --- a/variants.js +++ b/variants.js @@ -5,7 +5,7 @@ const variants = [ // {name: 'Alice', desc: 'Both sides of the mirror'}, // {name: 'Align4', desc: 'Align four pawns'}, // {name: 'Allmate', desc: 'Mate any piece'}, -// {name: 'Ambiguous', desc: "Play opponent's pieces"}, + {name: 'Ambiguous', desc: "Play opponent's pieces"}, // {name: 'Antiking1', desc: 'Keep antiking in check', disp: 'Anti-King'}, // {name: 'Antimatter', desc: 'Dangerous collisions'}, // {name: 'Apocalypse', desc: 'The end of the world'}, diff --git a/variants/Ambiguous/class.js b/variants/Ambiguous/class.js index 78d3d63..6a001a6 100644 --- a/variants/Ambiguous/class.js +++ b/variants/Ambiguous/class.js @@ -1,10 +1,14 @@ import ChessRules from "/base_rules.js"; -import { randInt, shuffle } from "@/utils/alea"; -import { ArrayFun } from "@/utils/array"; +import GiveawayRules from "/variants/Giveaway/class.js"; export default class AmbiguousRules extends ChessRules { - // TODO: options + static get Options() { + return { + select: C.Options.select, + styles: ["cylinder"] + }; + } get hasFlags() { return false; @@ -19,25 +23,29 @@ export default class AmbiguousRules extends ChessRules { } genRandInitFen(seed) { - const gr = new GiveawayRules( - {mode: "suicide", options: this.options, genFenOnly: true}); + const options = Object.assign({mode: "suicide"}, this.options); + const gr = new GiveawayRules({options: options, genFenOnly: true}); return gr.genRandInitFen(seed); } + canStepOver(x, y) { + return this.board[x][y] == "" || this.getPiece(x, y) == V.GOAL; + } + // Subturn 1: play a move for the opponent on the designated square. // Subturn 2: play a move for me (which just indicate a square). getPotentialMovesFrom([x, y]) { const color = this.turn; - const oppCol = V.GetOppCol(color); + const oppCol = C.GetOppCol(color); if (this.subTurn == 2) { // Just play a normal move (which in fact only indicate a square) let movesHash = {}; return ( super.getPotentialMovesFrom([x, y]) .filter(m => { - // Filter promotions: keep only one, since no choice now. + // Filter promotions: keep only one, since no choice for now. if (m.appear[0].p != m.vanish[0].p) { - const hash = V.CoordsToSquare(m.start) + V.CoordsToSquare(m.end); + const hash = C.CoordsToSquare(m.start) + C.CoordsToSquare(m.end); if (!movesHash[hash]) { movesHash[hash] = true; return true; @@ -47,48 +55,37 @@ export default class AmbiguousRules extends ChessRules { return true; }) .map(m => { - if (m.vanish.length == 1) m.appear[0].p = V.GOAL; - else m.appear[0].p = V.TARGET_CODE[m.vanish[1].p]; - m.appear[0].c = oppCol; + if (m.vanish.length == 1) { + m.appear[0].c = 'a'; //a-color + m.appear[0].p = V.GOAL; + } + else { + m.appear[0].p = V.TARGET_CODE[m.vanish[1].p]; + m.appear[0].c = oppCol; + } m.vanish.shift(); return m; }) ); } - // At subTurn == 1, play a targeted move for opponent + // At subTurn == 1, play a targeted move for the opponent. // Search for target (we could also have it in a stack...) - let target = { x: -1, y: -1 }; - outerLoop: 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 piece = this.board[i][j][1]; + let target = {x: -1, y: -1}; + outerLoop: for (let i = 0; i < this.size.x; i++) { + for (let j = 0; j < this.size.y; j++) { + if (this.board[i][j] != "") { + const piece = this.getPiece(i, j); if ( piece == V.GOAL || Object.keys(V.TARGET_DECODE).includes(piece) ) { - target = { x: i, y: j}; + target = {x: i, y:j}; break outerLoop; } } } } - // TODO: could be more efficient than generating all moves. - this.turn = oppCol; - const emptyTarget = (this.board[target.x][target.y][1] == V.GOAL); - if (emptyTarget) this.board[target.x][target.y] = V.EMPTY; - let moves = super.getPotentialMovesFrom([x, y]); - if (emptyTarget) { - this.board[target.x][target.y] = color + V.GOAL; - moves.forEach(m => { - m.vanish.push({ - x: target.x, - y: target.y, - c: color, - p: V.GOAL - }); - }); - } - this.turn = color; + const moves = super.getPotentialMovesFrom([x, y], oppCol); return moves.filter(m => m.end.x == target.x && m.end.y == target.y); } @@ -127,8 +124,17 @@ export default class AmbiguousRules extends ChessRules { }; } - pieces() { - // ......... + pieces(color, x, y) { + const targets = { + 's': {"class": "target-pawn", moves: []}, + 'u': {"class": "target-rook", moves: []}, + 'o': {"class": "target-knight", moves: []}, + 'c': {"class": "target-bishop", moves: []}, + 't': {"class": "target-queen", moves: []}, + 'l': {"class": "target-king", moves: []} + }; + return Object.assign( + { 'g': {"class": "target"} }, targets, super.pieces(color, x, y)); } atLeastOneMove() { @@ -140,27 +146,26 @@ export default class AmbiguousRules extends ChessRules { return moves; } + isKing(symbol) { + return ['k', 'l'].includes(symbol); + } + getCurrentScore() { // This function is only called at subTurn 1 - const color = V.GetOppCol(this.turn); - if (this.kingPos[color][0] < 0) return (color == 'w' ? "0-1" : "1-0"); + const color = C.GetOppCol(this.turn); + const kingPos = this.searchKingPos(color); + if (kingPos[0] < 0) + return (color == 'w' ? "0-1" : "1-0"); return "*"; } - play(move) { - let kingCaptured = false; - if (this.subTurn == 1) { - this.prePlay(move); - this.epSquares.push(this.getEpSquare(move)); - kingCaptured = this.kingPos[this.turn][0] < 0; - } - if (kingCaptured) move.kingCaptured = true; - V.PlayOnBoard(this.board, move); - if (this.subTurn == 2 || kingCaptured) { - this.turn = V.GetOppCol(this.turn); + postPlay(move) { + const color = this.turn; + if (this.subTurn == 2 || this.searchKingPos(color)[0] < 0) { + this.turn = C.GetOppCol(color); this.movesCount++; } - if (!kingCaptured) this.subTurn = 3 - this.subTurn; + this.subTurn = 3 - this.subTurn; } }; diff --git a/variants/Ambiguous/rules.html b/variants/Ambiguous/rules.html new file mode 100644 index 0000000..b51c747 --- /dev/null +++ b/variants/Ambiguous/rules.html @@ -0,0 +1,11 @@ +

+ Every move you play can be changed by your opponent by a move arriving + on the same square. +

+ +

+ Consequently, you play twice on each turn: first to select a move for + your opponent, then to choose one for you - which could be altered. +

+ +

Fabrice Liardet (2005).

diff --git a/variants/Ambiguous/style.css b/variants/Ambiguous/style.css new file mode 100644 index 0000000..e31d810 --- /dev/null +++ b/variants/Ambiguous/style.css @@ -0,0 +1,43 @@ +@import url("/base_pieces.css"); + +piece.target { + background-image: url('/pieces/ambiguous_target.svg'); +} + +piece.white.target-pawn { + background-image: url('/pieces/yellow_pawn.svg'); +} +piece.white.target-rook { + background-image: url('/pieces/yellow_rook.svg'); +} +piece.white.target-knight { + background-image: url('/pieces/yellow_knight.svg'); +} +piece.white.target-bishop { + background-image: url('/pieces/yellow_bishop.svg'); +} +piece.white.target-queen { + background-image: url('/pieces/yellow_queen.svg'); +} +piece.white.target-king { + background-image: url('/pieces/yellow_king.svg'); +} + +piece.black.target-pawn { + background-image: url('/pieces/red_pawn.svg'); +} +piece.black.target-rook { + background-image: url('/pieces/red_rook.svg'); +} +piece.black.target-knight { + background-image: url('/pieces/red_knight.svg'); +} +piece.black.target-bishop { + background-image: url('/pieces/red_bishop.svg'); +} +piece.black.target-queen { + background-image: url('/pieces/red_queen.svg'); +} +piece.black.target-king { + background-image: url('/pieces/red_king.svg'); +} diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js index 29fdee9..37261e4 100644 --- a/variants/Chakart/class.js +++ b/variants/Chakart/class.js @@ -131,8 +131,8 @@ export default class ChakartRules extends ChessRules { } genRandInitFen(seed) { - const gr = new GiveawayRules( - {mode: "suicide", options: this.options, genFenOnly: true}); + const options = Object.assign({mode: "suicide"}, this.options); + const gr = new GiveawayRules({options: options, genFenOnly: true}); // Add Peach + mario flags return gr.genRandInitFen(seed).slice(0, -17) + '{"flags":"1111"}'; } @@ -180,7 +180,7 @@ export default class ChakartRules extends ChessRules { this.moveStack = []; // Change seed (after FEN generation!!) // so that further calls differ between players: - Random.setSeed(Math.floor(10000 * Math.random())); + Random.setSeed(Math.floor(19840 * Math.random())); } // For Toadette bonus diff --git a/variants/Suction/class.js b/variants/Suction/class.js index f9cf26d..e2e36c8 100644 --- a/variants/Suction/class.js +++ b/variants/Suction/class.js @@ -42,8 +42,8 @@ export default class SuctionRules extends ChessRules { } genRandInitFen(seed) { - const gr = new GiveawayRules( - {mode: "suicide", options: this.options, genFenOnly: true}); + const options = Object.assign({mode: "suicide"}, this.options); + const gr = new GiveawayRules({options: options, genFenOnly: true}); // Add empty cmove: return ( gr.genRandInitFen(seed).slice(0, -17) + '{"enpassant":"-","cmove":"-"}'); -- 2.44.0 From f55a0a6753a62257c7caa62ae49c8a5673769065 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 21 Jun 2022 16:30:44 +0200 Subject: [PATCH 09/16] Add Alapo (unfinished). Reorganise pieces folder --- TODO | 5 +- base_rules.js | 24 +-- pieces/Alapo/black_CIRCLE.svg | 5 + pieces/Alapo/black_SQUARE.svg | 5 + pieces/Alapo/black_TRIANGLE.svg | 5 + pieces/Alapo/black_TRIANGLE_inv.svg | 5 + pieces/Alapo/black_circle.svg | 5 + pieces/Alapo/black_square.svg | 5 + pieces/Alapo/black_triangle.svg | 5 + pieces/Alapo/black_triangle_inv.svg | 5 + pieces/Alapo/white_CIRCLE.svg | 5 + pieces/Alapo/white_SQUARE.svg | 5 + pieces/Alapo/white_TRIANGLE.svg | 5 + pieces/Alapo/white_TRIANGLE_inv.svg | 5 + pieces/Alapo/white_circle.svg | 5 + pieces/Alapo/white_square.svg | 5 + pieces/Alapo/white_triangle.svg | 5 + pieces/Alapo/white_triangle_inv.svg | 5 + .../target.svg} | 0 .../banana.svg} | 0 pieces/{chakart_bomb.svg => Chakart/bomb.svg} | 0 pieces/{chakart_egg.svg => Chakart/egg.svg} | 0 .../mushroom.svg} | 0 .../mystery_black.svg} | 0 .../mystery_white.svg} | 0 .../{chakart_shell.svg => Chakart/shell.svg} | 0 variants.js | 2 +- variants/Alapo/class.js | 156 ++++++++++++++++++ variants/Alapo/rules.html | 7 + variants/Alapo/style.css | 49 ++++++ variants/Ambiguous/CREDITS | 3 + variants/Ambiguous/style.css | 2 +- variants/Chakart/style.css | 14 +- 33 files changed, 320 insertions(+), 22 deletions(-) create mode 100644 pieces/Alapo/black_CIRCLE.svg create mode 100644 pieces/Alapo/black_SQUARE.svg create mode 100644 pieces/Alapo/black_TRIANGLE.svg create mode 100644 pieces/Alapo/black_TRIANGLE_inv.svg create mode 100644 pieces/Alapo/black_circle.svg create mode 100644 pieces/Alapo/black_square.svg create mode 100644 pieces/Alapo/black_triangle.svg create mode 100644 pieces/Alapo/black_triangle_inv.svg create mode 100644 pieces/Alapo/white_CIRCLE.svg create mode 100644 pieces/Alapo/white_SQUARE.svg create mode 100644 pieces/Alapo/white_TRIANGLE.svg create mode 100644 pieces/Alapo/white_TRIANGLE_inv.svg create mode 100644 pieces/Alapo/white_circle.svg create mode 100644 pieces/Alapo/white_square.svg create mode 100644 pieces/Alapo/white_triangle.svg create mode 100644 pieces/Alapo/white_triangle_inv.svg rename pieces/{ambiguous_target.svg => Ambiguous/target.svg} (100%) rename pieces/{chakart_banana.svg => Chakart/banana.svg} (100%) rename pieces/{chakart_bomb.svg => Chakart/bomb.svg} (100%) rename pieces/{chakart_egg.svg => Chakart/egg.svg} (100%) rename pieces/{chakart_mushroom.svg => Chakart/mushroom.svg} (100%) rename pieces/{chakart_mystery_black.svg => Chakart/mystery_black.svg} (100%) rename pieces/{chakart_mystery_white.svg => Chakart/mystery_white.svg} (100%) rename pieces/{chakart_shell.svg => Chakart/shell.svg} (100%) create mode 100644 variants/Alapo/class.js create mode 100644 variants/Alapo/rules.html create mode 100644 variants/Alapo/style.css create mode 100644 variants/Ambiguous/CREDITS diff --git a/TODO b/TODO index d879b34..73b76b0 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,7 @@ +Debug Alapo +Add links to complete variants rules (Ambiguous...) + add variants : -Ambiguous -Refusal Dark Racing Kings ? Checkered-Teleport ? Otage, Emergo, Pacosako : fonction "buildPiece(arg1, arg2)" returns HTML element with 2 SVG or SVG + number diff --git a/base_rules.js b/base_rules.js index 1b22ae5..c19ee64 100644 --- a/base_rules.js +++ b/base_rules.js @@ -569,15 +569,16 @@ export default class ChessRules { // Compare window ratio width / height to aspectRatio: const windowRatio = window.innerWidth / window.innerHeight; let cbWidth, cbHeight; - if (windowRatio <= this.size.ratio) { + const vRatio = this.size.ratio || 1; + if (windowRatio <= vRatio) { // Limiting dimension is width: cbWidth = Math.min(window.innerWidth, 767); - cbHeight = cbWidth / this.size.ratio; + cbHeight = cbWidth / vRatio; } else { // Limiting dimension is height: cbHeight = Math.min(window.innerHeight, 767); - cbWidth = cbHeight * this.size.ratio; + cbWidth = cbHeight * vRatio; } if (this.hasReserve) { const sqSize = cbWidth / this.size.y; @@ -585,7 +586,7 @@ export default class ChessRules { // Cannot use getReserveSquareSize() here, but sqSize is an upper bound. if ((window.innerHeight - cbHeight) / 2 < sqSize + 5) { cbHeight = window.innerHeight - 2 * (sqSize + 5); - cbWidth = cbHeight * this.size.ratio; + cbWidth = cbHeight * vRatio; } } chessboard.style.width = cbWidth + "px"; @@ -610,7 +611,7 @@ export default class ChessRules { const flipped = (this.playerColor == 'b'); let board = ` `; for (let i=0; i < this.size.x; i++) { for (let j=0; j < this.size.y; j++) { @@ -798,13 +799,14 @@ export default class ChessRules { const multFact = (mode == "up" ? 1.05 : 0.95); let [newWidth, newHeight] = [multFact * r.width, multFact * r.height]; // Stay in window: + const vRatio = this.size.ratio || 1; if (newWidth > window.innerWidth) { newWidth = window.innerWidth; - newHeight = newWidth / this.size.ratio; + newHeight = newWidth / vRatio; } if (newHeight > window.innerHeight) { newHeight = window.innerHeight; - newWidth = newHeight * this.size.ratio; + newWidth = newHeight * vRatio; } chessboard.style.width = newWidth + "px"; chessboard.style.height = newHeight + "px"; @@ -1051,7 +1053,7 @@ export default class ChessRules { return { x: 8, y: 8, - ratio: 1 //for rectangular board = y / x + ratio: 1 //for rectangular board = y / x (optional, 1 = default) }; } @@ -2210,9 +2212,9 @@ export default class ChessRules { this.afterPlay(move); //user method } - getMaxDistance(rwidth) { + getMaxDistance(r) { // Works for all rectangular boards: - return Math.sqrt(rwidth ** 2 + (rwidth / this.size.ratio) ** 2); + return Math.sqrt(r.width ** 2 + r.height ** 2); } getDomPiece(x, y) { @@ -2237,7 +2239,7 @@ export default class ChessRules { movingPiece.style.width = pieceWidth + "px"; movingPiece.style.height = pieceWidth + "px"; } - const maxDist = this.getMaxDistance(r.width); + const maxDist = this.getMaxDistance(r); const pieces = this.pieces(); if (move.drag) { const startCode = this.getPiece(move.start.x, move.start.y); diff --git a/pieces/Alapo/black_CIRCLE.svg b/pieces/Alapo/black_CIRCLE.svg new file mode 100644 index 0000000..7948061 --- /dev/null +++ b/pieces/Alapo/black_CIRCLE.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/black_SQUARE.svg b/pieces/Alapo/black_SQUARE.svg new file mode 100644 index 0000000..fdd9921 --- /dev/null +++ b/pieces/Alapo/black_SQUARE.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/black_TRIANGLE.svg b/pieces/Alapo/black_TRIANGLE.svg new file mode 100644 index 0000000..939ba3a --- /dev/null +++ b/pieces/Alapo/black_TRIANGLE.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/black_TRIANGLE_inv.svg b/pieces/Alapo/black_TRIANGLE_inv.svg new file mode 100644 index 0000000..fb3c955 --- /dev/null +++ b/pieces/Alapo/black_TRIANGLE_inv.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/black_circle.svg b/pieces/Alapo/black_circle.svg new file mode 100644 index 0000000..8cd85d9 --- /dev/null +++ b/pieces/Alapo/black_circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/black_square.svg b/pieces/Alapo/black_square.svg new file mode 100644 index 0000000..3e86c70 --- /dev/null +++ b/pieces/Alapo/black_square.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/black_triangle.svg b/pieces/Alapo/black_triangle.svg new file mode 100644 index 0000000..8c531b5 --- /dev/null +++ b/pieces/Alapo/black_triangle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/black_triangle_inv.svg b/pieces/Alapo/black_triangle_inv.svg new file mode 100644 index 0000000..e9afcea --- /dev/null +++ b/pieces/Alapo/black_triangle_inv.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/white_CIRCLE.svg b/pieces/Alapo/white_CIRCLE.svg new file mode 100644 index 0000000..af99afa --- /dev/null +++ b/pieces/Alapo/white_CIRCLE.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/white_SQUARE.svg b/pieces/Alapo/white_SQUARE.svg new file mode 100644 index 0000000..716a8e5 --- /dev/null +++ b/pieces/Alapo/white_SQUARE.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/white_TRIANGLE.svg b/pieces/Alapo/white_TRIANGLE.svg new file mode 100644 index 0000000..411ec2f --- /dev/null +++ b/pieces/Alapo/white_TRIANGLE.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/white_TRIANGLE_inv.svg b/pieces/Alapo/white_TRIANGLE_inv.svg new file mode 100644 index 0000000..61733b1 --- /dev/null +++ b/pieces/Alapo/white_TRIANGLE_inv.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/white_circle.svg b/pieces/Alapo/white_circle.svg new file mode 100644 index 0000000..f5f19cf --- /dev/null +++ b/pieces/Alapo/white_circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/white_square.svg b/pieces/Alapo/white_square.svg new file mode 100644 index 0000000..9c7e4e7 --- /dev/null +++ b/pieces/Alapo/white_square.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/white_triangle.svg b/pieces/Alapo/white_triangle.svg new file mode 100644 index 0000000..abad0f9 --- /dev/null +++ b/pieces/Alapo/white_triangle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/Alapo/white_triangle_inv.svg b/pieces/Alapo/white_triangle_inv.svg new file mode 100644 index 0000000..4791916 --- /dev/null +++ b/pieces/Alapo/white_triangle_inv.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pieces/ambiguous_target.svg b/pieces/Ambiguous/target.svg similarity index 100% rename from pieces/ambiguous_target.svg rename to pieces/Ambiguous/target.svg diff --git a/pieces/chakart_banana.svg b/pieces/Chakart/banana.svg similarity index 100% rename from pieces/chakart_banana.svg rename to pieces/Chakart/banana.svg diff --git a/pieces/chakart_bomb.svg b/pieces/Chakart/bomb.svg similarity index 100% rename from pieces/chakart_bomb.svg rename to pieces/Chakart/bomb.svg diff --git a/pieces/chakart_egg.svg b/pieces/Chakart/egg.svg similarity index 100% rename from pieces/chakart_egg.svg rename to pieces/Chakart/egg.svg diff --git a/pieces/chakart_mushroom.svg b/pieces/Chakart/mushroom.svg similarity index 100% rename from pieces/chakart_mushroom.svg rename to pieces/Chakart/mushroom.svg diff --git a/pieces/chakart_mystery_black.svg b/pieces/Chakart/mystery_black.svg similarity index 100% rename from pieces/chakart_mystery_black.svg rename to pieces/Chakart/mystery_black.svg diff --git a/pieces/chakart_mystery_white.svg b/pieces/Chakart/mystery_white.svg similarity index 100% rename from pieces/chakart_mystery_white.svg rename to pieces/Chakart/mystery_white.svg diff --git a/pieces/chakart_shell.svg b/pieces/Chakart/shell.svg similarity index 100% rename from pieces/chakart_shell.svg rename to pieces/Chakart/shell.svg diff --git a/variants.js b/variants.js index c9af9e4..90e9244 100644 --- a/variants.js +++ b/variants.js @@ -1,7 +1,7 @@ const variants = [ // TODO: https://mancala.fandom.com/wiki/William_Daniel_Troyka Cleopatra chess {name: 'Absorption', desc: 'Absorb powers'}, -// {name: 'Alapo', desc: 'Geometric Chess'}, + {name: 'Alapo', desc: 'Geometric Chess'}, // {name: 'Alice', desc: 'Both sides of the mirror'}, // {name: 'Align4', desc: 'Align four pawns'}, // {name: 'Allmate', desc: 'Mate any piece'}, diff --git a/variants/Alapo/class.js b/variants/Alapo/class.js new file mode 100644 index 0000000..eb0e73f --- /dev/null +++ b/variants/Alapo/class.js @@ -0,0 +1,156 @@ +import ChessRules from "/base_rules.js"; +import { ArrayFun } from "/utils/array.js"; +import { Random } from "/utils/alea.js"; + +export default class AlapoRules extends ChessRules { + + get hasFlags() { + return false; + } + get hasEnpassant() { + return false; + } + + getSvgChessboard() { + let board = super.getSvgChessboard().slice(0, -6); + // Add lines to delimitate goals + board += ` + + + `; + return board; + } + + genRandInitFen(seed) { + if (this.options["randomness"] == 0) + return "rbqqbr/tcssct/6/6/TCSSCT/RBQQBR w 0"; + + Random.setSeed(seed); + + const piece2pawn = { + r: 't', + q: 's', + b: 'c' + }; + + let pieces = { w: new Array(6), b: new Array(6) }; + // Shuffle pieces on first (and last rank if randomness == 2) + for (let c of ["w", "b"]) { + if (c == 'b' && this.options["randomness"] == 1) { + pieces['b'] = pieces['w']; + break; + } + + let positions = ArrayFun.range(6); + + // Get random squares for bishops + let randIndex = 2 * Random.randInt(3); + const bishop1Pos = positions[randIndex]; + let randIndex_tmp = 2 * Random.randInt(3) + 1; + const bishop2Pos = positions[randIndex_tmp]; + positions.splice(Math.max(randIndex, randIndex_tmp), 1); + positions.splice(Math.min(randIndex, randIndex_tmp), 1); + + // Get random square for queens + randIndex = Random.randInt(4); + const queen1Pos = positions[randIndex]; + positions.splice(randIndex, 1); + randIndex = Random.randInt(3); + const queen2Pos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Rooks positions are now fixed, + const rook1Pos = positions[0]; + const rook2Pos = positions[1]; + + pieces[c][rook1Pos] = "r"; + pieces[c][bishop1Pos] = "b"; + pieces[c][queen1Pos] = "q"; + pieces[c][queen2Pos] = "q"; + pieces[c][bishop2Pos] = "b"; + pieces[c][rook2Pos] = "r"; + } + + return ( + pieces["b"].join("") + "/" + + pieces["b"].map(p => piece2pawn[p]).join("") + + "/6/6/" + + pieces["w"].map(p => piece2pawn[p].toUpperCase()).join("") + "/" + + pieces["w"].join("").toUpperCase() + + " w 0" + ); + } + + pieces(color, x, y) { + return { + 'r': super.pieces(color, x, y)['r'], + 'q': super.pieces(color, x, y)['q'], + 'b': { + // Triangle is rotated from opponent viewpoint + "class": "bishop" + (this.playerColor != color ? "_inv" : ""), + moves: [ { steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]] } ] + }, + 's': { //"square" + "class": "babyrook", + moves: [ + { + steps: [[0, 1], [0, -1], [1, 0], [-1, 0]], + range: 1 + } + ] + }, + 'c': { //"circle" + "class": "babyqueen", + moves: [ + { + steps: [ + [0, 1], [0, -1], [1, 0], [-1, 0], + [1, 1], [1, -1], [-1, 1], [-1, -1] + ], + range: 1 + } + ] + }, + 't': { //"triangle" + "class": "babybishop" + (this.playerColor != color ? "_inv" : ""), + moves: [ + { + steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]], + range: 1 + } + ] + } + }; + } + + get size() { + return { + x: 6, + y: 6 + }; + } + + filterValid(moves) { + return moves; + } + + getCurrentScore() { + // Try both colors (to detect potential suicides) + let won = {}; + for (let c of ['w', 'b']) { + const oppCol = C.GetOppCol(c); + const goal = (c == 'w' ? 0 : 5); + won[c] = this.board[goal].some((b,j) => { + return ( + this.getColor(goal, j) == c && + this.findCapturesOn( + [goal, j], {one: true, oppCol: oppCol}).length == 0 + ); + }); + } + if (won['w'] && won['b']) + return "?"; //no idea who won, not relevant anyway :) + return (won['w'] ? "1-0" : (won['b'] ? "0-1" : "*")); + } + +}; diff --git a/variants/Alapo/rules.html b/variants/Alapo/rules.html new file mode 100644 index 0000000..252699c --- /dev/null +++ b/variants/Alapo/rules.html @@ -0,0 +1,7 @@ +

Pieces move like rook, bishop and queen. Small ones by one square only.

+ +

Goal: bring a piece safely on the last rank.

+ + + chessvariants page. + diff --git a/variants/Alapo/style.css b/variants/Alapo/style.css new file mode 100644 index 0000000..942f9f4 --- /dev/null +++ b/variants/Alapo/style.css @@ -0,0 +1,49 @@ +piece.black.rook { + background-image: url('/pieces/Alapo/black_SQUARE.svg'); +} +piece.black.bishop { + background-image: url('/pieces/Alapo/black_TRIANGLE.svg'); +} +piece.black.bishop_inv { + background-image: url('/pieces/Alapo/black_TRIANGLE_inv.svg'); +} +piece.black.queen { + background-image: url('/pieces/Alapo/black_CIRCLE.svg'); +} +piece.black.babyrook { + background-image: url('/pieces/Alapo/black_square.svg'); +} +piece.black.babybishop { + background-image: url('/pieces/Alapo/black_triangle.svg'); +} +piece.black.babybishop { + background-image: url('/pieces/Alapo/black_triangle_inv.svg'); +} +piece.black.babyqueen { + background-image: url('/pieces/Alapo/black_circle.svg'); +} + +piece.white.rook { + background-image: url('/pieces/Alapo/white_SQUARE.svg'); +} +piece.white.bishop { + background-image: url('/pieces/Alapo/white_TRIANGLE.svg'); +} +piece.white.bishop { + background-image: url('/pieces/Alapo/white_TRIANGLE_inv.svg'); +} +piece.white.queen { + background-image: url('/pieces/Alapo/white_CIRCLE.svg'); +} +piece.white.babyrook { + background-image: url('/pieces/Alapo/white_square.svg'); +} +piece.white.babybishop { + background-image: url('/pieces/Alapo/white_triangle.svg'); +} +piece.white.babybishop { + background-image: url('/pieces/Alapo/white_triangle_inv.svg'); +} +piece.white.babyqueen { + background-image: url('/pieces/Alapo/white_circle.svg'); +} diff --git a/variants/Ambiguous/CREDITS b/variants/Ambiguous/CREDITS new file mode 100644 index 0000000..68ab598 --- /dev/null +++ b/variants/Ambiguous/CREDITS @@ -0,0 +1,3 @@ +Images: + +https://freesvg.org/black-target diff --git a/variants/Ambiguous/style.css b/variants/Ambiguous/style.css index e31d810..db300f7 100644 --- a/variants/Ambiguous/style.css +++ b/variants/Ambiguous/style.css @@ -1,7 +1,7 @@ @import url("/base_pieces.css"); piece.target { - background-image: url('/pieces/ambiguous_target.svg'); + background-image: url('/pieces/Ambiguous/target.svg'); } piece.white.target-pawn { diff --git a/variants/Chakart/style.css b/variants/Chakart/style.css index b3687ce..91ad633 100644 --- a/variants/Chakart/style.css +++ b/variants/Chakart/style.css @@ -1,19 +1,19 @@ @import url("/base_pieces.css"); piece.egg { - background-image: url('/pieces/chakart_egg.svg'); + background-image: url('/pieces/Chakart/egg.svg'); } piece.mushroom { - background-image: url('/pieces/chakart_mushroom.svg'); + background-image: url('/pieces/Chakart/mushroom.svg'); } piece.banana { - background-image: url('/pieces/chakart_banana.svg'); + background-image: url('/pieces/Chakart/banana.svg'); } piece.bomb { - background-image: url('/pieces/chakart_bomb.svg'); + background-image: url('/pieces/Chakart/bomb.svg'); } piece.white.invisible { @@ -29,14 +29,14 @@ piece.immobilized { } piece.remote-capture { - background-image: url('/pieces/chakart_shell.svg'); + background-image: url('/pieces/Chakart/shell.svg'); } piece.mystery.white { - background-image: url('/pieces/chakart_mystery_white.svg'); + background-image: url('/pieces/Chakart/mystery_white.svg'); } piece.mystery.black { - background-image: url('/pieces/chakart_mystery_black.svg'); + background-image: url('/pieces/Chakart/mystery_black.svg'); } div.bonus-text { -- 2.44.0 From 2159c2391f765c55ad79d5e05a62ecc6586b8522 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 21 Jun 2022 18:07:32 +0200 Subject: [PATCH 10/16] Fix Alapo --- base_rules.js | 15 +++++++++------ variants/Alapo/style.css | 6 +++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/base_rules.js b/base_rules.js index c19ee64..4b387b6 100644 --- a/base_rules.js +++ b/base_rules.js @@ -666,7 +666,8 @@ export default class ChessRules { const color = this.getColor(i, j); const piece = this.getPiece(i, j); this.g_pieces[i][j] = document.createElement("piece"); - C.AddClass_es(this.g_pieces[i][j], this.pieces()[piece]["class"]); + C.AddClass_es(this.g_pieces[i][j], + this.pieces(color, i, j)[piece]["class"]); this.g_pieces[i][j].classList.add(C.GetColorClass(color)); this.g_pieces[i][j].style.width = pieceWidth + "px"; this.g_pieces[i][j].style.height = pieceWidth + "px"; @@ -734,7 +735,7 @@ export default class ChessRules { r_cell.style.height = sqResSize + "px"; rcontainer.appendChild(r_cell); let piece = document.createElement("piece"); - C.AddClass_es(piece, this.pieces()[p]["class"]); + C.AddClass_es(piece, this.pieces(c, c, p)[p]["class"]); piece.classList.add(C.GetColorClass(c)); piece.style.width = "100%"; piece.style.height = "100%"; @@ -1037,7 +1038,8 @@ export default class ChessRules { choice.onclick = () => callback(moves[i]); const piece = document.createElement("piece"); const cdisp = moves[i].choice || moves[i].appear[0].p; - C.AddClass_es(piece, this.pieces()[cdisp]["class"]); + C.AddClass_es(piece, + this.pieces(color, moves[i].end.x, moves[i].end.y)[cdisp]["class"]); piece.classList.add(C.GetColorClass(color)); piece.style.width = "100%"; piece.style.height = "100%"; @@ -2190,7 +2192,8 @@ export default class ChessRules { const pieceWidth = this.getPieceWidth(r.width); move.appear.forEach(a => { this.g_pieces[a.x][a.y] = document.createElement("piece"); - C.AddClass_es(this.g_pieces[a.x][a.y], this.pieces()[a.p]["class"]); + C.AddClass_es(this.g_pieces[a.x][a.y], + this.pieces(a.c, a.x, a.y)[a.p]["class"]); this.g_pieces[a.x][a.y].classList.add(C.GetColorClass(a.c)); this.g_pieces[a.x][a.y].style.width = pieceWidth + "px"; this.g_pieces[a.x][a.y].style.height = pieceWidth + "px"; @@ -2240,12 +2243,12 @@ export default class ChessRules { movingPiece.style.height = pieceWidth + "px"; } const maxDist = this.getMaxDistance(r); - const pieces = this.pieces(); + const apparentColor = this.getColor(move.start.x, move.start.y); + const pieces = this.pieces(apparentColor, move.start.x, move.start.y); if (move.drag) { const startCode = this.getPiece(move.start.x, move.start.y); C.RemoveClass_es(movingPiece, pieces[startCode]["class"]); C.AddClass_es(movingPiece, pieces[move.drag.p]["class"]); - const apparentColor = this.getColor(move.start.x, move.start.y); if (apparentColor != move.drag.c) { movingPiece.classList.remove(C.GetColorClass(apparentColor)); movingPiece.classList.add(C.GetColorClass(move.drag.c)); diff --git a/variants/Alapo/style.css b/variants/Alapo/style.css index 942f9f4..4fbefb1 100644 --- a/variants/Alapo/style.css +++ b/variants/Alapo/style.css @@ -16,7 +16,7 @@ piece.black.babyrook { piece.black.babybishop { background-image: url('/pieces/Alapo/black_triangle.svg'); } -piece.black.babybishop { +piece.black.babybishop_inv { background-image: url('/pieces/Alapo/black_triangle_inv.svg'); } piece.black.babyqueen { @@ -29,7 +29,7 @@ piece.white.rook { piece.white.bishop { background-image: url('/pieces/Alapo/white_TRIANGLE.svg'); } -piece.white.bishop { +piece.white.bishop_inv { background-image: url('/pieces/Alapo/white_TRIANGLE_inv.svg'); } piece.white.queen { @@ -41,7 +41,7 @@ piece.white.babyrook { piece.white.babybishop { background-image: url('/pieces/Alapo/white_triangle.svg'); } -piece.white.babybishop { +piece.white.babybishop_inv { background-image: url('/pieces/Alapo/white_triangle_inv.svg'); } piece.white.babyqueen { -- 2.44.0 From e2be4b04faeac3fca5b292499bb70b98542e45ce Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 21 Jun 2022 19:36:14 +0200 Subject: [PATCH 11/16] Add Cleopatra option to Benedict chess --- pieces/Benedict/black_cleopatra.svg | 1 + pieces/Benedict/white_cleopatra.svg | 1 + variants.js | 1 - variants/Benedict/CREDITS | 4 +++ variants/Benedict/class.js | 52 ++++++++++++++++++++++------- variants/Benedict/rules.html | 8 +++++ variants/Benedict/style.css | 8 +++++ 7 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 pieces/Benedict/black_cleopatra.svg create mode 100644 pieces/Benedict/white_cleopatra.svg create mode 100644 variants/Benedict/CREDITS diff --git a/pieces/Benedict/black_cleopatra.svg b/pieces/Benedict/black_cleopatra.svg new file mode 100644 index 0000000..4360044 --- /dev/null +++ b/pieces/Benedict/black_cleopatra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pieces/Benedict/white_cleopatra.svg b/pieces/Benedict/white_cleopatra.svg new file mode 100644 index 0000000..fd7f323 --- /dev/null +++ b/pieces/Benedict/white_cleopatra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/variants.js b/variants.js index 90e9244..59a69c0 100644 --- a/variants.js +++ b/variants.js @@ -1,5 +1,4 @@ const variants = [ - // TODO: https://mancala.fandom.com/wiki/William_Daniel_Troyka Cleopatra chess {name: 'Absorption', desc: 'Absorb powers'}, {name: 'Alapo', desc: 'Geometric Chess'}, // {name: 'Alice', desc: 'Both sides of the mirror'}, diff --git a/variants/Benedict/CREDITS b/variants/Benedict/CREDITS new file mode 100644 index 0000000..e936f02 --- /dev/null +++ b/variants/Benedict/CREDITS @@ -0,0 +1,4 @@ +Images: + +https://game-icons.net/1x1/delapouite/cleopatra.html +https://www.svgrepo.com/svg/322047/cleopatra diff --git a/variants/Benedict/class.js b/variants/Benedict/class.js index 81fee87..2bc60e7 100644 --- a/variants/Benedict/class.js +++ b/variants/Benedict/class.js @@ -6,6 +6,14 @@ export default class BenedictRules extends ChessRules { static get Options() { return { select: C.Options.select, + input: [ + { + label: "Cleopatra", + variable: "cleopatra", + type: "checkbox", + defaut: false + } + ], styles: [ "balance", "cylinder", @@ -25,6 +33,24 @@ export default class BenedictRules extends ChessRules { return false; } + pieces(color, x, y) { + if (!this.options["cleopatra"]) + return super.pieces(color, x, y); + return Object.assign({}, super.pieces(color, x, y), { + 'q': { + "class": "cleopatra", + moves: [ + { + steps: [ + [0, 1], [0, -1], [1, 0], [-1, 0], + [1, 1], [1, -1], [-1, 1], [-1, -1] + ] + } + ] + }, + }); + } + // Find potential captures from a square // follow steps from x,y until something is met. findAttacks([x, y]) { @@ -56,20 +82,22 @@ export default class BenedictRules extends ChessRules { postProcessPotentialMoves(moves) { moves.forEach(m => { - super.playOnBoard(m); - let attacks = this.findAttacks([m.end.x, m.end.y]) - if (this.options["zen"]) { - let endSquares = {}; - super.findCapturesOn([m.end.x, m.end.y], {zen: true}).forEach(c => { - endSquares[C.CoordsToSquare(c.end)] = true; + m.flips = []; + if (!this.options["cleopatra"] || m.vanish[0].p == 'q') { + super.playOnBoard(m); + let attacks = this.findAttacks([m.end.x, m.end.y]) + if (this.options["zen"]) { + let endSquares = {}; + super.findCapturesOn([m.end.x, m.end.y], {zen: true}).forEach(c => { + endSquares[C.CoordsToSquare(c.end)] = true; + }); + Array.prototype.push.apply(attacks, Object.keys(endSquares)); + } + super.undoOnBoard(m); + attacks.map(C.SquareToCoords).forEach(a => { + m.flips.push({x: a.x, y: a.y}); }); - Array.prototype.push.apply(attacks, Object.keys(endSquares)); } - super.undoOnBoard(m); - m.flips = []; - attacks.map(C.SquareToCoords).forEach(a => { - m.flips.push({x: a.x, y: a.y}); - }); }); return moves; } diff --git a/variants/Benedict/rules.html b/variants/Benedict/rules.html index 1a21c0c..92f633e 100644 --- a/variants/Benedict/rules.html +++ b/variants/Benedict/rules.html @@ -2,4 +2,12 @@

Goal: change the enemy king's color.

+

+ The "Cleopatra" option follow + + these rules + + where only the queen can change the color of enemy pieces. +

+

William Daniel Troyka (2001).

diff --git a/variants/Benedict/style.css b/variants/Benedict/style.css index a3550bc..0923e9e 100644 --- a/variants/Benedict/style.css +++ b/variants/Benedict/style.css @@ -1 +1,9 @@ @import url("/base_pieces.css"); + +piece.black.cleopatra { + background-image: url('/pieces/Benedict/black_cleopatra.svg'); +} + +piece.white.cleopatra { + background-image: url('/pieces/Benedict/white_cleopatra.svg'); +} -- 2.44.0 From 65cf1690c6119c949e2ea8feba8835b6e90b79a2 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 22 Jun 2022 11:53:09 +0200 Subject: [PATCH 12/16] Add Alice Chess, fix a few things in base_rules.js --- base_rules.js | 49 ++++-- pieces/Ambiguous/red_target.svg | 78 +++++++++ pieces/Ambiguous/target.svg | 191 ----------------------- pieces/Ambiguous/yellow_target.svg | 73 +++++++++ pieces/Benedict/CREDITS | 7 + pieces/Benedict/black_cleopatra.svg | 2 +- pieces/Benedict/black_cleopatra_TODO.svg | 1 + pieces/Benedict/white_cleopatra.svg | 2 +- pieces/Benedict/white_cleopatra_TODO.svg | 1 + {variants => pieces}/Chakart/CREDITS | 2 - variants.js | 2 +- variants/Alapo/class.js | 13 +- variants/Alapo/rules.html | 2 + variants/Alice/class.js | 126 +++++++++++++++ variants/Alice/rules.html | 7 + variants/Alice/style.css | 39 +++++ variants/Ambiguous/CREDITS | 3 - variants/Ambiguous/class.js | 12 +- variants/Ambiguous/style.css | 7 +- variants/Benedict/CREDITS | 4 - variants/Benedict/class.js | 18 +-- 21 files changed, 393 insertions(+), 246 deletions(-) create mode 100644 pieces/Ambiguous/red_target.svg delete mode 100644 pieces/Ambiguous/target.svg create mode 100644 pieces/Ambiguous/yellow_target.svg create mode 100644 pieces/Benedict/CREDITS mode change 100644 => 120000 pieces/Benedict/black_cleopatra.svg create mode 100644 pieces/Benedict/black_cleopatra_TODO.svg mode change 100644 => 120000 pieces/Benedict/white_cleopatra.svg create mode 100644 pieces/Benedict/white_cleopatra_TODO.svg rename {variants => pieces}/Chakart/CREDITS (97%) create mode 100644 variants/Alice/class.js create mode 100644 variants/Alice/rules.html create mode 100644 variants/Alice/style.css delete mode 100644 variants/Ambiguous/CREDITS delete mode 100644 variants/Benedict/CREDITS diff --git a/base_rules.js b/base_rules.js index 4b387b6..a48bd3d 100644 --- a/base_rules.js +++ b/base_rules.js @@ -1082,8 +1082,9 @@ export default class ChessRules { } // Piece type on square (i,j) - getPieceType(i, j) { - const p = this.getPiece(i, j); + getPieceType(i, j, p) { + if (!p) + p = this.getPiece(i, j); return C.CannibalKings[p] || p; //a cannibal king move as... } @@ -1092,7 +1093,7 @@ export default class ChessRules { return (color == "w" ? "b" : "w"); } - // Can thing on square1 capture (no return) thing on square2? + // Can thing on square1 capture (enemy) thing on square2? canTake([x1, y1], [x2, y2]) { return (this.getColor(x1, y1) !== this.getColor(x2, y2)); } @@ -1214,6 +1215,8 @@ export default class ChessRules { if (this.board[i][j] != "" && this.getColor(i, j) == color) { const allSpecs = this.pieces(color, i, j) let specs = allSpecs[this.getPieceType(i, j)]; + if (specs.moveas) + specs = allSpecs[specs.moveas]; const attacks = specs.attack || specs.moves; for (let a of attacks) { outerLoop: for (let step of a.steps) { @@ -1482,7 +1485,10 @@ export default class ChessRules { const color = this.getColor(x, y); const oppCol = C.GetOppCol(color); const piece = this.getPieceType(x, y); //ok not cannibal king - const stepSpec = this.pieces(color, x, y)[piece]; + const allSpecs = this.pieces(color, x, y); + let stepSpec = allSpecs[piece]; + if (stepSpec.moveas) + stepSpec = allSpecs[stepSpec.moveas]; const attacks = stepSpec.attack || stepSpec.moves; for (let a of attacks) { outerLoop: for (let step of a.steps) { @@ -1506,7 +1512,7 @@ export default class ChessRules { return false; } - canStepOver(i, j) { + canStepOver(i, j, p) { // In some variants, objects on boards don't stop movement (Chakart) return this.board[i][j] == ""; } @@ -1514,7 +1520,11 @@ export default class ChessRules { // Generic method to find possible moves of "sliding or jumping" pieces getPotentialMovesOf(piece, [x, y]) { const color = this.getColor(x, y); - const stepSpec = this.pieces(color, x, y)[piece]; + const apparentPiece = this.getPiece(x, y); //how it looks + const allSpecs = this.pieces(color, x, y); + let stepSpec = allSpecs[piece]; + if (stepSpec.moveas) + stepSpec = allSpecs[stepSpec.moveas]; let moves = []; // Next 3 for Cylinder mode: let explored = {}; @@ -1539,7 +1549,7 @@ export default class ChessRules { let stepCounter = 0; while ( this.onBoard(i, j) && - (this.canStepOver(i, j) || (i == x && j == y)) + ((i == x && j == y) || this.canStepOver(i, j, apparentPiece)) ) { if ( type != "attack" && @@ -1610,8 +1620,14 @@ export default class ChessRules { ) { if (args.zen && this.isKing(this.getPiece(i, j))) continue; //king not captured in this way - const stepSpec = - this.pieces(args.oppCol, i, j)[this.getPieceType(i, j)]; + const apparentPiece = this.getPiece(i, j); + // Quick check: does this potential attacker target x,y ? + if (this.canStepOver(x, y, apparentPiece)) + continue; + const allSpecs = this.pieces(args.oppCol, i, j); + let stepSpec = allSpecs[this.getPieceType(i, j)]; + if (stepSpec.moveas) + stepSpec = allSpecs[stepSpec.moveas]; const attacks = stepSpec.attack || stepSpec.moves; for (let a of attacks) { for (let s of a.steps) { @@ -1754,8 +1770,15 @@ export default class ChessRules { s.y == e.y && Math.abs(s.x - e.x) == 2 && // Next conditions for variants like Atomic or Rifle, Recycle... - (move.appear.length > 0 && move.appear[0].p == "p") && - (move.vanish.length > 0 && move.vanish[0].p == "p") + ( + move.appear.length > 0 && + this.getPieceType(0, 0, move.appear[0].p) == "p" + ) + && + ( + move.vanish.length > 0 && + this.getPieceType(0, 0, move.vanish[0].p) == "p" + ) ) { return { x: (s.x + e.x) / 2, @@ -1945,12 +1968,12 @@ export default class ChessRules { let square = kingPos, res = true; //a priori valid if (m.vanish.some(v => { - return C.CannibalKings[v.p] && v.c == color; + return this.isKing(v.p) && v.c == color; })) { // Search king in appear array: const newKingIdx = m.appear.findIndex(a => { - return C.CannibalKings[a.p] && a.c == color; + return this.isKing(a.p) && a.c == color; }); if (newKingIdx >= 0) square = [m.appear[newKingIdx].x, m.appear[newKingIdx].y]; diff --git a/pieces/Ambiguous/red_target.svg b/pieces/Ambiguous/red_target.svg new file mode 100644 index 0000000..2f7bb8c --- /dev/null +++ b/pieces/Ambiguous/red_target.svg @@ -0,0 +1,78 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/pieces/Ambiguous/target.svg b/pieces/Ambiguous/target.svg deleted file mode 100644 index 53f0ccc..0000000 --- a/pieces/Ambiguous/target.svg +++ /dev/null @@ -1,191 +0,0 @@ - - - - Target - - - - - - - - - - - - - - image/svg+xml - - - - - Openclipart - - - Target - 2012-02-15T07:37:04 - Target symbol - https://openclipart.org/detail/168253/target-by-fanda@cz - - - Fanda@CZ - - - - - target - - - - - - - - - - - diff --git a/pieces/Ambiguous/yellow_target.svg b/pieces/Ambiguous/yellow_target.svg new file mode 100644 index 0000000..400b872 --- /dev/null +++ b/pieces/Ambiguous/yellow_target.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/pieces/Benedict/CREDITS b/pieces/Benedict/CREDITS new file mode 100644 index 0000000..7844bd6 --- /dev/null +++ b/pieces/Benedict/CREDITS @@ -0,0 +1,7 @@ +Unusable? +https://game-icons.net/1x1/delapouite/cleopatra.html +https://www.svgrepo.com/svg/322047/cleopatra + +PNG? or paying... +https://www.flaticon.com/free-icon/cleopatra_1393471 +https://www.flaticon.com/fr/icone-gratuite/cleopatre_1929899 diff --git a/pieces/Benedict/black_cleopatra.svg b/pieces/Benedict/black_cleopatra.svg deleted file mode 100644 index 4360044..0000000 --- a/pieces/Benedict/black_cleopatra.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pieces/Benedict/black_cleopatra.svg b/pieces/Benedict/black_cleopatra.svg new file mode 120000 index 0000000..1669cd3 --- /dev/null +++ b/pieces/Benedict/black_cleopatra.svg @@ -0,0 +1 @@ +../black_queen.svg \ No newline at end of file diff --git a/pieces/Benedict/black_cleopatra_TODO.svg b/pieces/Benedict/black_cleopatra_TODO.svg new file mode 100644 index 0000000..4360044 --- /dev/null +++ b/pieces/Benedict/black_cleopatra_TODO.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pieces/Benedict/white_cleopatra.svg b/pieces/Benedict/white_cleopatra.svg deleted file mode 100644 index fd7f323..0000000 --- a/pieces/Benedict/white_cleopatra.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pieces/Benedict/white_cleopatra.svg b/pieces/Benedict/white_cleopatra.svg new file mode 120000 index 0000000..e31fd1d --- /dev/null +++ b/pieces/Benedict/white_cleopatra.svg @@ -0,0 +1 @@ +../white_queen.svg \ No newline at end of file diff --git a/pieces/Benedict/white_cleopatra_TODO.svg b/pieces/Benedict/white_cleopatra_TODO.svg new file mode 100644 index 0000000..fd7f323 --- /dev/null +++ b/pieces/Benedict/white_cleopatra_TODO.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/variants/Chakart/CREDITS b/pieces/Chakart/CREDITS similarity index 97% rename from variants/Chakart/CREDITS rename to pieces/Chakart/CREDITS index 53a9181..11b0bb7 100644 --- a/variants/Chakart/CREDITS +++ b/pieces/Chakart/CREDITS @@ -1,5 +1,3 @@ -Images: - https://fr.m.wikiversity.org/wiki/Fichier:Tango_Style_Mushroom_icon.svg https://commons.wikimedia.org/wiki/File:Tux_Paint_banana.svg https://www.onlinewebfonts.com/icon/425540 diff --git a/variants.js b/variants.js index 59a69c0..8918f47 100644 --- a/variants.js +++ b/variants.js @@ -1,7 +1,7 @@ const variants = [ {name: 'Absorption', desc: 'Absorb powers'}, {name: 'Alapo', desc: 'Geometric Chess'}, -// {name: 'Alice', desc: 'Both sides of the mirror'}, + {name: 'Alice', desc: 'Both sides of the mirror'}, // {name: 'Align4', desc: 'Align four pawns'}, // {name: 'Allmate', desc: 'Mate any piece'}, {name: 'Ambiguous', desc: "Play opponent's pieces"}, diff --git a/variants/Alapo/class.js b/variants/Alapo/class.js index eb0e73f..540747d 100644 --- a/variants/Alapo/class.js +++ b/variants/Alapo/class.js @@ -81,15 +81,14 @@ export default class AlapoRules extends ChessRules { ); } + // Triangles are rotated from opponent viewpoint (=> suffix "_inv") pieces(color, x, y) { + const allSpecs = super.pieces(color, x, y); return { - 'r': super.pieces(color, x, y)['r'], - 'q': super.pieces(color, x, y)['q'], - 'b': { - // Triangle is rotated from opponent viewpoint - "class": "bishop" + (this.playerColor != color ? "_inv" : ""), - moves: [ { steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]] } ] - }, + 'r': allSpecs['r'], + 'q': allSpecs['q'], + 'b': Object.assign({}, allSpecs['b'], + {"class": "bishop" + (this.playerColor != color ? "_inv" : "")}), 's': { //"square" "class": "babyrook", moves: [ diff --git a/variants/Alapo/rules.html b/variants/Alapo/rules.html index 252699c..9ec57e0 100644 --- a/variants/Alapo/rules.html +++ b/variants/Alapo/rules.html @@ -5,3 +5,5 @@ chessvariants page. + +

Johannes Tranelis (1982).

diff --git a/variants/Alice/class.js b/variants/Alice/class.js new file mode 100644 index 0000000..1957383 --- /dev/null +++ b/variants/Alice/class.js @@ -0,0 +1,126 @@ +import ChessRules from "/base_rules.js"; +import { ArrayFun } from "/utils/array.js"; + +export default class AliceRules extends ChessRules { + + static get Options() { + return { + select: C.Options.select, + input: C.Options.input, + styles: [ + "balance", + "capture", + "cylinder", + "dark", + "doublemove", + "progressive", + "zen" + ] + }; + } + + // To the other side of the mirror and back... + static get ALICE_PIECES() { + return { + s: "p", + u: "r", + o: "n", + c: "b", + t: "q", + l: "k" + }; + } + static get ALICE_CODES() { + return { + p: "s", + r: "u", + n: "o", + b: "c", + q: "t", + k: "l" + }; + } + + getPieceType(x, y, p) { + if (!p) + p = super.getPiece(x, y); + return V.ALICE_PIECES[p] || p; + } + + pieces(color, x, y) { + let alices = { + 's': {"class": "alice-pawn", "moveas": "p"}, + 'u': {"class": "alice-rook", "moveas": "r"}, + 'o': {"class": "alice-knight", "moveas": "n"}, + 'c': {"class": "alice-bishop", "moveas": "b"}, + 't': {"class": "alice-queen", "moveas": "q"}, + 'l': {"class": "alice-king", "moveas": "k"} + }; + return Object.assign(alices, super.pieces(color, x, y)); + } + + fromSameWorld(p1, p2) { + return ( + (V.ALICE_PIECES[p1] && V.ALICE_PIECES[p2]) || + (V.ALICE_CODES[p1] && V.ALICE_CODES[p2]) + ); + } + + // Step of p over i,j ? + canStepOver(i, j, p) { + return ( + this.board[i][j] == "" || !this.fromSameWorld(this.getPiece(i, j), p)); + } + + // NOTE: castle & enPassant + // https://www.chessvariants.com/other.dir/alice.html + getPotentialMovesFrom([x, y]) { + return super.getPotentialMovesFrom([x, y]).filter(m => { + // Remove moves landing on occupied square on other board + return ( + this.board[m.end.x][m.end.y] == "" || + this.fromSameWorld(m.vanish[0].p, m.vanish[1].p) + ); + }).map(m => { + // Apply Alice rule: go to the other side of the mirror + if (Object.keys(V.ALICE_CODES).includes(m.vanish[0].p)) + // Board 1 + m.appear.forEach(a => a.p = V.ALICE_CODES[a.p]) + else + // Board 2 + m.appear.forEach(a => a.p = V.ALICE_PIECES[a.p]) + return m; + }); + } + + isKing(symbol) { + return ['k', 'l'].includes(symbol); + } + + getCurrentScore() { + const color = this.turn; + const inCheck = this.underCheck(this.searchKingPos(color)); + let someLegalMove = false; + // Search for legal moves: if any is found and + // does not change king world (if under check), then game not over. + for (let i=0; i= 1 && + ( + !inCheck || + moves.some(m => m.vanish.every(v => !this.isKing(v.p))) + ) + ) { + return "*"; + } + } + } + } + // Couldn't find any legal move + return (inCheck ? "1/2" : (color == 'w' ? "0-1" : "1-0")); + } + +}; diff --git a/variants/Alice/rules.html b/variants/Alice/rules.html new file mode 100644 index 0000000..98659ea --- /dev/null +++ b/variants/Alice/rules.html @@ -0,0 +1,7 @@ +

Pieces move to the next board after playing (other side of the mirror).

+ + + chessvariants page. + + +

Vernon R. Parton (1953).

diff --git a/variants/Alice/style.css b/variants/Alice/style.css new file mode 100644 index 0000000..2683462 --- /dev/null +++ b/variants/Alice/style.css @@ -0,0 +1,39 @@ +@import url("/base_pieces.css"); + +piece.white.alice-pawn { + background-image: url('/pieces/yellow_pawn.svg'); +} +piece.white.alice-rook { + background-image: url('/pieces/yellow_rook.svg'); +} +piece.white.alice-knight { + background-image: url('/pieces/yellow_knight.svg'); +} +piece.white.alice-bishop { + background-image: url('/pieces/yellow_bishop.svg'); +} +piece.white.alice-queen { + background-image: url('/pieces/yellow_queen.svg'); +} +piece.white.alice-king { + background-image: url('/pieces/yellow_king.svg'); +} + +piece.black.alice-pawn { + background-image: url('/pieces/red_pawn.svg'); +} +piece.black.alice-rook { + background-image: url('/pieces/red_rook.svg'); +} +piece.black.alice-knight { + background-image: url('/pieces/red_knight.svg'); +} +piece.black.alice-bishop { + background-image: url('/pieces/red_bishop.svg'); +} +piece.black.alice-queen { + background-image: url('/pieces/red_queen.svg'); +} +piece.black.alice-king { + background-image: url('/pieces/red_king.svg'); +} diff --git a/variants/Ambiguous/CREDITS b/variants/Ambiguous/CREDITS deleted file mode 100644 index 68ab598..0000000 --- a/variants/Ambiguous/CREDITS +++ /dev/null @@ -1,3 +0,0 @@ -Images: - -https://freesvg.org/black-target diff --git a/variants/Ambiguous/class.js b/variants/Ambiguous/class.js index 6a001a6..58458b6 100644 --- a/variants/Ambiguous/class.js +++ b/variants/Ambiguous/class.js @@ -55,14 +55,10 @@ export default class AmbiguousRules extends ChessRules { return true; }) .map(m => { - if (m.vanish.length == 1) { - m.appear[0].c = 'a'; //a-color + if (m.vanish.length == 1) m.appear[0].p = V.GOAL; - } - else { + else m.appear[0].p = V.TARGET_CODE[m.vanish[1].p]; - m.appear[0].c = oppCol; - } m.vanish.shift(); return m; }) @@ -133,8 +129,8 @@ export default class AmbiguousRules extends ChessRules { 't': {"class": "target-queen", moves: []}, 'l': {"class": "target-king", moves: []} }; - return Object.assign( - { 'g': {"class": "target"} }, targets, super.pieces(color, x, y)); + return Object.assign({ 'g': {"class": "target", moves: []} }, + targets, super.pieces(color, x, y)); } atLeastOneMove() { diff --git a/variants/Ambiguous/style.css b/variants/Ambiguous/style.css index db300f7..f8dac72 100644 --- a/variants/Ambiguous/style.css +++ b/variants/Ambiguous/style.css @@ -1,7 +1,10 @@ @import url("/base_pieces.css"); -piece.target { - background-image: url('/pieces/Ambiguous/target.svg'); +piece.white.target { + background-image: url('/pieces/Ambiguous/yellow_target.svg'); +} +piece.black.target { + background-image: url('/pieces/Ambiguous/red_target.svg'); } piece.white.target-pawn { diff --git a/variants/Benedict/CREDITS b/variants/Benedict/CREDITS deleted file mode 100644 index e936f02..0000000 --- a/variants/Benedict/CREDITS +++ /dev/null @@ -1,4 +0,0 @@ -Images: - -https://game-icons.net/1x1/delapouite/cleopatra.html -https://www.svgrepo.com/svg/322047/cleopatra diff --git a/variants/Benedict/class.js b/variants/Benedict/class.js index 2bc60e7..e055e86 100644 --- a/variants/Benedict/class.js +++ b/variants/Benedict/class.js @@ -36,19 +36,11 @@ export default class BenedictRules extends ChessRules { pieces(color, x, y) { if (!this.options["cleopatra"]) return super.pieces(color, x, y); - return Object.assign({}, super.pieces(color, x, y), { - 'q': { - "class": "cleopatra", - moves: [ - { - steps: [ - [0, 1], [0, -1], [1, 0], [-1, 0], - [1, 1], [1, -1], [-1, 1], [-1, -1] - ] - } - ] - }, - }); + const allSpecs = super.pieces(color, x, y); + return Object.assign({}, + allSpecs, + {'q': Object.assign({}, allSpecs['q'], {"class": "cleopatra"})} + ); } // Find potential captures from a square -- 2.44.0 From 475da4ac3761e5e27f06fffedb537f501688ae85 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 22 Jun 2022 12:40:43 +0200 Subject: [PATCH 13/16] Add some links --- TODO | 3 --- variants/Ambiguous/rules.html | 4 ++++ variants/Atomic/rules.html | 4 ++++ variants/Benedict/rules.html | 4 ++++ variants/Cannibal/rules.html | 4 ++++ variants/Crazyhouse/rules.html | 4 ++++ variants/Dark/rules.html | 4 ++++ variants/Doublemove/rules.html | 4 ++++ variants/Giveaway/rules.html | 4 ++++ variants/Hex/rules.html | 4 ++++ variants/Madrasi/rules.html | 4 ++++ variants/Progressive/rules.html | 4 ++++ variants/Recycle/rules.html | 4 ++++ variants/Refusal/rules.html | 4 ++++ variants/Rifle/rules.html | 4 ++++ variants/Suction/rules.html | 4 ++++ 16 files changed, 60 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index 73b76b0..0b9f7d7 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,3 @@ -Debug Alapo -Add links to complete variants rules (Ambiguous...) - add variants : Dark Racing Kings ? Checkered-Teleport ? diff --git a/variants/Ambiguous/rules.html b/variants/Ambiguous/rules.html index b51c747..acd3585 100644 --- a/variants/Ambiguous/rules.html +++ b/variants/Ambiguous/rules.html @@ -8,4 +8,8 @@ your opponent, then to choose one for you - which could be altered.

+ + chessvariants page. + +

Fabrice Liardet (2005).

diff --git a/variants/Atomic/rules.html b/variants/Atomic/rules.html index c46cf22..6067ed5 100644 --- a/variants/Atomic/rules.html +++ b/variants/Atomic/rules.html @@ -4,3 +4,7 @@

Win by checkmate or by exploding the enemy king.

+ + + lichess page. + diff --git a/variants/Benedict/rules.html b/variants/Benedict/rules.html index 92f633e..db103a1 100644 --- a/variants/Benedict/rules.html +++ b/variants/Benedict/rules.html @@ -10,4 +10,8 @@ where only the queen can change the color of enemy pieces.

+ + chessvariants page. + +

William Daniel Troyka (2001).

diff --git a/variants/Cannibal/rules.html b/variants/Cannibal/rules.html index b9791a2..480b5cf 100644 --- a/variants/Cannibal/rules.html +++ b/variants/Cannibal/rules.html @@ -1 +1,5 @@

After each capture, the capturer transforms into the captured piece.

+ + + chessvariants page. + diff --git a/variants/Crazyhouse/rules.html b/variants/Crazyhouse/rules.html index ecb0837..148015a 100644 --- a/variants/Crazyhouse/rules.html +++ b/variants/Crazyhouse/rules.html @@ -1,3 +1,7 @@

Captured pieces can be landed on the board in place of a normal turn.

Promoted pawns return on the board as pawns.

+ + + lichess page. + diff --git a/variants/Dark/rules.html b/variants/Dark/rules.html index 8716272..137a626 100644 --- a/variants/Dark/rules.html +++ b/variants/Dark/rules.html @@ -2,4 +2,8 @@

Win by capturing the enemy king.

+ + Wikipedia page. + +

Jens Baek Nielsen (1997).

diff --git a/variants/Doublemove/rules.html b/variants/Doublemove/rules.html index 56054c8..5ac21d4 100644 --- a/variants/Doublemove/rules.html +++ b/variants/Doublemove/rules.html @@ -1,3 +1,7 @@

After the initial white move, each player moves twice on each turn.

+ + chessvariants page. + +

Albert Fortis (1922).

diff --git a/variants/Giveaway/rules.html b/variants/Giveaway/rules.html index 920b382..3e257bd 100644 --- a/variants/Giveaway/rules.html +++ b/variants/Giveaway/rules.html @@ -1 +1,5 @@

Win by losing all your material, or get stalemated.

+ + + lichess page. + diff --git a/variants/Hex/rules.html b/variants/Hex/rules.html index 04e700d..15a91b1 100644 --- a/variants/Hex/rules.html +++ b/variants/Hex/rules.html @@ -4,4 +4,8 @@ Detailed rules. + + A Strategy Guide. + +

Piet Hein (1942).

diff --git a/variants/Madrasi/rules.html b/variants/Madrasi/rules.html index 10bfd87..741869c 100644 --- a/variants/Madrasi/rules.html +++ b/variants/Madrasi/rules.html @@ -1,3 +1,7 @@

Pieces of same nature attacking each other are immobilized.

+ + Wikipedia page. + +

Abdul J. Karwathar (1979).

diff --git a/variants/Progressive/rules.html b/variants/Progressive/rules.html index 51ec7f2..0521495 100644 --- a/variants/Progressive/rules.html +++ b/variants/Progressive/rules.html @@ -2,3 +2,7 @@ White play one move, then Black play two in a row, then White play 3, and so on.

+ + + Wikipedia page. + diff --git a/variants/Recycle/rules.html b/variants/Recycle/rules.html index b9b8008..ceea70c 100644 --- a/variants/Recycle/rules.html +++ b/variants/Recycle/rules.html @@ -3,4 +3,8 @@ can be self-captured and dropped later in the game.

+ + chessvariants page. + +

Robert Huber (2000).

diff --git a/variants/Refusal/rules.html b/variants/Refusal/rules.html index 8efc3ce..5c0b533 100644 --- a/variants/Refusal/rules.html +++ b/variants/Refusal/rules.html @@ -3,4 +3,8 @@ Different pawn promotions count as different moves.

+ + chessvariants page. + +

Fred Galvin (1958).

diff --git a/variants/Rifle/rules.html b/variants/Rifle/rules.html index 495bdf3..650264b 100644 --- a/variants/Rifle/rules.html +++ b/variants/Rifle/rules.html @@ -1,3 +1,7 @@

Pieces capture (as usual) without moving.

+ + chessvariants page. + +

William Buehler Seabrook (1921).

diff --git a/variants/Suction/rules.html b/variants/Suction/rules.html index 4006ce0..a9cfb2e 100644 --- a/variants/Suction/rules.html +++ b/variants/Suction/rules.html @@ -5,4 +5,8 @@

Win by bringing the enemy king on your first rank.

+ + chessvariants page. + +

Nathaniel Virgo (2018).

-- 2.44.0 From 4cec374b0172e0888aa2fa33283ad72210be6e56 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 22 Jun 2022 16:22:13 +0200 Subject: [PATCH 14/16] Fix memory leak of moves hash on server side. Draft Align4 --- server.js | 11 +++--- variants.js | 2 +- variants/Align4/class.js | 81 ++++++++++++++++++++++++++++++++++++++ variants/Align4/rules.html | 6 +++ variants/Align4/style.css | 1 + 5 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 variants/Align4/class.js create mode 100644 variants/Align4/rules.html create mode 100644 variants/Align4/style.css diff --git a/server.js b/server.js index dc8bd97..e08d13b 100644 --- a/server.js +++ b/server.js @@ -7,7 +7,6 @@ const wss = new WebSocket.Server({ let challenges = {}; //variantName --> socketId, name let games = {}; //gameId --> gameInfo (vname, fen, players, options, time) -let moveHash = {}; //gameId --> set of hashes seen so far let sockets = {}; //socketId --> socket const variants = require("./variants.js"); const Crypto = require("crypto"); @@ -28,14 +27,14 @@ function initializeGame(vname, players, options) { vname: vname, players: players, options: options, - time: Date.now() + time: Date.now(), + moveHash: {} //set of moves hashes seen so far }; return gid; } // Provide seed in case of, so that both players initialize with same FEN function launchGame(gid) { - moveHash[gid] = {}; const gameInfo = Object.assign( {seed: Math.floor(Math.random() * 19840), gid: gid}, games[gid] @@ -195,11 +194,11 @@ wss.on("connection", (socket, req) => { const hash = Crypto.createHash("md5") .update(JSON.stringify(obj.fen)) .digest("hex"); - if (moveHash[hash]) + if (games[obj.gid].moveHash[hash]) break; - moveHash[hash] = true; + games[obj.gid].moveHash[hash] = true; games[obj.gid].fen = obj.fen; - games[obj.gid].time = Date.now(); //update timestamp in case of + games[obj.gid].time = Date.now(); //update useful if verrry slow game const playingWhite = (games[obj.gid].players[0].sid == sid); const oppSid = games[obj.gid].players[playingWhite ? 1 : 0].sid; send(oppSid, "newmove", {moves: obj.moves}); diff --git a/variants.js b/variants.js index 8918f47..5d04f45 100644 --- a/variants.js +++ b/variants.js @@ -2,7 +2,7 @@ const variants = [ {name: 'Absorption', desc: 'Absorb powers'}, {name: 'Alapo', desc: 'Geometric Chess'}, {name: 'Alice', desc: 'Both sides of the mirror'}, -// {name: 'Align4', desc: 'Align four pawns'}, + {name: 'Align4', desc: 'Align four pawns'}, // {name: 'Allmate', desc: 'Mate any piece'}, {name: 'Ambiguous', desc: "Play opponent's pieces"}, // {name: 'Antiking1', desc: 'Keep antiking in check', disp: 'Anti-King'}, diff --git a/variants/Align4/class.js b/variants/Align4/class.js new file mode 100644 index 0000000..b7b0c4a --- /dev/null +++ b/variants/Align4/class.js @@ -0,0 +1,81 @@ +import ChessRules from "/base_rules.js"; + +export default class Align4Rules extends ChessRules { + + static get Options() { + return { + select: [{ + label: "Randomness", + variable: "randomness", + defaut: 0, + options: [ + {label: "Deterministic", value: 0}, + {label: "Random", value: 1} + ] + }], + styles: ["atomic", "capture", "cylinder"] + }; + } + + get hasReserve() { + return true; + } + get hasReserveFen() { + return false; + } + + genRandInitFen(seed) { + const baseFen = super.genRandInitFen(seed); + return "4k3/8" + baseFen.substring(17, 50) + " -"; //TODO: + flags 1188 + } + + setOtherVariables(fenParsed) { + super.setOtherVariables(fenParsed); + this.reserve = { b: { p: 1 } }; + } + + // Just do not update any reserve (infinite supply) + updateReserve() {} + + getCastleMoves([x, y]) { + if (this.GetColor(x, y) == 'b') + return []; + return super.getCastleMoves([x, y]); + } + + getCurrentScore(move) { + const score = super.getCurrentScore(move); + if (score != "*") + return score; + // Check pawns connection: + for (let i = 0; i < this.size.x; i++) { + for (let j = 0; j < this.size.y; j++) { + if ( + this.board[i][j] != "" && + this.getColor(i, j) == 'b' && + this.getPiece(i, j) == 'p' + ) { + // Exploration "rightward + downward" is enough + for (let step of [[1, 0], [0, 1], [1, 1], [-1, 1]]) { + let [ii, jj] = [i + step[0], j + step[1]]; + let kounter = 1; + while ( + this.onBoard(ii, jj) && + this.board[ii][jj] != "" && + this.getColor(ii, jj) == 'b' && + this.getPiece(ii, jj) == 'p' + ) { + kounter++; + ii += step[0]; + jj += step[1]; + } + if (kounter == 4) + return "0-1"; + } + } + } + } + return "*"; + } + +}; diff --git a/variants/Align4/rules.html b/variants/Align4/rules.html new file mode 100644 index 0000000..691a7a8 --- /dev/null +++ b/variants/Align4/rules.html @@ -0,0 +1,6 @@ +

+ Black goal is to align 4 pawns, either orthogonaly or diagonaly. + White goal is to checkmate the black king. +

+ +

Fynmorph [Discord] (2021).

diff --git a/variants/Align4/style.css b/variants/Align4/style.css new file mode 100644 index 0000000..a3550bc --- /dev/null +++ b/variants/Align4/style.css @@ -0,0 +1 @@ +@import url("/base_pieces.css"); -- 2.44.0 From fc12475fd434835816796ece83d93341af6c1550 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Thu, 23 Jun 2022 19:00:17 +0200 Subject: [PATCH 15/16] Fix Align4, fix mushrooms effect for Chakart --- base_rules.js | 2 +- variants/Align4/class.js | 15 ++++++--------- variants/Chakart/class.js | 32 +++++++++++++++++++++----------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/base_rules.js b/base_rules.js index a48bd3d..3fac51b 100644 --- a/base_rules.js +++ b/base_rules.js @@ -252,7 +252,7 @@ export default class ChessRules { parts.push(`"flags":"${flags}"`); if (this.hasEnpassant) parts.push('"enpassant":"-"'); - if (this.hasReserve) + if (this.hasReserveFen) parts.push('"reserve":"000000000000"'); if (this.options["crazyhouse"]) parts.push('"ispawn":"-"'); diff --git a/variants/Align4/class.js b/variants/Align4/class.js index b7b0c4a..b2683aa 100644 --- a/variants/Align4/class.js +++ b/variants/Align4/class.js @@ -26,23 +26,20 @@ export default class Align4Rules extends ChessRules { genRandInitFen(seed) { const baseFen = super.genRandInitFen(seed); - return "4k3/8" + baseFen.substring(17, 50) + " -"; //TODO: + flags 1188 + const fen = baseFen.replace("rnbqkbnr/pppppppp", "4k3/8"); + const fenParts = baseFen.split(" "); + let others = JSON.parse(fenParts[3]); + others["flags"] = others["flags"].substr(0, 2) + "88"; + return fenParts.slice(0, 3).join(" ") + " " + JSON.stringify(others); } - setOtherVariables(fenParsed) { - super.setOtherVariables(fenParsed); + initReserves() { this.reserve = { b: { p: 1 } }; } // Just do not update any reserve (infinite supply) updateReserve() {} - getCastleMoves([x, y]) { - if (this.GetColor(x, y) == 'b') - return []; - return super.getCastleMoves([x, y]); - } - getCurrentScore(move) { const score = super.getCurrentScore(move); if (score != "*") diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js index 37261e4..24bbd3e 100644 --- a/variants/Chakart/class.js +++ b/variants/Chakart/class.js @@ -133,8 +133,12 @@ export default class ChakartRules extends ChessRules { genRandInitFen(seed) { const options = Object.assign({mode: "suicide"}, this.options); const gr = new GiveawayRules({options: options, genFenOnly: true}); - // Add Peach + mario flags - return gr.genRandInitFen(seed).slice(0, -17) + '{"flags":"1111"}'; + const baseFen = gr.genRandInitFen(seed); + const fenParts = baseFen.split(" "); + let others = JSON.parse(fenParts[3]); + delete others["enpassant"]; + others["flags"] = "1111"; //Peach + Mario flags + return fenParts.slice(0, 3).join(" ") + " " + JSON.stringify(others); } fen2board(f) { @@ -645,19 +649,25 @@ export default class ChakartRules extends ChessRules { } getMushroomEffect(move) { - if (typeof move.start.x == "string") //drop move (toadette) + if ( + typeof move.start.x == "string" || //drop move (toadette) + ['b', 'r', 'q'].includes(move.vanish[0].p) //slider + ) { 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)) { - // Slider, multi-squares: normalize step - for (let j of [0, 1]) - step[j] = step[j] / Math.abs(step[j]) || 0; } + let step = [move.end.x - move.start.x, move.end.y - move.start.y]; + if (Math.abs(step[0]) == 2 && Math.abs(step[1]) == 0) + // Pawn initial 2-squares move: normalize step + step[0] /= 2; const nextSquare = [move.end.x + step[0], move.end.y + step[1]]; - const afterSquare = - [nextSquare[0] + step[0], nextSquare[1] + step[1]]; let nextMove = null; - if (this.onBoard(nextSquare[0], nextSquare[1])) { + if ( + this.onBoard(nextSquare[0], nextSquare[1]) && + ( + this.board[nextSquare[0]][nextSquare[1]] == "" || + this.getColor(nextSquare[0], nextSquare[1]) == 'a' + ) + ) { this.playOnBoard(move); //HACK for getBasicMove() nextMove = this.getBasicMove([move.end.x, move.end.y], nextSquare); this.undoOnBoard(move); -- 2.44.0 From 5f08c59b29c2173cc8b2df1a3799ee971a14e691 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sat, 25 Jun 2022 12:22:15 +0200 Subject: [PATCH 16/16] Experimental: improve animation, reduce lags in stack moves sending. Add Allmate --- app.js | 49 +++++---- base_rules.js | 212 ++++++++++++++++++++++++++---------- variants.js | 2 +- variants/Allmate/class.js | 97 +++++++++++++++++ variants/Allmate/rules.html | 9 ++ variants/Allmate/style.css | 1 + variants/Chakart/class.js | 164 +++++++++++++++------------- variants/Suction/class.js | 9 ++ 8 files changed, 386 insertions(+), 157 deletions(-) create mode 100644 variants/Allmate/class.js create mode 100644 variants/Allmate/rules.html create mode 100644 variants/Allmate/style.css diff --git a/app.js b/app.js index 4c0b5e4..6f17c64 100644 --- a/app.js +++ b/app.js @@ -456,9 +456,28 @@ function notifyMe(code) { let curMoves = [], lastFen; -const afterPlay = (move_s) => { - const callbackAfterSend = () => { - curMoves = []; +const afterPlay = (move_s, newTurn, ops) => { + if (ops.send) { + // Pack into one moves array, then send (if turn changed) + if (Array.isArray(move_s)) + // Array of simple moves (e.g. Chakart) + Array.prototype.push.apply(curMoves, move_s); + else + // Usual case + curMoves.push(move_s); + if (newTurn != playerColor) { + send("newmove", + {gid: gid, moves: curMoves, fen: vr.getFen()}, + { + retry: true, + success: () => curMoves = [], + error: () => alert("Move not sent: reload page") + } + ); + } + } + if (ops.res && newTurn != playerColor) { + toggleTurnIndicator(false); //now all moves are sent and animated const result = vr.getCurrentScore(move_s); if (result != "*") { setTimeout(() => { @@ -466,23 +485,6 @@ const afterPlay = (move_s) => { send("gameover", {gid: gid}); }, 2000); } - }; - // Pack into one moves array, then send - if (Array.isArray(move_s)) - // Array of simple moves (e.g. Chakart) - Array.prototype.push.apply(curMoves, move_s); - else - // Usual case - curMoves.push(move_s); - if (vr.turn != playerColor) { - toggleTurnIndicator(false); - send("newmove", - {gid: gid, moves: curMoves, fen: vr.getFen()}, - { - retry: true, - success: callbackAfterSend, - error: () => alert("Move not sent: reload page") - }); } }; @@ -532,8 +534,9 @@ function initializeGame(obj) { afterPlay: afterPlay, options: options }); - if (!obj.fen) { - // Game creation: both players set FEN, in case of one is offline + const gameCreation = !obj.fen; + if (gameCreation) { + // Both players set FEN, in case of one is offline send("setfen", {gid: obj.gid, fen: vr.getFen()}); localStorage.setItem("gid", obj.gid); } @@ -547,7 +550,7 @@ function initializeGame(obj) { } const playerIndex = (playerColor == "w" ? 0 : 1); fillGameInfos(obj, 1 - playerIndex); - if (obj.players[playerIndex].randvar) + if (obj.players[playerIndex].randvar && gameCreation) toggleVisible("gameInfos"); else toggleVisible("boardContainer"); diff --git a/base_rules.js b/base_rules.js index 3fac51b..6562673 100644 --- a/base_rules.js +++ b/base_rules.js @@ -3,6 +3,21 @@ import { ArrayFun } from "/utils/array.js"; import PiPo from "/utils/PiPo.js"; import Move from "/utils/Move.js"; +// Helper class for move animation +class TargetObj { + + constructor(callOnComplete) { + this.value = 0; + this.target = 0; + this.callOnComplete = callOnComplete; + } + increment() { + if (++this.value == this.target) + this.callOnComplete(); + } + +}; + // NOTE: x coords: top to bottom (white perspective); y: left to right // NOTE: ChessRules is aliased as window.C, and variants as window.V export default class ChessRules { @@ -96,6 +111,10 @@ export default class ChessRules { return !!this.options["dark"]; } + get hasMoveStack() { + return false; + } + // Some variants use click infos: doClick(coords) { if (typeof coords.x != "number") @@ -391,17 +410,21 @@ export default class ChessRules { // Fen string fully describes the game state if (!o.fen) o.fen = this.genRandInitFen(o.seed); - const fenParsed = this.parseFen(o.fen); - this.board = this.getBoard(fenParsed.position); - this.turn = fenParsed.turn; - this.movesCount = parseInt(fenParsed.movesCount, 10); - this.setOtherVariables(fenParsed); + this.re_initFromFen(o.fen); // Graphical (can use variables defined above) this.containerId = o.element; this.graphicalInit(); } + re_initFromFen(fen, oldBoard) { + const fenParsed = this.parseFen(fen); + this.board = oldBoard || this.getBoard(fenParsed.position); + this.turn = fenParsed.turn; + this.movesCount = parseInt(fenParsed.movesCount, 10); + this.setOtherVariables(fenParsed); + } + // Turn position fen into double array ["wb","wp","bk",...] getBoard(position) { const rows = position.split("/"); @@ -433,7 +456,6 @@ export default class ChessRules { this.initReserves(fenParsed.reserve); if (this.options["crazyhouse"]) this.initIspawn(fenParsed.ispawn); - this.subTurn = 1; //may be unused if (this.options["teleport"]) { this.subTurnTeleport = 1; this.captured = null; @@ -443,6 +465,9 @@ export default class ChessRules { this.enlightened = ArrayFun.init(this.size.x, this.size.y, false); this.updateEnlightened(); } + this.subTurn = 1; //may be unused + if (!this.moveStack) //avoid resetting (unwanted) + this.moveStack = []; } updateEnlightened() { @@ -2128,35 +2153,38 @@ export default class ChessRules { this.subTurnTeleport = 1; this.captured = null; } - if (this.options["balance"]) { - if (![1, 3].includes(this.movesCount)) - this.turn = oppCol; - } - else { + if ( + ( + this.options["doublemove"] && + this.movesCount >= 1 && + this.subTurn == 1 + ) || + (this.options["progressive"] && this.subTurn <= this.movesCount) + ) { + const oppKingPos = this.searchKingPos(oppCol); if ( + oppKingPos[0] >= 0 && ( - this.options["doublemove"] && - this.movesCount >= 1 && - this.subTurn == 1 - ) || - (this.options["progressive"] && this.subTurn <= this.movesCount) + this.options["taking"] || + !this.underCheck(oppKingPos, color) + ) ) { - const oppKingPos = this.searchKingPos(oppCol); - if ( - oppKingPos[0] >= 0 && - ( - this.options["taking"] || - !this.underCheck(oppKingPos, color) - ) - ) { - this.subTurn++; - return; - } + this.subTurn++; + return; } + } + if (this.isLastMove(move)) { this.turn = oppCol; + this.movesCount++; + this.subTurn = 1; } - this.movesCount++; - this.subTurn = 1; + } + + isLastMove(move) { + return ( + (this.options["balance"] && ![1, 3].includes(this.movesCount)) || + !move.next + ); } // "Stop at the first move found" @@ -2233,11 +2261,50 @@ export default class ChessRules { } playPlusVisual(move, r) { + if (this.hasMoveStack) + this.buildMoveStack(move); + else { + this.play(move); + this.playVisual(move, r); + this.afterPlay(move, this.turn, {send: true, res: true}); //user method + } + } + + // TODO: send stack receive stack, or allow incremental? (good/bad points) + buildMoveStack(move) { + this.moveStack.push(move); + this.computeNextMove(move); this.play(move); - this.playVisual(move, r); - this.afterPlay(move); //user method + const newTurn = this.turn; + if (this.moveStack.length == 1) { + this.playVisual(move); + this.gameState = { + fen: this.getFen(), + board: JSON.parse(JSON.stringify(this.board)) //easier + }; + } + if (move.next) + this.buildMoveStack(move.next); + else { + // Send, animate + play until here + if (this.moveStack.length == 1) { + this.afterPlay(this.moveStack, newTurn, {send: true, res: true}); + this.moveStack = [] + } + else { + this.afterPlay(this.moveStack, newTurn, {send: true, res: false}); + this.re_initFromFen(this.gameState.fen, this.gameState.board); + this.playReceivedMove(this.moveStack.slice(1), () => { + this.afterPlay(this.moveStack, newTurn, {send: false, res: true}); + this.moveStack = [] + }); + } + } } + // Implemented in variants using (automatic) moveStack + computeNextMove(move) {} + getMaxDistance(r) { // Works for all rectangular boards: return Math.sqrt(r.width ** 2 + r.height ** 2); @@ -2247,41 +2314,37 @@ export default class ChessRules { return (typeof x == "string" ? this.r_pieces : this.g_pieces)[x][y]; } - animate(move, callback) { - if (this.noAnimate || move.noAnimate) { - callback(); - return; - } - let initPiece = this.getDomPiece(move.start.x, move.start.y); - // NOTE: cloning generally not required, but light enough, and simpler + animateMoving(start, end, drag, segments, cb) { + let initPiece = this.getDomPiece(start.x, start.y); + // NOTE: cloning often not required, but light enough, and simpler let movingPiece = initPiece.cloneNode(); initPiece.style.opacity = "0"; let container = document.getElementById(this.containerId) const r = container.querySelector(".chessboard").getBoundingClientRect(); - if (typeof move.start.x == "string") { + if (typeof start.x == "string") { // Need to bound width/height (was 100% for reserve pieces) const pieceWidth = this.getPieceWidth(r.width); movingPiece.style.width = pieceWidth + "px"; movingPiece.style.height = pieceWidth + "px"; } const maxDist = this.getMaxDistance(r); - const apparentColor = this.getColor(move.start.x, move.start.y); - const pieces = this.pieces(apparentColor, move.start.x, move.start.y); - if (move.drag) { - const startCode = this.getPiece(move.start.x, move.start.y); + const apparentColor = this.getColor(start.x, start.y); + const pieces = this.pieces(apparentColor, start.x, start.y); + if (drag) { + const startCode = this.getPiece(start.x, start.y); C.RemoveClass_es(movingPiece, pieces[startCode]["class"]); - C.AddClass_es(movingPiece, pieces[move.drag.p]["class"]); - if (apparentColor != move.drag.c) { + C.AddClass_es(movingPiece, pieces[drag.p]["class"]); + if (apparentColor != drag.c) { movingPiece.classList.remove(C.GetColorClass(apparentColor)); - movingPiece.classList.add(C.GetColorClass(move.drag.c)); + movingPiece.classList.add(C.GetColorClass(drag.c)); } } container.appendChild(movingPiece); const animateSegment = (index, cb) => { // NOTE: move.drag could be generalized per-segment (usage?) - const [i1, j1] = move.segments[index][0]; - const [i2, j2] = move.segments[index][1]; + const [i1, j1] = segments[index][0]; + const [i2, j2] = segments[index][1]; const dep = this.getPixelPosition(i1, j1, r); const arr = this.getPixelPosition(i2, j2, r); movingPiece.style.transitionDuration = "0s"; @@ -2297,24 +2360,63 @@ export default class ChessRules { setTimeout(cb, duration * 1000); }, 50); }; - if (!move.segments) { - move.segments = [ - [[move.start.x, move.start.y], [move.end.x, move.end.y]] - ]; - } let index = 0; const animateSegmentCallback = () => { - if (index < move.segments.length) + if (index < segments.length) animateSegment(index++, animateSegmentCallback); else { movingPiece.remove(); initPiece.style.opacity = "1"; - callback(); + cb(); } }; animateSegmentCallback(); } + // Input array of objects with at least fields x,y (e.g. PiPo) + animateFading(arr, cb) { + const animLength = 350; //TODO: 350ms? More? Less? + arr.forEach(v => { + let fadingPiece = this.getDomPiece(v.x, v.y); + fadingPiece.style.transitionDuration = (animLength / 1000) + "s"; + fadingPiece.style.opacity = "0"; + }); + setTimeout(cb, animLength); + } + + animate(move, callback) { + if (this.noAnimate || move.noAnimate) { + callback(); + return; + } + let segments = move.segments; + if (!segments) + segments = [ [[move.start.x, move.start.y], [move.end.x, move.end.y]] ]; + let targetObj = new TargetObj(callback); + if (move.start.x != move.end.x || move.start.y != move.end.y) { + targetObj.target++; + this.animateMoving(move.start, move.end, move.drag, segments, + () => targetObj.increment()); + } + if (move.vanish.length > move.appear.length) { + const arr = move.vanish.slice(move.appear.length) + .filter(v => v.x != move.end.x || v.y != move.end.y); + if (arr.length > 0) { + targetObj.target++; + this.animateFading(arr, () => targetObj.increment()); + } + } + targetObj.target += + this.customAnimate(move, segments, () => targetObj.increment()); + if (targetObj.target == 0) + callback(); + } + + // Potential other animations (e.g. for Suction variant) + customAnimate(move, segments, cb) { + return 0; //nb of targets + } + playReceivedMove(moves, callback) { const launchAnimation = () => { const r = container.querySelector(".chessboard").getBoundingClientRect(); diff --git a/variants.js b/variants.js index 5d04f45..18d5891 100644 --- a/variants.js +++ b/variants.js @@ -3,7 +3,7 @@ const variants = [ {name: 'Alapo', desc: 'Geometric Chess'}, {name: 'Alice', desc: 'Both sides of the mirror'}, {name: 'Align4', desc: 'Align four pawns'}, -// {name: 'Allmate', desc: 'Mate any piece'}, + {name: 'Allmate', desc: 'Mate any piece'}, {name: 'Ambiguous', desc: "Play opponent's pieces"}, // {name: 'Antiking1', desc: 'Keep antiking in check', disp: 'Anti-King'}, // {name: 'Antimatter', desc: 'Dangerous collisions'}, diff --git a/variants/Allmate/class.js b/variants/Allmate/class.js new file mode 100644 index 0000000..8c5a92c --- /dev/null +++ b/variants/Allmate/class.js @@ -0,0 +1,97 @@ +import ChessRules from "/base_rules.js"; +import PiPo from "/utils/PiPo.js"; +import Move from "/utils/Move.js"; + +export default class AllmateRules extends ChessRules { + + static get Options() { + return { + select: C.Options.select, + styles: [ + "cylinder", + "madrasi", + "zen" + ] + }; + } + + get hasEnpassant() { + return false; + } + get hasMoveStack() { + return true; + } + + setOtherVariables(fenParsed) { + super.setOtherVariables(fenParsed); + this.curMove = null; + } + + getPotentialMovesFrom(sq) { + // Remove direct captures: + return super.getPotentialMovesFrom(sq) + .filter(m => m.vanish.length == m.appear.length); + } + + // Called "recursively" before a move is played, until no effect + computeNextMove(move) { + if (move.appear.length > 0) + this.curMove = move; + const color = this.turn; + const oppCol = C.GetOppCol(this.turn); + let mv = new Move({ + start: this.curMove.end, + end: this.curMove.end, + appear: [], + vanish: [] + }); + this.playOnBoard(move); + for (let i=0; i 0 ? mv : null); + } + + // is piece on square x,y mated by color? + isMated(x, y, color) { + const myColor = C.GetOppCol(color); + if (!this.underCheck([x, y], color)) + return false; + for (let i=0; iTODO

+ +

Win by mate-capturing the enemy king.

+ + + chessvariants page. + + +

Dr. Chris Taylor (1979).

diff --git a/variants/Allmate/style.css b/variants/Allmate/style.css new file mode 100644 index 0000000..a3550bc --- /dev/null +++ b/variants/Allmate/style.css @@ -0,0 +1 @@ +@import url("/base_pieces.css"); diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js index 24bbd3e..abd695b 100644 --- a/variants/Chakart/class.js +++ b/variants/Chakart/class.js @@ -41,6 +41,9 @@ export default class ChakartRules extends ChessRules { get hasReserveFen() { return false; } + get hasMoveStack() { + return true; + } static get IMMOBILIZE_CODE() { return { @@ -178,15 +181,17 @@ export default class ChakartRules extends ChessRules { } setOtherVariables(fenParsed) { - this.setFlags(fenParsed.flags); - this.reserve = {}; //to be filled later + super.setOtherVariables(fenParsed); this.egg = null; - this.moveStack = []; // Change seed (after FEN generation!!) // so that further calls differ between players: Random.setSeed(Math.floor(19840 * Math.random())); } + initReserves() { + this.reserve = {}; //to be filled later + } + // For Toadette bonus getDropMovesFrom([c, p]) { if (typeof c != "string" || this.reserve[c][p] == 0) @@ -415,57 +420,11 @@ export default class ChakartRules extends ChessRules { super.showChoices(moves); return false; } - if (!move.nextComputed) { - // Set potential random effects, so that play() is deterministic - // from opponent viewpoint: - const endPiece = this.getPiece(move.end.x, move.end.y); - switch (endPiece) { - case V.EGG: - move.egg = Random.sample(V.EGG_SURPRISE); - move.next = this.getEggEffect(move); - break; - case V.MUSHROOM: - move.next = this.getMushroomEffect(move); - break; - case V.BANANA: - case V.BOMB: - move.next = this.getBombBananaEffect(move, endPiece); - break; - } - if (!move.next && move.appear.length > 0 && !move.kingboo) { - const movingPiece = move.appear[0].p; - if (['b', 'r'].includes(movingPiece)) { - // Drop a banana or bomb: - const bs = - this.getRandomSquare([move.end.x, move.end.y], - movingPiece == 'r' - ? [[1, 1], [1, -1], [-1, 1], [-1, -1]] - : [[1, 0], [-1, 0], [0, 1], [0, -1]], - "freeSquare"); - if (bs) { - move.appear.push( - new PiPo({ - x: bs[0], - y: bs[1], - c: 'a', - p: movingPiece == 'r' ? 'd' : 'w' - }) - ); - if (this.board[bs[0]][bs[1]] != "") { - move.vanish.push( - new PiPo({ - x: bs[0], - y: bs[1], - c: this.getColor(bs[0], bs[1]), - p: this.getPiece(bs[0], bs[1]) - }) - ); - } - } - } - } - move.nextComputed = true; - } + this.postPlay(move, color, oppCol); + return true; + } + + postPlay(move, color, oppCol) { this.egg = move.egg; if (move.egg == "toadette") { this.reserve = { w: {}, b: {} }; @@ -526,15 +485,73 @@ export default class ChakartRules extends ChessRules { } } } - if (!move.next && !["daisy", "toadette", "kingboo"].includes(move.egg)) { - this.turn = oppCol; - this.movesCount++; - } + this.playOnBoard(move); + super.postPlay(move); + } + + playVisual(move, r) { + super.playVisual(move, r); if (move.egg) this.displayBonus(move); - this.playOnBoard(move); - this.nextMove = move.next; - return true; + } + + computeNextMove(move) { + // Set potential random effects, so that play() is deterministic + // from opponent viewpoint: + const endPiece = this.getPiece(move.end.x, move.end.y); + switch (endPiece) { + case V.EGG: + move.egg = Random.sample(V.EGG_SURPRISE); + move.next = this.getEggEffect(move); + break; + case V.MUSHROOM: + move.next = this.getMushroomEffect(move); + break; + case V.BANANA: + case V.BOMB: + move.next = this.getBombBananaEffect(move, endPiece); + break; + } + // NOTE: Chakart has also some side-effects: + if ( + !move.next && move.appear.length > 0 && + !move.kingboo && !move.luigiEffect + ) { + const movingPiece = move.appear[0].p; + if (['b', 'r'].includes(movingPiece)) { + // Drop a banana or bomb: + const bs = + this.getRandomSquare([move.end.x, move.end.y], + movingPiece == 'r' + ? [[1, 1], [1, -1], [-1, 1], [-1, -1]] + : [[1, 0], [-1, 0], [0, 1], [0, -1]], + "freeSquare"); + if (bs) { + move.appear.push( + new PiPo({ + x: bs[0], + y: bs[1], + c: 'a', + p: movingPiece == 'r' ? 'd' : 'w' + }) + ); + if (this.board[bs[0]][bs[1]] != "") { + move.vanish.push( + new PiPo({ + x: bs[0], + y: bs[1], + c: this.getColor(bs[0], bs[1]), + p: this.getPiece(bs[0], bs[1]) + }) + ); + } + } + } + } + } + + isLastMove(move) { + return !move.next && !["daisy", "toadette", "kingboo"].includes(move.egg); } // Helper to set and apply banana/bomb effect @@ -584,6 +601,7 @@ export default class ChakartRules extends ChessRules { new PiPo({x: coords[0], y: coords[1], c: oldColor, p: piece}) ] }); + em.luigiEffect = true; //avoid dropping bomb/banana by mistake } break; case "bowser": @@ -703,23 +721,13 @@ export default class ChakartRules extends ChessRules { return moves; } - playPlusVisual(move, r) { - const nextLines = () => { - if (!this.play(move)) - return; - this.moveStack.push(move); - this.playVisual(move, r); - if (this.nextMove) - this.playPlusVisual(this.nextMove, r); - else { - this.afterPlay(this.moveStack); - this.moveStack = []; - } - }; - if (this.moveStack.length == 0) - nextLines(); - else - this.animate(move, nextLines); + // Kingboo bonus can be animated better: + customAnimate(move, segments, cb) { + if (!move.kingboo) + return 0; + super.animateMoving(move.end, move.start, null, + segments.reverse().map(s => s.reverse()), cb); + return 1; } }; diff --git a/variants/Suction/class.js b/variants/Suction/class.js index e2e36c8..dbaefa0 100644 --- a/variants/Suction/class.js +++ b/variants/Suction/class.js @@ -110,4 +110,13 @@ export default class SuctionRules extends ChessRules { return "*"; } + // Better animation for swaps + customAnimate(move, segments, cb) { + if (move.vanish.length < 2) + return 0; + super.animateMoving(move.end, move.start, null, + segments.reverse().map(s => s.reverse()), cb); + return 1; + } + }; -- 2.44.0