1d8469e2419be18c51c9a63bc5ecee03bbb03784
1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
3 export class CannibalRules
extends ChessRules
{
5 static get KING_CODE() {
15 static get KING_DECODE() {
25 // Kings may be disguised:
27 const piece
= this.board
[x
][y
].charAt(1);
28 if (Object
.keys(V
.KING_DECODE
).includes(piece
))
29 return V
.KING_DECODE
[piece
];
34 return (Object
.keys(V
.KING_DECODE
).includes(b
[1]) ? "Cannibal/" : "") + b
;
37 static IsGoodPosition(position
) {
38 if (position
.length
== 0) return false;
39 const rows
= position
.split("/");
40 if (rows
.length
!= V
.size
.x
) return false;
41 let kings
= { "w": 0, "b": 0 };
42 const allPiecesCodes
= V
.PIECES
.concat(Object
.keys(V
.KING_DECODE
));
43 const kingBlackCodes
= Object
.keys(V
.KING_DECODE
).concat(['k']);
44 const kingWhiteCodes
=
45 Object
.keys(V
.KING_DECODE
).map(k
=> k
.toUpperCase()).concat(['K']);
46 for (let row
of rows
) {
48 for (let i
= 0; i
< row
.length
; i
++) {
49 if (kingBlackCodes
.includes(row
[i
])) kings
['b']++;
50 else if (kingWhiteCodes
.includes(row
[i
])) kings
['w']++;
51 if (allPiecesCodes
.includes(row
[i
].toLowerCase())) sumElts
++;
53 const num
= parseInt(row
[i
], 10);
54 if (isNaN(num
)) return false;
58 if (sumElts
!= V
.size
.y
) return false;
60 // Both kings should be on board, only one of each color:
61 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
65 // Kings may be disguised:
66 setOtherVariables(fen
) {
67 super.setOtherVariables(fen
);
68 const rows
= V
.ParseFen(fen
).position
.split("/");
69 if (this.kingPos
["w"][0] < 0 || this.kingPos
["b"][0] < 0) {
70 for (let i
= 0; i
< rows
.length
; i
++) {
71 let k
= 0; //column index on board
72 for (let j
= 0; j
< rows
[i
].length
; j
++) {
73 const piece
= rows
[i
].charAt(j
);
74 if (Object
.keys(V
.KING_DECODE
).includes(piece
.toLowerCase())) {
75 const color
= (piece
.charCodeAt(0) <= 90 ? 'w' : 'b');
76 this.kingPos
[color
] = [i
, k
];
78 const num
= parseInt(rows
[i
].charAt(j
), 10);
79 if (!isNaN(num
)) k
+= num
- 1;
87 // Trim all non-capturing moves
88 static KeepCaptures(moves
) {
89 return moves
.filter(m
=> m
.vanish
.length
== 2 && m
.appear
.length
== 1);
92 // Stop at the first capture found (if any)
94 const color
= this.turn
;
95 const oppCol
= V
.GetOppCol(color
);
96 for (let i
= 0; i
< V
.size
.x
; i
++) {
97 for (let j
= 0; j
< V
.size
.y
; j
++) {
99 this.board
[i
][j
] != V
.EMPTY
&&
100 this.getColor(i
, j
) != oppCol
&&
101 this.filterValid(this.getPotentialMovesFrom([i
, j
])).some(m
=>
102 // Warning: discard castle moves
103 m
.vanish
.length
== 2 && m
.appear
.length
== 1)
112 // Because of the disguised kings, getPiece() could be wrong:
113 // use board[x][y][1] instead (always valid).
114 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
115 const initColor
= this.getColor(sx
, sy
);
116 const initPiece
= this.board
[sx
][sy
].charAt(1);
122 c: tr
? tr
.c : initColor
,
123 p: tr
? tr
.p : initPiece
136 // The opponent piece disappears if we take it
137 if (this.board
[ex
][ey
] != V
.EMPTY
) {
142 c: this.getColor(ex
, ey
),
143 p: this.board
[ex
][ey
].charAt(1)
147 // If the captured piece has a different nature: take it as well
148 if (mv
.vanish
[0].p
!= mv
.vanish
[1].p
) {
150 mv
.vanish
[0].p
== V
.KING
||
151 Object
.keys(V
.KING_DECODE
).includes(mv
.vanish
[0].p
)
153 mv
.appear
[0].p
= V
.KING_CODE
[mv
.vanish
[1].p
];
154 } else mv
.appear
[0].p
= mv
.vanish
[1].p
;
157 else if (!!tr
&& mv
.vanish
[0].p
!= V
.PAWN
)
158 // Special case of a non-capturing king-as-pawn promotion
159 mv
.appear
[0].p
= V
.KING_CODE
[tr
.p
];
164 getPotentialMovesFrom([x
, y
]) {
165 const piece
= this.board
[x
][y
].charAt(1);
166 if (Object
.keys(V
.KING_DECODE
).includes(piece
))
167 return super.getPotentialMovesFrom([x
, y
], V
.KING_DECODE
[piece
]);
168 return super.getPotentialMovesFrom([x
, y
], piece
);
171 addPawnMoves([x1
, y1
], [x2
, y2
], moves
) {
172 let finalPieces
= [V
.PAWN
];
173 const color
= this.turn
;
174 const lastRank
= (color
== "w" ? 0 : V
.size
.x
- 1);
175 if (x2
== lastRank
) {
176 if (this.board
[x2
][y2
] != V
.EMPTY
)
177 // Cannibal rules: no choice if capture
178 finalPieces
= [this.getPiece(x2
, y2
)];
179 else finalPieces
= V
.PawnSpecs
.promotions
;
182 for (let piece
of finalPieces
) {
183 tr
= (piece
!= V
.PAWN
? { c: color
, p: piece
} : null);
184 moves
.push(this.getBasicMove([x1
, y1
], [x2
, y2
], tr
));
188 getPossibleMovesFrom(sq
) {
189 let moves
= this.filterValid(this.getPotentialMovesFrom(sq
));
190 const captureMoves
= V
.KeepCaptures(moves
);
191 if (captureMoves
.length
> 0) return captureMoves
;
192 if (this.atLeastOneCapture()) return [];
197 const moves
= super.getAllValidMoves();
198 if (moves
.some(m
=> m
.vanish
.length
== 2 && m
.appear
.length
== 1))
199 return V
.KeepCaptures(moves
);
204 const c
= V
.GetOppCol(this.turn
);
205 const piece
= move.appear
[0].p
;
206 // Update king position + flags
207 if (piece
== V
.KING
|| Object
.keys(V
.KING_DECODE
).includes(piece
)) {
208 this.kingPos
[c
][0] = move.appear
[0].x
;
209 this.kingPos
[c
][1] = move.appear
[0].y
;
210 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
212 // Next call is still required because the king may eat an opponent's rook
213 // TODO: castleFlags will be turned off twice then.
214 super.updateCastleFlags(move, piece
);
218 // (Potentially) Reset king position
219 const c
= this.getColor(move.start
.x
, move.start
.y
);
220 const piece
= move.appear
[0].p
;
221 if (piece
== V
.KING
|| Object
.keys(V
.KING_DECODE
).includes(piece
))
222 this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
225 static get VALUES() {
237 let notation
= super.getNotation(move);
238 const lastRank
= (move.appear
[0].c
== "w" ? 0 : 7);
240 move.end
.x
!= lastRank
&&
241 this.getPiece(move.start
.x
, move.start
.y
) == V
.PAWN
&&
242 move.vanish
.length
== 2 &&
243 move.appear
[0].p
!= V
.PAWN
245 // Fix "promotion" (transform indicator) from base_rules notation
246 notation
= notation
.slice(0, -2);