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