dbce84217e674fd077364880f95e4a63c6d91a70
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?
188 m
.appear
[0].p
== V
.PAWN
&&
189 ((color
== 'w' && i
== 0) || (color
== 'b' && i
== 7))
191 m
.appear
[0].p
= V
.ROOK
;
192 for (let ppiece
of [V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]) {
193 let mp
= JSON
.parse(JSON
.stringify(m
));
194 mp
.appear
[0].p
= ppiece
;
195 promoteAfterStun
.push(mp
);
205 return moves
.concat(promoteAfterStun
);
209 // Forbid kicking own king out
210 const color
= this.turn
;
211 return moves
.filter(m
=> {
212 const kingAppear
= m
.appear
.some(a
=> a
.c
== color
&& a
.p
== V
.KING
);
213 return m
.vanish
.every(v
=> {
216 !["k", "l"].includes(v
.p
) ||
217 (v
.p
== "k" && kingAppear
)
228 if (this.kingPos
['w'][0] < 0) return "0-1";
229 if (this.kingPos
['b'][0] < 0) return "1-0";
230 if (!this.atLeastOneMove()) return "1/2";
235 // Base method is fine because a stunned king (which won't be detected)
236 // can still castle after going back to normal.
237 super.postPlay(move);
238 const color
= this.turn
;
239 const kp
= this.kingPos
[color
];
241 this.board
[kp
[0], kp
[1]] == V
.EMPTY
||
242 !['k', 'l'].includes(this.getPiece(kp
[0], kp
[1])) ||
243 this.getColor(kp
[0], kp
[1]) != color
245 // King didn't move by itself, and vanished => game over
246 this.kingPos
[color
] = [-1, -1];
248 move.stunned
= JSON
.stringify(this.stunned
);
249 // Array of stunned stage 1 pieces (just back to normal then)
250 Object
.keys(this.stunned
).forEach(square
=> {
251 // All (formerly) stunned pieces progress by 1 level, if still on board
252 const coords
= V
.SquareToCoords(square
);
253 const [x
, y
] = [coords
.x
, coords
.y
];
254 if (V
.STUNNED
.includes(this.board
[x
][y
][1])) {
255 // Stunned piece still on board
256 this.stunned
[square
]--;
257 if (this.stunned
[square
] == 0) {
258 delete this.stunned
[square
];
259 const color
= this.getColor(x
, y
);
260 const piece
= this.getPiece(x
, y
);
261 const pIdx
= V
.STUNNED
.findIndex(p
=> p
== piece
);
262 this.board
[x
][y
] = color
+ ChessRules
.PIECES
[pIdx
];
265 else delete this.stunned
[square
];
267 // Any new stunned pieces?
268 move.appear
.forEach(a
=> {
269 if (V
.STUNNED
.includes(a
.p
))
270 // Set to maximum stun level:
271 this.stunned
[V
.CoordsToSquare({ x: a
.x
, y: a
.y
})] = 4;
276 super.postUndo(move);
277 const oppCol
= V
.GetOppCol(this.turn
);
278 if (this.kingPos
[oppCol
][0] < 0) {
279 // Opponent's king vanished
281 move.vanish
.find((v
,i
) => i
>= 1 && ['k', 'l'].includes(v
.p
));
282 this.kingPos
[oppCol
] = [psq
.x
, psq
.y
];
284 this.stunned
= JSON
.parse(move.stunned
);
285 for (let i
=0; i
<8; i
++) {
286 for (let j
=0; j
<8; j
++) {
287 const square
= V
.CoordsToSquare({ x: i
, y: j
});
288 const pieceIJ
= this.getPiece(i
, j
);
289 if (!this.stunned
[square
]) {
290 const pIdx
= V
.STUNNED
.findIndex(p
=> p
== pieceIJ
);
292 this.board
[i
][j
] = this.getColor(i
, j
) + ChessRules
.PIECES
[pIdx
];
295 const pIdx
= ChessRules
.PIECES
.findIndex(p
=> p
== pieceIJ
);
297 this.board
[i
][j
] = this.getColor(i
, j
) + V
.STUNNED
[pIdx
];
303 static get VALUES() {
304 return Object
.assign(
317 static get SEARCH_DEPTH() {
323 move.appear
.length
== 2 &&
324 move.vanish
.length
== 2 &&
325 move.appear
.concat(move.vanish
).every(
326 av
=> ChessRules
.PIECES
.includes(av
.p
)) &&
327 move.appear
[0].p
== V
.KING
329 if (move.end
.y
< move.start
.y
) return "0-0-0";
332 const finalSquare
= V
.CoordsToSquare(move.end
);
333 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
334 const captureMark
= move.vanish
.length
>= 2 ? "x" : "";
336 if (piece
== 'p' && captureMark
.length
== 1)
337 pawnMark
= V
.CoordToColumn(move.start
.y
); //start column
338 // Piece or pawn movement
340 (piece
== V
.PAWN
? pawnMark : piece
.toUpperCase()) +
341 captureMark
+ finalSquare
;
344 move.appear
[0].c
== move.vanish
[0].c
&&
345 move.appear
[0].p
!= 'p'
348 notation
+= "=" + move.appear
[0].p
.toUpperCase();