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
]).filter(m
=> {
123 m
.vanish
[0].p
!= V
.PAWN
||
124 m
.appear
[0].p
== V
.PAWN
||
129 // Pawn promotion, "capturing": remove duplicates
130 return m
.appear
[0].p
== V
.QUEEN
;
132 // Complete moves: stuns & kicks
133 let promoteAfterStun
= [];
134 const color
= this.turn
;
136 if (m
.vanish
.length
== 2 && m
.appear
.length
== 1) {
138 this.getNormalizedStep([m
.end
.x
- m
.start
.x
, m
.end
.y
- m
.start
.y
]);
139 // "Capture" something: is target stunned?
140 if (V
.STUNNED
.includes(m
.vanish
[1].p
)) {
141 // Kick it: continue movement in the same direction,
142 // destroying all on its path.
143 let [i
, j
] = [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
144 while (V
.OnBoard(i
, j
)) {
145 if (this.board
[i
][j
] != V
.EMPTY
) {
150 c: this.getColor(i
, j
),
151 p: this.getPiece(i
, j
)
160 // The piece is now stunned
161 m
.appear
.push(JSON
.parse(JSON
.stringify(m
.vanish
[1])));
162 const pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== m
.appear
[1].p
);
163 m
.appear
[1].p
= V
.STUNNED
[pIdx
];
164 // And the capturer continue in the same direction until an empty
165 // square or the edge of the board, maybe stunning other pieces.
166 let [i
, j
] = [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
167 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) {
168 const colIJ
= this.getColor(i
, j
);
169 const pieceIJ
= this.getPiece(i
, j
);
170 let pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== pieceIJ
);
172 // The piece isn't already stunned
193 if (V
.OnBoard(i
, j
)) {
196 // Is it a pawn on last rank?
198 m
.appear
[0].p
== V
.PAWN
&&
199 ((color
== 'w' && i
== 0) || (color
== 'b' && i
== 7))
201 m
.appear
[0].p
= V
.ROOK
;
202 for (let ppiece
of [V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]) {
203 let mp
= JSON
.parse(JSON
.stringify(m
));
204 mp
.appear
[0].p
= ppiece
;
205 promoteAfterStun
.push(mp
);
215 return moves
.concat(promoteAfterStun
);
218 getPotentialKingMoves(sq
) {
220 this.getSlideNJumpMoves(
222 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
224 ).concat(super.getCastleMoves(sq
, true, ['r']))
229 // Forbid kicking own king out
230 const color
= this.turn
;
231 return moves
.filter(m
=> {
233 m
.vanish
.some(v
=> v
.c
== color
&& ['k', 'l'].includes(v
.p
));
236 m
.appear
.some(a
=> a
.c
== color
&& ['k', 'l'].includes(a
.p
));
248 if (this.kingPos
['w'][0] < 0) return "0-1";
249 if (this.kingPos
['b'][0] < 0) return "1-0";
250 if (!this.atLeastOneMove()) return "1/2";
255 // Base method is fine because a stunned king (which won't be detected)
256 // can still castle after going back to normal.
257 super.postPlay(move);
258 const color
= this.turn
;
259 const kp
= this.kingPos
[color
];
261 this.board
[kp
[0], kp
[1]] == V
.EMPTY
||
262 !['k', 'l'].includes(this.getPiece(kp
[0], kp
[1])) ||
263 this.getColor(kp
[0], kp
[1]) != color
265 // King didn't move by itself, and vanished => game over
266 this.kingPos
[color
] = [-1, -1];
268 move.stunned
= JSON
.stringify(this.stunned
);
269 // Array of stunned stage 1 pieces (just back to normal then)
270 Object
.keys(this.stunned
).forEach(square
=> {
271 // All (formerly) stunned pieces progress by 1 level, if still on board
272 const coords
= V
.SquareToCoords(square
);
273 const [x
, y
] = [coords
.x
, coords
.y
];
274 if (V
.STUNNED
.includes(this.board
[x
][y
][1])) {
275 // Stunned piece still on board
276 this.stunned
[square
]--;
277 if (this.stunned
[square
] == 0) {
278 delete this.stunned
[square
];
279 const color
= this.getColor(x
, y
);
280 const piece
= this.getPiece(x
, y
);
281 const pIdx
= V
.STUNNED
.findIndex(p
=> p
== piece
);
282 this.board
[x
][y
] = color
+ ChessRules
.PIECES
[pIdx
];
285 else delete this.stunned
[square
];
287 // Any new stunned pieces?
288 move.appear
.forEach(a
=> {
289 if (V
.STUNNED
.includes(a
.p
))
290 // Set to maximum stun level:
291 this.stunned
[V
.CoordsToSquare({ x: a
.x
, y: a
.y
})] = 4;
296 super.postUndo(move);
297 const oppCol
= V
.GetOppCol(this.turn
);
298 if (this.kingPos
[oppCol
][0] < 0) {
299 // Opponent's king vanished
301 move.vanish
.find((v
,i
) => i
>= 1 && ['k', 'l'].includes(v
.p
));
302 this.kingPos
[oppCol
] = [psq
.x
, psq
.y
];
304 this.stunned
= JSON
.parse(move.stunned
);
305 for (let i
=0; i
<8; i
++) {
306 for (let j
=0; j
<8; j
++) {
307 const square
= V
.CoordsToSquare({ x: i
, y: j
});
308 const pieceIJ
= this.getPiece(i
, j
);
309 if (!this.stunned
[square
]) {
310 const pIdx
= V
.STUNNED
.findIndex(p
=> p
== pieceIJ
);
312 this.board
[i
][j
] = this.getColor(i
, j
) + ChessRules
.PIECES
[pIdx
];
315 const pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== pieceIJ
);
317 this.board
[i
][j
] = this.getColor(i
, j
) + V
.STUNNED
[pIdx
];
323 static get VALUES() {
324 return Object
.assign(
337 static get SEARCH_DEPTH() {
343 move.appear
.length
== 2 &&
344 move.vanish
.length
== 2 &&
345 move.appear
.concat(move.vanish
).every(
346 av
=> ChessRules
.PIECES
.includes(av
.p
)) &&
347 move.appear
[0].p
== V
.KING
349 if (move.end
.y
< move.start
.y
) return "0-0-0";
352 const finalSquare
= V
.CoordsToSquare(move.end
);
353 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
354 const captureMark
= move.vanish
.length
>= 2 ? "x" : "";
356 if (piece
== 'p' && captureMark
.length
== 1)
357 pawnMark
= V
.CoordToColumn(move.start
.y
); //start column
358 // Piece or pawn movement
360 (piece
== V
.PAWN
? pawnMark : piece
.toUpperCase()) +
361 captureMark
+ finalSquare
;
364 move.appear
[0].c
== move.vanish
[0].c
&&
365 move.appear
[0].p
!= 'p'
368 notation
+= "=" + move.appear
[0].p
.toUpperCase();