On the way to 8-pieces implementation main
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 31 Mar 2026 15:39:00 +0000 (17:39 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 31 Mar 2026 15:39:00 +0000 (17:39 +0200)
14 files changed:
base_rules.js
variants.js
variants/Apocalypse/class.js
variants/Avalanche/class.js
variants/Balaklava/class.js
variants/Capablanca/class.js
variants/Chakart/class.js
variants/Cwda/class.js
variants/Discoduel/class.js
variants/Dobutsu/class.js
variants/Dynamo/class.js
variants/Eightpieces/class.js
variants/Giveaway/class.js
variants/Suction/class.js

index 8613c4b..83a3c7b 100644 (file)
@@ -80,7 +80,7 @@ export default class ChessRules {
   }
 
   // Arguments x, y are useful for Eightpieces (maybe others?)
   }
 
   // Arguments x, y are useful for Eightpieces (maybe others?)
-  get pawnPromotions(x, y) {
+  pawnPromotions(x, y) {
     return ['q', 'r', 'n', 'b'];
   }
 
     return ['q', 'r', 'n', 'b'];
   }
 
@@ -639,8 +639,7 @@ export default class ChessRules {
       this[arrName][i][j].style.height = pieceWidth + "px";
       let [ip, jp] = this.getPixelPosition(i, j, r);
       // Translate coordinates to use chessboard as reference:
       this[arrName][i][j].style.height = pieceWidth + "px";
       let [ip, jp] = this.getPixelPosition(i, j, r);
       // Translate coordinates to use chessboard as reference:
-      this[arrName][i][j].style.transform =
-        `translate(${ip - r.x}px,${jp - r.y}px)`;
+      this[arrName][i][j].style.translate = `${ip - r.x}px ${jp - r.y}px`;
       chessboard.appendChild(this[arrName][i][j]);
     };
     const conditionalReset = (arrName) => {
       chessboard.appendChild(this[arrName][i][j]);
     };
     const conditionalReset = (arrName) => {
@@ -809,8 +808,8 @@ export default class ChessRules {
             this.g_pieces[i][j].style.height = pieceWidth + "px";
             const [ip, jp] = this.getPixelPosition(i, j, newR);
             // Translate coordinates to use chessboard as reference:
             this.g_pieces[i][j].style.height = pieceWidth + "px";
             const [ip, jp] = this.getPixelPosition(i, j, newR);
             // Translate coordinates to use chessboard as reference:
-            this.g_pieces[i][j].style.transform =
-              `translate(${ip - newX}px,${jp - newY}px)`;
+            this.g_pieces[i][j].style.translate =
+              `${ip - newX}px ${jp - newY}px`;
           }
         }
       }
           }
         }
       }
@@ -1667,7 +1666,7 @@ export default class ChessRules {
         finalPieces = [this.getPieceType(x2, y2)];
       }
       else
         finalPieces = [this.getPieceType(x2, y2)];
       }
       else
-        finalPieces = this.pawnPromotions;
+        finalPieces = this.pawnPromotions(x2, y2);
       m.appear[0].p = finalPieces[0];
       if (initPiece == "!") //cannibal king-pawn
         m.appear[0].p = C.CannibalKingCode[finalPieces[0]];
       m.appear[0].p = finalPieces[0];
       if (initPiece == "!") //cannibal king-pawn
         m.appear[0].p = C.CannibalKingCode[finalPieces[0]];
@@ -1692,10 +1691,12 @@ export default class ChessRules {
         m.appear[0].x == m.start.x &&
         m.appear[0].y == m.start.y
       ) {
         m.appear[0].x == m.start.x &&
         m.appear[0].y == m.start.y
       ) {
-        m.appear[0].p = this.pawnPromotions[0];
-        for (let i=1; i<this.pawnPromotions.length; i++) {
+        const promotions = this.pawnPromotions(m.start.x, m.start.y);
+        m.appear[0].p = promotions[0];
+        for (let i = 1; i < promotions.length; i++)
+        {
           let newMv = JSON.parse(JSON.stringify(m));
           let newMv = JSON.parse(JSON.stringify(m));
-          newMv.appear[0].p = this.pawnPromotions[i];
+          newMv.appear[0].p = promotions[i];
           newMoves.push(newMv);
         }
       }
           newMoves.push(newMv);
         }
       }
