1 import { ChessRules
, PiPo
} from "@/base_rules";
3 export class KoopaRules
extends ChessRules
{
5 static get HasEnpassant() {
10 return ['s', 'u', 'o', 'c', 't', 'l'];
14 return ChessRules
.PIECES
.concat(V
.STUNNED
);
17 static ParseFen(fen
) {
18 let res
= ChessRules
.ParseFen(fen
);
19 const fenParts
= fen
.split(" ");
20 res
.stunned
= fenParts
[4];
24 static IsGoodFen(fen
) {
25 if (!ChessRules
.IsGoodFen(fen
)) return false;
26 const fenParsed
= V
.ParseFen(fen
);
31 fenParsed
.stunned
!= "-" &&
32 !fenParsed
.stunned
.match(/^([a-h][1-8][1-4],?)*$/)
41 return (V
.STUNNED
.includes(b
[1]) ? "Koopa/" : "") + b
;
45 return super.getFen() + " " + this.getStunnedFen();
49 return super.getFenForRepeat() + "_" + this.getStunnedFen();
53 const squares
= Object
.keys(this.stunned
);
54 if (squares
.length
== 0) return "-";
55 return squares
.map(square
=> square
+ this.stunned
[square
]).join(",");
58 // Base GenRandInitFen() is fine because en-passant indicator will
59 // stand for stunned indicator.
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
];
76 this.kingPos
["w"] = [i
, k
];
79 const num
= parseInt(fenRows
[i
].charAt(j
), 10);
80 if (!isNaN(num
)) k
+= num
- 1;
88 setOtherVariables(fen
) {
89 super.setOtherVariables(fen
);
90 let stunnedArray
= [];
91 const stunnedFen
= V
.ParseFen(fen
).stunned
;
92 if (stunnedFen
!= "-") {
98 square: s
.substr(0, 2),
99 state: parseInt(s
[2], 10)
104 stunnedArray
.forEach(s
=> {
105 this.stunned
[s
.square
] = s
.state
;
109 getNormalizedStep(step
) {
110 const [deltaX
, deltaY
] = [Math
.abs(step
[0]), Math
.abs(step
[1])];
111 if (deltaX
== 0 || deltaY
== 0 || deltaX
== deltaY
)
112 return [step
[0] / deltaX
|| 0, step
[1] / deltaY
|| 0];
114 const divisor
= Math
.min(deltaX
, deltaY
)
115 return [step
[0] / divisor
, step
[1] / divisor
];
118 getPotentialMovesFrom([x
, y
]) {
119 let moves
= super.getPotentialMovesFrom([x
, y
]).filter(m
=> {
121 m
.vanish
[0].p
!= V
.PAWN
||
122 m
.appear
[0].p
== V
.PAWN
||
127 // Pawn promotion, "capturing": remove duplicates
128 return m
.appear
[0].p
== V
.QUEEN
;
130 // Complete moves: stuns & kicks
131 let promoteAfterStun
= [];
132 const color
= this.turn
;
134 if (m
.vanish
.length
== 2 && m
.appear
.length
== 1) {
136 this.getNormalizedStep([m
.end
.x
- m
.start
.x
, m
.end
.y
- m
.start
.y
]);
137 // "Capture" something: is target stunned?
138 if (V
.STUNNED
.includes(m
.vanish
[1].p
)) {
139 // Kick it: continue movement in the same direction,
140 // destroying all on its path.
141 let [i
, j
] = [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
142 while (V
.OnBoard(i
, j
)) {
143 if (this.board
[i
][j
] != V
.EMPTY
) {
148 c: this.getColor(i
, j
),
149 p: this.getPiece(i
, j
)
158 // The piece is now stunned
159 m
.appear
.push(JSON
.parse(JSON
.stringify(m
.vanish
[1])));
160 const pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== m
.appear
[1].p
);
161 m
.appear
[1].p
= V
.STUNNED
[pIdx
];
162 // And the capturer continue in the same direction until an empty
163 // square or the edge of the board, maybe stunning other pieces.
164 let [i
, j
] = [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
165 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] != V
.EMPTY
) {
166 const colIJ
= this.getColor(i
, j
);
167 const pieceIJ
= this.getPiece(i
, j
);
168 let pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== pieceIJ
);
170 // The piece isn't already stunned
191 if (V
.OnBoard(i
, j
)) {
194 // Is it a pawn on last rank?
196 m
.appear
[0].p
== V
.PAWN
&&
197 ((color
== 'w' && i
== 0) || (color
== 'b' && i
== 7))
199 m
.appear
[0].p
= V
.ROOK
;
200 for (let ppiece
of [V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]) {
201 let mp
= JSON
.parse(JSON
.stringify(m
));
202 mp
.appear
[0].p
= ppiece
;
203 promoteAfterStun
.push(mp
);
213 return moves
.concat(promoteAfterStun
);
216 getPotentialKingMoves(sq
) {
218 this.getSlideNJumpMoves(
219 sq
, V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), 1
220 ).concat(super.getCastleMoves(sq
, null, true, ['r']))
225 // Forbid kicking own king out
226 const color
= this.turn
;
227 return moves
.filter(m
=> {
229 m
.vanish
.some(v
=> v
.c
== color
&& ['k', 'l'].includes(v
.p
));
232 m
.appear
.some(a
=> a
.c
== color
&& ['k', 'l'].includes(a
.p
));
244 if (this.kingPos
['w'][0] < 0) return "0-1";
245 if (this.kingPos
['b'][0] < 0) return "1-0";
246 if (!this.atLeastOneMove()) return "1/2";
251 // Base method is fine because a stunned king (which won't be detected)
252 // can still castle after going back to normal.
253 super.postPlay(move);
254 const color
= this.turn
;
255 const kp
= this.kingPos
[color
];
257 this.board
[kp
[0], kp
[1]] == V
.EMPTY
||
258 !['k', 'l'].includes(this.getPiece(kp
[0], kp
[1])) ||
259 this.getColor(kp
[0], kp
[1]) != color
261 // King didn't move by itself, and vanished => game over
262 this.kingPos
[color
] = [-1, -1];
264 move.stunned
= JSON
.stringify(this.stunned
);
265 // Array of stunned stage 1 pieces (just back to normal then)
266 Object
.keys(this.stunned
).forEach(square
=> {
267 // All (formerly) stunned pieces progress by 1 level, if still on board
268 const coords
= V
.SquareToCoords(square
);
269 const [x
, y
] = [coords
.x
, coords
.y
];
270 if (V
.STUNNED
.includes(this.board
[x
][y
][1])) {
271 // Stunned piece still on board
272 this.stunned
[square
]--;
273 if (this.stunned
[square
] == 0) {
274 delete this.stunned
[square
];
275 const color
= this.getColor(x
, y
);
276 const piece
= this.getPiece(x
, y
);
277 const pIdx
= V
.STUNNED
.findIndex(p
=> p
== piece
);
278 this.board
[x
][y
] = color
+ ChessRules
.PIECES
[pIdx
];
281 else delete this.stunned
[square
];
283 // Any new stunned pieces?
284 move.appear
.forEach(a
=> {
285 if (V
.STUNNED
.includes(a
.p
))
286 // Set to maximum stun level:
287 this.stunned
[V
.CoordsToSquare({ x: a
.x
, y: a
.y
})] = 4;
292 super.postUndo(move);
293 const oppCol
= V
.GetOppCol(this.turn
);
294 if (this.kingPos
[oppCol
][0] < 0) {
295 // Opponent's king vanished
297 move.vanish
.find((v
,i
) => i
>= 1 && ['k', 'l'].includes(v
.p
));
298 this.kingPos
[oppCol
] = [psq
.x
, psq
.y
];
300 this.stunned
= JSON
.parse(move.stunned
);
301 for (let i
=0; i
<8; i
++) {
302 for (let j
=0; j
<8; j
++) {
303 const square
= V
.CoordsToSquare({ x: i
, y: j
});
304 const pieceIJ
= this.getPiece(i
, j
);
305 if (!this.stunned
[square
]) {
306 const pIdx
= V
.STUNNED
.findIndex(p
=> p
== pieceIJ
);
308 this.board
[i
][j
] = this.getColor(i
, j
) + ChessRules
.PIECES
[pIdx
];
311 const pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== pieceIJ
);
313 this.board
[i
][j
] = this.getColor(i
, j
) + V
.STUNNED
[pIdx
];
319 static get VALUES() {
320 return Object
.assign(
333 static get SEARCH_DEPTH() {
339 move.appear
.length
== 2 &&
340 move.vanish
.length
== 2 &&
341 move.appear
.concat(move.vanish
).every(
342 av
=> ChessRules
.PIECES
.includes(av
.p
)) &&
343 move.appear
[0].p
== V
.KING
345 if (move.end
.y
< move.start
.y
) return "0-0-0";
348 const finalSquare
= V
.CoordsToSquare(move.end
);
349 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
350 const captureMark
= move.vanish
.length
>= 2 ? "x" : "";
352 if (piece
== 'p' && captureMark
.length
== 1)
353 pawnMark
= V
.CoordToColumn(move.start
.y
); //start column
354 // Piece or pawn movement
356 (piece
== V
.PAWN
? pawnMark : piece
.toUpperCase()) +
357 captureMark
+ finalSquare
;
360 move.appear
[0].c
== move.vanish
[0].c
&&
361 move.appear
[0].p
!= 'p'
364 notation
+= "=" + move.appear
[0].p
.toUpperCase();