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 let mv
= super.getBasicMove([sx
, sy
], [ex
, ey
], tr
);
117 if (this.board
[ex
][ey
] != V
.EMPTY
) {
118 // If the captured piece has a different nature: take it as well
119 if (mv
.vanish
[0].p
!= mv
.vanish
[1].p
) {
121 mv
.vanish
[0].p
== V
.KING
||
122 Object
.keys(V
.KING_DECODE
).includes(mv
.vanish
[0].p
)
124 mv
.appear
[0].p
= V
.KING_CODE
[mv
.vanish
[1].p
];
126 else mv
.appear
[0].p
= mv
.vanish
[1].p
;
129 else if (!!tr
&& mv
.vanish
[0].p
!= V
.PAWN
)
130 // Special case of a non-capturing king-as-pawn promotion
131 mv
.appear
[0].p
= V
.KING_CODE
[tr
.p
];
136 getPotentialMovesFrom([x
, y
]) {
137 const piece
= this.board
[x
][y
].charAt(1);
138 if (Object
.keys(V
.KING_DECODE
).includes(piece
))
139 return super.getPotentialMovesFrom([x
, y
], V
.KING_DECODE
[piece
]);
140 return super.getPotentialMovesFrom([x
, y
], piece
);
143 addPawnMoves([x1
, y1
], [x2
, y2
], moves
) {
144 let finalPieces
= [V
.PAWN
];
145 const color
= this.turn
;
146 const lastRank
= (color
== "w" ? 0 : V
.size
.x
- 1);
147 if (x2
== lastRank
) {
148 if (this.board
[x2
][y2
] != V
.EMPTY
)
149 // Cannibal rules: no choice if capture
150 finalPieces
= [this.getPiece(x2
, y2
)];
151 else finalPieces
= V
.PawnSpecs
.promotions
;
154 for (let piece
of finalPieces
) {
155 tr
= (piece
!= V
.PAWN
? { c: color
, p: piece
} : null);
156 moves
.push(this.getBasicMove([x1
, y1
], [x2
, y2
], tr
));
160 getPossibleMovesFrom(sq
) {
161 let moves
= this.filterValid(this.getPotentialMovesFrom(sq
));
162 const captureMoves
= V
.KeepCaptures(moves
);
163 if (captureMoves
.length
> 0) return captureMoves
;
164 if (this.atLeastOneCapture()) return [];
169 const moves
= super.getAllValidMoves();
170 if (moves
.some(m
=> m
.vanish
.length
== 2 && m
.appear
.length
== 1))
171 return V
.KeepCaptures(moves
);
176 const c
= V
.GetOppCol(this.turn
);
177 const piece
= move.appear
[0].p
;
178 // Update king position + flags
179 if (piece
== V
.KING
|| Object
.keys(V
.KING_DECODE
).includes(piece
)) {
180 this.kingPos
[c
][0] = move.appear
[0].x
;
181 this.kingPos
[c
][1] = move.appear
[0].y
;
182 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
184 // Next call is still required because the king may eat an opponent's rook
185 // TODO: castleFlags will be turned off twice then.
186 super.updateCastleFlags(move, piece
);
190 // (Potentially) Reset king position
191 const c
= this.getColor(move.start
.x
, move.start
.y
);
192 const piece
= move.appear
[0].p
;
193 if (piece
== V
.KING
|| Object
.keys(V
.KING_DECODE
).includes(piece
))
194 this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
197 static get VALUES() {
209 let notation
= super.getNotation(move);
210 const lastRank
= (move.appear
[0].c
== "w" ? 0 : 7);
212 move.end
.x
!= lastRank
&&
213 this.getPiece(move.start
.x
, move.start
.y
) == V
.PAWN
&&
214 move.vanish
.length
== 2 &&
215 move.appear
[0].p
!= V
.PAWN
217 // Fix "promotion" (transform indicator) from base_rules notation
218 notation
= notation
.slice(0, -2);