@@ -1876,6 +1877,8 @@ export default class ChessRules {
           if (this.canStepOver(x, y, apparentPiece))
             continue;
           const stepSpec = this.getStepSpec(colIJ, i, j);
           if (this.canStepOver(x, y, apparentPiece))
             continue;
           const stepSpec = this.getStepSpec(colIJ, i, j);
+          if (stepSpec.indirectAttack) //e.g. 8-pieces (only?)
+            continue;
           const attacks = stepSpec.attack.concat(stepSpec.both);
           for (let a of attacks) {
             for (let s of a.steps) {
           const attacks = stepSpec.attack.concat(stepSpec.both);
           for (let a of attacks) {
             for (let s of a.steps) {
@@ -2476,8 +2479,7 @@ export default class ChessRules {
       this.g_pieces[a.x][a.y].style.height = pieceWidth + "px";
       const [ip, jp] = this.getPixelPosition(a.x, a.y, r);
       // Translate coordinates to use chessboard as reference:
       this.g_pieces[a.x][a.y].style.height = pieceWidth + "px";
       const [ip, jp] = this.getPixelPosition(a.x, a.y, r);
       // Translate coordinates to use chessboard as reference:
-      this.g_pieces[a.x][a.y].style.transform =
-        `translate(${ip - r.x}px,${jp - r.y}px)`;
+      this.g_pieces[a.x][a.y].style.translate = `${ip - r.x}px ${jp - r.y}px`;
       if (this.enlightened && !this.enlightened[a.x][a.y])
         this.g_pieces[a.x][a.y].classList.add("hidden");
       chessboard.appendChild(this.g_pieces[a.x][a.y]);
       if (this.enlightened && !this.enlightened[a.x][a.y])
         this.g_pieces[a.x][a.y].classList.add("hidden");
       chessboard.appendChild(this.g_pieces[a.x][a.y]);
@@ -2564,7 +2566,7 @@ export default class ChessRules {
       const dep = this.getPixelPosition(i1, j1, r);
       const arr = this.getPixelPosition(i2, j2, r);
       movingPiece.style.transitionDuration = "0s";
       const dep = this.getPixelPosition(i1, j1, r);
       const arr = this.getPixelPosition(i2, j2, r);
       movingPiece.style.transitionDuration = "0s";
-      movingPiece.style.transform = `translate(${dep[0]}px, ${dep[1]}px)`;
+      movingPiece.style.translate = `${dep[0]}px ${dep[1]}px`;
       const distance =
         Math.sqrt((arr[0] - dep[0]) ** 2 + (arr[1] - dep[1]) ** 2);
       const duration = 0.2 + (distance / maxDist) * 0.3;
       const distance =
         Math.sqrt((arr[0] - dep[0]) ** 2 + (arr[1] - dep[1]) ** 2);
       const duration = 0.2 + (distance / maxDist) * 0.3;
@@ -2572,7 +2574,7 @@ export default class ChessRules {
       setTimeout(() => {
         movingPiece.style.transitionDuration = duration + "s";
         // movingPiece is child of container: no need to adjust coordinates
       setTimeout(() => {
         movingPiece.style.transitionDuration = duration + "s";
         // movingPiece is child of container: no need to adjust coordinates
-        movingPiece.style.transform = `translate(${arr[0]}px, ${arr[1]}px)`;
+        movingPiece.style.translate = `${arr[0]}px ${arr[1]}px`;
         setTimeout(cb, duration * 1000);
       }, 50);
     };
         setTimeout(cb, duration * 1000);
       }, 50);
     };
index 10cf42a..6519dcc 100644 (file)
@@ -48,7 +48,7 @@ const variants = [
   {name: 'Doublearmy', desc: '64 pieces on the board', disp: 'Double Army'},
   {name: 'Doublemove', desc: 'Double moves'},
   {name: 'Dynamo', desc: 'Push and pull'},
   {name: 'Doublearmy', desc: '64 pieces on the board', disp: 'Double Army'},
   {name: 'Doublemove', desc: 'Double moves'},
   {name: 'Dynamo', desc: 'Push and pull'},
-//  {name: 'Eightpieces', desc: 'Each piece is unique', disp: '8 Pieces'},
+  {name: 'Eightpieces', desc: 'Each piece is unique', disp: '8 Pieces'},
 //  {name: 'Emergo', desc: 'Stacking Checkers variant'},
 //  {name: 'Empire', desc: 'Empire versus Kingdom'},
 //  {name: 'Enpassant', desc: 'Capture en passant', disp: 'En-passant'},
 //  {name: 'Emergo', desc: 'Stacking Checkers variant'},
 //  {name: 'Empire', desc: 'Empire versus Kingdom'},
 //  {name: 'Enpassant', desc: 'Capture en passant', disp: 'En-passant'},
index 3b735b1..681873f 100644 (file)
@@ -17,7 +17,7 @@ export default class ApocalypseRules extends ChessRules {
     return true;
   }
 
     return true;
   }
 
-  get pawnPromotions() {
+  pawnPromotions() {
     return ['n', 'p'];
   }
 
     return ['n', 'p'];
   }
 
index 8a4981f..dffc846 100644 (file)
@@ -58,7 +58,7 @@ export default class AvalancheRules extends ChessRules {
       return null;
     }
     let moves = [];
       return null;
     }
     let moves = [];
-    this.pawnPromotions.forEach(pr => {
+    this.pawnPromotions().forEach(pr => {
       moves.push(
         new Move({
           vanish: [new PiPo({x: coords.x, y: coords.y, c: this.turn, p: 'p'})],
       moves.push(
         new Move({
           vanish: [new PiPo({x: coords.x, y: coords.y, c: this.turn, p: 'p'})],
index 1ee2dd7..06ce365 100644 (file)
@@ -3,7 +3,7 @@ import {FenUtil} from "/utils/setupPieces.js";
 
 export default class BalaklavaRules extends ChessRules {
 
 
 export default class BalaklavaRules extends ChessRules {
 
-  get pawnPromotions() {
+  pawnPromotions() {
     return ['r', 'm', 'b', 'q'];
   }
 
     return ['r', 'm', 'b', 'q'];
   }
 
index 5b1c80e..60416ca 100644 (file)
@@ -3,7 +3,7 @@ import {FenUtil} from "/utils/setupPieces.js";
 
 export default class CapablancaRules extends ChessRules {
 
 
 export default class CapablancaRules extends ChessRules {
 
-  get pawnPromotions() {
+  pawnPromotions() {
     return ['q', 'e', 's', 'r', 'n', 'b'];
   }
 
     return ['q', 'e', 's', 'r', 'n', 'b'];
   }
 
index 3454d8f..006193c 100644 (file)
@@ -25,7 +25,7 @@ export default class ChakartRules extends ChessRules {
     };
   }
 
     };
   }
 
-  get pawnPromotions() {
+  pawnPromotions() {
     return ['q', 'r', 'n', 'b', 'k'];
   }
 
     return ['q', 'r', 'n', 'b', 'k'];
   }
 
@@ -404,7 +404,7 @@ export default class ChakartRules extends ChessRules {
       this.reserve = { w: {}, b: {} };
       // Randomly select a piece in pawnPromotions
       if (!move.toadette)
       this.reserve = { w: {}, b: {} };
       // Randomly select a piece in pawnPromotions
       if (!move.toadette)
-        move.toadette = Random.sample(this.pawnPromotions);
+        move.toadette = Random.sample( this.pawnPromotions() );
       this.reserve[color][move.toadette] = 1;
       this.re_drawReserve([color]);
     }
       this.reserve[color][move.toadette] = 1;
       this.re_drawReserve([color]);
     }
index db79917..32f0322 100644 (file)
@@ -275,7 +275,7 @@ export default class CwdaRules extends ChessRules {
     );
   }
 
     );
   }
 
-  get pawnPromotions() {
+  pawnPromotions() {
     // Can promote in anything from the two current armies
     let promotions = [];
     for (let army of ["army1", "army2"]) {
     // Can promote in anything from the two current armies
     let promotions = [];
     for (let army of ["army1", "army2"]) {
index ec96a43..26f02ea 100644 (file)
@@ -7,7 +7,7 @@ export default class DiscoduelRules extends ChessRules {
     return {}; //nothing would make sense
   }
 
     return {}; //nothing would make sense
   }
 
-  get pawnPromotions() {
+  pawnPromotions() {
     return ['p'];
   }
 
     return ['p'];
   }
 
index d8ceafa..cca2920 100644 (file)
@@ -68,7 +68,7 @@ export default class DobutsuRules extends ChessRules {
     super(o);
   }
 
     super(o);
   }
 
-  get pawnPromotions() {
+  pawnPromotions() {
     return ['h'];
   }
 
     return ['h'];
   }
 
index 5098bbb..1e4592c 100644 (file)
@@ -65,7 +65,7 @@ export default class DynamoRules extends ChessRules {
     while (this.onBoard(i, j) && this.board[i][j] == "") {
       if (i == lastRank && piece == 'p') {
         // Promotion by push or pull (if suicide)
     while (this.onBoard(i, j) && this.board[i][j] == "") {
       if (i == lastRank && piece == 'p') {
         // Promotion by push or pull (if suicide)
-        this.pawnPromotions.forEach(p => {
+        this.pawnPromotions().forEach(p => {
           let move = super.getBasicMove([x, y], [i, j], { c: color, p: p });
           moves.push(move);
         });
           let move = super.getBasicMove([x, y], [i, j], { c: color, p: p });
           moves.push(move);
         });
index fa14438..0627b64 100644 (file)
@@ -1,4 +1,5 @@
 import {FenUtil} from "/utils/setupPieces.js";
 import {FenUtil} from "/utils/setupPieces.js";
+import ChessRules from "/base_rules.js";
 import PiPo from "/utils/PiPo.js";
 import Move from "/utils/Move.js";
 
 import PiPo from "/utils/PiPo.js";
 import Move from "/utils/Move.js";
 
@@ -13,7 +14,7 @@ export default class EightpiecesRules extends ChessRules {
     };
   }
 
     };
   }
 
-  get pawnPromotions(x, y) {
+  pawnPromotions(x, y) {
     const base_pieces = ['q', 'r', 'n', 'b', 'j', 's'];
     let lancers = [];
     if (y > 0)
     const base_pieces = ['q', 'r', 'n', 'b', 'j', 's'];
     let lancers = [];
     if (y > 0)
@@ -27,18 +28,18 @@ export default class EightpiecesRules extends ChessRules {
       if (y < this.size.y)
         lancers.push('f');
     }
       if (y < this.size.y)
         lancers.push('f');
     }
-    else { //x == this.size.x (8)
+    else { //x == this.size.x-1 (7)
       lancers.push('c');
       if (y > 0)
         lancers.push('o');
       if (y < this.size.y)
         lancers.push('d');
     }
       lancers.push('c');
       if (y > 0)
         lancers.push('o');
       if (y < this.size.y)
         lancers.push('d');
     }
-    return ['q', 'r', 'n', 'b', 'j', 's', 'l'];
+    return base_pieces.concat(lancers);
   }
 
   genRandInitBaseFen() {
   }
 
   genRandInitBaseFen() {
-    const s = FenUtil.setupPieces(
+    let s = FenUtil.setupPieces(
       ['j', 'l', 's', 'q', 'k', 'b', 'n', 'r'],
       {
         randomness: this.options["randomness"],
       ['j', 'l', 's', 'q', 'k', 'b', 'n', 'r'],
       {
         randomness: this.options["randomness"],
@@ -48,21 +49,26 @@ export default class EightpiecesRules extends ChessRules {
         flags: ['r', 'j']
       }
     );
         flags: ['r', 'j']
       }
     );
+    const random = (this.options["randomness"] > 0);
+    const fen = s.b.join("").replace('l', random ? 'g' : 'f') +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      s.w.join("").replace('l', random > 0 ? 'c' : 'd').toUpperCase();
     return {
     return {
-      fen: s.b.join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" +
-           s.w.join("").toUpperCase(),
+      fen: fen,
       o: {flags: s.flags}
     };
   }
 
   setOtherVariables(fenParsed) {
     super.setOtherVariables(fenParsed);
       o: {flags: s.flags}
     };
   }
 
   setOtherVariables(fenParsed) {
     super.setOtherVariables(fenParsed);
-    // TODO: state variables (sentry pushes, lancers?)
+    //this.pushFrom = 
+    //this.afterPush = 
   }
 
   }
 
+  // TODO: FEN utils pushFrom et afterPush
+
   pieces(color, x, y) {
   pieces(color, x, y) {
-    const base_pieces = super.pieces(color, x, y);
-    return {
+    return Object.assign({
       'j': {
         "class": "jailer",
         moves: [
       'j': {
         "class": "jailer",
         moves: [
@@ -71,6 +77,7 @@ export default class EightpiecesRules extends ChessRules {
       },
       's': {
         "class": "sentry",
       },
       's': {
         "class": "sentry",
+        indirectAttack: true,
         both: [
           {steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]]}
         ]
         both: [
           {steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]]}
         ]
@@ -123,14 +130,14 @@ export default class EightpiecesRules extends ChessRules {
           {steps: [[-1, -1]]}
         ]
       },
           {steps: [[-1, -1]]}
         ]
       },
-    };
+    }, super.pieces(color, x, y));
   }
 
   isImmobilized([x, y]) {
     const color = this.getColor(x, y);
   }
 
   isImmobilized([x, y]) {
     const color = this.getColor(x, y);
-    const oppCol = C.getOppTurn(color);
+    const oppCol = C.GetOppTurn(color);
     const stepSpec = this.getStepSpec(color, x, y, 'j');
     const stepSpec = this.getStepSpec(color, x, y, 'j');
-    for (let step of stepSpec.both[0].steps) {
+    for (let step of stepSpec.moves[0].steps) {
       let [i, j] = this.increment([x, y], step);
       if (
         this.onBoard(i, j) &&
       let [i, j] = this.increment([x, y], step);
       if (
         this.onBoard(i, j) &&
@@ -143,9 +150,18 @@ export default class EightpiecesRules extends ChessRules {
     return false;
   }
 
     return false;
   }
 
+  getPotentialMovesFrom([x, y], color) {
+    if (!this.pushFrom)
+      return super.getPotentialMovesFrom([x, y], color);
+    if (x != this.pushFrom.x || y != this.pushFrom.y)
+      return [];
+    // After sentry "attack": move enemy as if it was ours
+    return []; //TODO
+  }
+
   getSentryPushes(x, y) {
     // TODO: return all squares piece on x, y can be pushed to
   getSentryPushes(x, y) {
     // TODO: return all squares piece on x, y can be pushed to
-    // Simple
+    return [{x: x+1, y: y-1}];
   }
 
   // Post-process sentry pushes (if any)
   }
 
   // Post-process sentry pushes (if any)
@@ -171,56 +187,18 @@ export default class EightpiecesRules extends ChessRules {
     return finalMoves;
   }
 
     return finalMoves;
   }
 
-//idée exception globle dans base_rules.js d'une pièce marquée comme "indirect attack" ?!
 
 
-  // TODO:::::: under sentry attack?!
-  // Is piece (or square) at given position attacked by "oppCol(s)" ?
-  underAttack([x, y], oppCols) {
-    super.underAttack([x, y], oppCols)
-    // An empty square is considered as king,
-    // since it's used only in getCastleMoves (TODO?)
-    const king = this.board[x][y] == "" || this.isKing(x, y);
-    return (
-      (
-        (!this.options["zen"] || king) &&
-        this.findCapturesOn(
-          [x, y],
-          {
-            byCol: oppCols,
-            one: true
-          }
-        )
-      )
-      ||
-      (
-        (!!this.options["zen"] && !king) &&
-        this.findDestSquares(
-          [x, y],
-          {
-            attackOnly: true,
-            one: true
-          },
-          ([i1, j1], [i2, j2]) => oppCols.includes(this.getColor(i2, j2))
-        )
-      )
-    );
-  }
-
-
-  // TODO::: sentry subturn ???
-  // 'color' arg because some variants (e.g. Refusal) check opponent moves
+  // Lazy sentry attacks check: after push move
   filterValid(moves, color) {
   filterValid(moves, color) {
-    color = color || this.turn;
-    const oppCols = this.getOppCols(color);
-    let kingPos = this.searchKingPos(color);
-    return moves.filter(m => {
-      this.playOnBoard(m);
-      const res = this.trackKingWrap(m, kingPos, (kp) => {
-        return !this.underCheck(kp, oppCols);
-      });
-      this.undoOnBoard(m);
-      return res;
+    let sentryAttack = [];
+    moves = moves.filter(m => {
+      if (m.appear.length == 0) {
+        sentryAttack.push(m);
+        return false;
+      }
+      return true;
     });
     });
+    return super.filterValid(moves, color).concat(sentryAttack);
   }
 
 };
   }
 
 };
index 45f6a34..ed05d23 100644 (file)
@@ -30,7 +30,7 @@ export default class GiveawayRules extends ChessRules {
     return this.options["mode"] == "losers";
   }
 
     return this.options["mode"] == "losers";
   }
 
-  get pawnPromotions() {
+  pawnPromotions() {
     let res = ['q', 'r', 'n', 'b'];
     if (this.options["mode"] == "suicide")
       res.push('k');
     let res = ['q', 'r', 'n', 'b'];
     if (this.options["mode"] == "suicide")
       res.push('k');
index ff307d4..08254fd 100644 (file)
@@ -21,7 +21,7 @@ export default class SuctionRules extends ChessRules {
     };
   }
 
     };
   }
 
-  get pawnPromotions() {
+  pawnPromotions() {
     return ['p']; //no promotions
   }
 
     return ['p']; //no promotions
   }