1 import { ChessRules
, PiPo
} from "@/base_rules";
3 export class KoopaRules
extends ChessRules
{
4 static get HasEnpassant() {
9 return ['s', 'u', 'o', 'c', 't', 'l'];
13 return ChessRules
.PIECES
.concat(V
.STUNNED
);
16 static ParseFen(fen
) {
17 let res
= ChessRules
.ParseFen(fen
);
18 const fenParts
= fen
.split(" ");
19 res
.stunned
= fenParts
[4];
23 static IsGoodFen(fen
) {
24 if (!ChessRules
.IsGoodFen(fen
)) return false;
25 const fenParsed
= V
.ParseFen(fen
);
30 fenParsed
.stunned
!= "-" &&
31 !fenParsed
.stunned
.match(/^([a-h][1-8][1-4],?)*$/)
40 return (V
.STUNNED
.includes(b
[1]) ? "Koopa/" : "") + b
;
44 return super.getFen() + " " + this.getStunnedFen();
48 return super.getFenForRepeat() + "_" + this.getStunnedFen();
52 const squares
= Object
.keys(this.stunned
);
53 if (squares
.length
== 0) return "-";
54 return squares
.map(square
=> square
+ this.stunned
[square
]).join(",");
57 // Base GenRandInitFen() is fine because en-passant indicator will
58 // stand for stunned indicator.
61 this.INIT_COL_KING
= { w: -1, b: -1 };
62 // Squares of white and black king:
63 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
64 const fenRows
= V
.ParseFen(fen
).position
.split("/");
65 const startRow
= { 'w': V
.size
.x
- 1, 'b': 0 };
66 for (let i
= 0; i
< fenRows
.length
; i
++) {
67 let k
= 0; //column index on board
68 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
69 switch (fenRows
[i
].charAt(j
)) {
72 this.kingPos
["b"] = [i
, k
];
73 this.INIT_COL_KING
["b"] = k
;
77 this.kingPos
["w"] = [i
, k
];
78 this.INIT_COL_KING
["w"] = k
;
81 const num
= parseInt(fenRows
[i
].charAt(j
), 10);
82 if (!isNaN(num
)) k
+= num
- 1;
90 setOtherVariables(fen
) {
91 super.setOtherVariables(fen
);
92 let stunnedArray
= [];
93 const stunnedFen
= V
.ParseFen(fen
).stunned
;
94 if (stunnedFen
!= "-") {
100 square: s
.substr(0, 2),
101 state: parseInt(s
[2], 10)
106 stunnedArray
.forEach(s
=> {
107 this.stunned
[s
.square
] = s
.state
;
111 getNormalizedStep(step
) {
112 const [deltaX
, deltaY
] = [Math
.abs(step
[0]), Math
.abs(step
[1])];
113 if (deltaX
== 0 || deltaY
== 0 || deltaX
== deltaY
)
114 return [step
[0] / deltaX
|| 0, step
[1] / deltaY
|| 0];
116 const divisor
= Math
.min(deltaX
, deltaY
)
117 return [step
[0] / divisor
, step
[1] / divisor
];
120 getPotentialMovesFrom([x
, y
]) {
121 let moves
= super.getPotentialMovesFrom([x
, y
]);
122 // Complete moves: stuns & kicks
123 let promoteAfterStun
= [];
124 const color
= this.turn
;
126 if (m
.vanish
.length
== 2 && m
.appear
.length
== 1) {
128 this.getNormalizedStep([m
.end
.x
- m
.start
.x
, m
.end
.y
- m
.start
.y
]);
129 // "Capture" something: is target stunned?
130 if (V
.STUNNED
.includes(m
.vanish
[1].p
)) {
131 // Kick it: continue movement in the same direction,
132 // destroying all on its path.
133 let [i
, j
] = [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
134 while (V
.OnBoard(i
, j
)) {
135 if (this.board
[i
][j
] != V
.EMPTY
) {
140 c: this.getColor(i
, j
),
141 p: this.getPiece(i
, j
)
150 // The piece is now stunned
151 m
.appear
.push(JSON
.parse(JSON
.stringify(m
.vanish
[1])));
152 const pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== m
.appear
[1].p
);
153 m
.appear
[1].p
= V
.STUNNED
[pIdx
];
154 // And the capturer continue in the same direction until an empty
155 // square or the edge of the board, maybe stunning other pieces.
156 let [i
, j
] = [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
157 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) {
158 const colIJ
= this.getColor(i
, j
);
159 const pieceIJ
= this.getPiece(i
, j
);
160 let pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== pieceIJ
);
162 // The piece isn't already stunned
183 if (V
.OnBoard(i
, j
)) {
186 // Is it a pawn on last rank?
187 if ((color
== 'w' && i
== 0) || (color
== 'b' && i
== 7)) {
188 m
.appear
[0].p
= V
.ROOK
;
189 for (let ppiece
of [V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]) {
190 let mp
= JSON
.parse(JSON
.stringify(m
));
191 mp
.appear
[0].p
= ppiece
;
192 promoteAfterStun
.push(mp
);
202 return moves
.concat(promoteAfterStun
);
206 // Forbid kicking own king out
207 const color
= this.turn
;
208 return moves
.filter(m
=> {
209 const kingAppear
= m
.appear
.some(a
=> a
.c
== color
&& a
.p
== V
.KING
);
210 return m
.vanish
.every(v
=> {
213 !["k", "l"].includes(v
.p
) ||
214 (v
.p
== "k" && kingAppear
)
225 if (this.kingPos
['w'][0] < 0) return "0-1";
226 if (this.kingPos
['b'][0] < 0) return "1-0";
227 if (!this.atLeastOneMove()) return "1/2";
232 // Base method is fine because a stunned king (which won't be detected)
233 // can still castle after going back to normal.
234 super.postPlay(move);
235 const kIdx
= move.vanish
.findIndex(v
=> v
.p
== "l");
237 // A stunned king vanish (game over)
238 this.kingPos
[move.vanish
[kIdx
].c
] = [-1, -1];
239 move.stunned
= JSON
.stringify(this.stunned
);
240 // Array of stunned stage 1 pieces (just back to normal then)
241 Object
.keys(this.stunned
).forEach(square
=> {
242 // All (formerly) stunned pieces progress by 1 level, if still on board
243 const coords
= V
.SquareToCoords(square
);
244 const [x
, y
] = [coords
.x
, coords
.y
];
245 if (V
.STUNNED
.includes(this.board
[x
][y
][1])) {
246 // Stunned piece still on board
247 this.stunned
[square
]--;
248 if (this.stunned
[square
] == 0) {
249 delete this.stunned
[square
];
250 const color
= this.getColor(x
, y
);
251 const piece
= this.getPiece(x
, y
);
252 const pIdx
= V
.STUNNED
.findIndex(p
=> p
== piece
);
253 this.board
[x
][y
] = color
+ ChessRules
.PIECES
[pIdx
];
256 else delete this.stunned
[square
];
258 // Any new stunned pieces?
259 move.appear
.forEach(a
=> {
260 if (V
.STUNNED
.includes(a
.p
))
261 // Set to maximum stun level:
262 this.stunned
[V
.CoordsToSquare({ x: a
.x
, y: a
.y
})] = 4;
267 super.postUndo(move);
268 const kIdx
= move.vanish
.findIndex(v
=> v
.p
== "l");
270 // A stunned king vanished
271 this.kingPos
[move.vanish
[kIdx
].c
] =
272 [move.vanish
[kIdx
].x
, move.vanish
[kIdx
].y
];
274 this.stunned
= JSON
.parse(move.stunned
);
275 for (let i
=0; i
<8; i
++) {
276 for (let j
=0; j
<8; j
++) {
277 const square
= V
.CoordsToSquare({ x: i
, y: j
});
278 const pieceIJ
= this.getPiece(i
, j
);
279 if (!this.stunned
[square
]) {
280 const pIdx
= V
.STUNNED
.findIndex(p
=> p
== pieceIJ
);
282 this.board
[i
][j
] = this.getColor(i
, j
) + ChessRules
.PIECES
[pIdx
];
285 const pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== pieceIJ
);
287 this.board
[i
][j
] = this.getColor(i
, j
) + V
.STUNNED
[pIdx
];
293 static get VALUES() {
294 return Object
.assign(
307 static get SEARCH_DEPTH() {
313 move.appear
.length
== 2 &&
314 move.vanish
.length
== 2 &&
315 move.appear
.concat(move.vanish
).every(
316 av
=> ChessRules
.PIECES
.includes(av
.p
)) &&
317 move.appear
[0].p
== V
.KING
319 if (move.end
.y
< move.start
.y
) return "0-0-0";
322 const finalSquare
= V
.CoordsToSquare(move.end
);
323 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
324 const captureMark
= move.vanish
.length
>= 2 ? "x" : "";
326 if (piece
== 'p' && captureMark
.length
== 1)
327 pawnMark
= V
.CoordToColumn(move.start
.y
); //start column
328 // Piece or pawn movement
330 (piece
== V
.PAWN
? pawnMark : piece
.toUpperCase()) +
331 captureMark
+ finalSquare
;
334 move.appear
[0].c
== move.vanish
[0].c
&&
335 move.appear
[0].p
!= 'p'
338 notation
+= "=" + move.appear
[0].p
.toUpperCase();