Add Colorbound Clobberers (R. Betza)
[vchess.git] / client / src / variants / Colorbound.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt } from "@/utils/alea";
4
5 export class ColorboundRules extends ChessRules {
6 static get PawnSpecs() {
7 return Object.assign(
8 {},
9 ChessRules.PawnSpecs,
10 { promotions: V.PIECES }
11 );
12 }
13
14 getPpath(b) {
15 if ([V.C_ROOK, V.C_KNIGHT, V.C_BISHOP, V.C_QUEEN].includes(b[1]))
16 return "Colorbound/" + b;
17 return b;
18 }
19
20 static GenRandInitFen(randomness) {
21 if (randomness == 0)
22 return "dhaskahd/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ahah -";
23
24 // Mapping white --> black (at least at start):
25 const piecesMap = {
26 'r': 'd',
27 'n': 'h',
28 'b': 'a',
29 'q': 's',
30 'k': 'k'
31 };
32
33 let pieces = { w: new Array(8), b: new Array(8) };
34 let flags = "";
35 // Shuffle pieces on first (and last rank if randomness == 2)
36 for (let c of ["w", "b"]) {
37 if (c == 'b' && randomness == 1) {
38 pieces['b'] = pieces['w'].map(p => piecesMap[p]);
39 flags += flags;
40 break;
41 }
42
43 // TODO: same code as in base_rules. Should extract and factorize?
44
45 let positions = ArrayFun.range(8);
46
47 let randIndex = 2 * randInt(4);
48 const bishop1Pos = positions[randIndex];
49 let randIndex_tmp = 2 * randInt(4) + 1;
50 const bishop2Pos = positions[randIndex_tmp];
51 positions.splice(Math.max(randIndex, randIndex_tmp), 1);
52 positions.splice(Math.min(randIndex, randIndex_tmp), 1);
53
54 randIndex = randInt(6);
55 const knight1Pos = positions[randIndex];
56 positions.splice(randIndex, 1);
57 randIndex = randInt(5);
58 const knight2Pos = positions[randIndex];
59 positions.splice(randIndex, 1);
60
61 randIndex = randInt(4);
62 const queenPos = positions[randIndex];
63 positions.splice(randIndex, 1);
64
65 const rook1Pos = positions[0];
66 const kingPos = positions[1];
67 const rook2Pos = positions[2];
68
69 pieces[c][rook1Pos] = "r";
70 pieces[c][knight1Pos] = "n";
71 pieces[c][bishop1Pos] = "b";
72 pieces[c][queenPos] = "q";
73 pieces[c][kingPos] = "k";
74 pieces[c][bishop2Pos] = "b";
75 pieces[c][knight2Pos] = "n";
76 pieces[c][rook2Pos] = "r";
77 if (c == 'b') pieces[c] = pieces[c].map(p => piecesMap[p]);
78 flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
79 }
80 // Add turn + flags + enpassant
81 return (
82 pieces["b"].join("") +
83 "/8/pppppppp/8/8/8/PPPPPPPP/" +
84 pieces["w"].join("").toUpperCase() +
85 " w 0 " + flags + " -"
86 );
87 }
88
89 static get C_ROOK() {
90 return 'd';
91 }
92 static get C_KNIGHT() {
93 return 'h';
94 }
95 static get C_BISHOP() {
96 return 'a';
97 }
98 static get C_QUEEN() {
99 return 's';
100 }
101
102 static get PIECES() {
103 return (
104 ChessRules.PIECES.concat([V.C_ROOK, V.C_KINGHT, V.C_BISHOP, V.C_QUEEN])
105 );
106 }
107
108 getPotentialMovesFrom([x, y]) {
109 switch (this.getPiece(x, y)) {
110 case V.C_ROOK:
111 return this.getPotentialC_rookMoves([x, y]);
112 case V.C_KNIGHT:
113 return this.getPotentialC_knightMoves([x, y]);
114 case V.C_BISHOP:
115 return this.getPotentialC_bishopMoves([x, y]);
116 case V.C_QUEEN:
117 return this.getPotentialC_queenMoves([x, y]);
118 default:
119 return super.getPotentialMovesFrom([x, y]);
120 }
121 return [];
122 }
123
124 static get steps() {
125 return Object.assign(
126 {},
127 ChessRules.steps,
128 {
129 // Dabbabah
130 'd': [
131 [-2, 0],
132 [0, -2],
133 [2, 0],
134 [0, 2]
135 ],
136 // Alfil
137 'a': [
138 [2, 2],
139 [2, -2],
140 [-2, 2],
141 [-2, -2]
142 ],
143 // Ferz
144 'f': [
145 [1, 1],
146 [1, -1],
147 [-1, 1],
148 [-1, -1]
149 ]
150 }
151 );
152 }
153
154 getPotentialC_rookMoves(sq) {
155 return (
156 this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
157 this.getSlideNJumpMoves(sq, V.steps['d'], "oneStep"))
158 );
159 }
160
161 getPotentialC_knightMoves(sq) {
162 return (
163 this.getSlideNJumpMoves(sq, V.steps['a'], "oneStep").concat(
164 this.getSlideNJumpMoves(sq, V.steps[V.ROOK], "oneStep"))
165 );
166 }
167
168 getPotentialC_bishopMoves(sq) {
169 return (
170 this.getSlideNJumpMoves(sq, V.steps['d'], "oneStep").concat(
171 this.getSlideNJumpMoves(sq, V.steps['a'], "oneStep")).concat(
172 this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep"))
173 );
174 }
175
176 getPotentialC_queenMoves(sq) {
177 return (
178 this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
179 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"))
180 );
181 }
182
183 // TODO: really find a way to avoid duolicating most of the castling code
184 // each time: here just the queenside castling squares change for black.
185 getCastleMoves([x, y]) {
186 const c = this.getColor(x, y);
187 if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
188 return [];
189
190 const oppCol = V.GetOppCol(c);
191 let moves = [];
192 let i = 0;
193 // King, then rook:
194 const finalSquares = [
195 // Black castle long in an unusual way:
196 (c == 'w' ? [2, 3] : [1, 2]),
197 [V.size.y - 2, V.size.y - 3]
198 ];
199 castlingCheck: for (
200 let castleSide = 0;
201 castleSide < 2;
202 castleSide++ //large, then small
203 ) {
204 if (this.castleFlags[c][castleSide] >= V.size.y) continue;
205
206 const rookPos = this.castleFlags[c][castleSide];
207 const castlingPiece = this.getPiece(x, rookPos);
208 const finDist = finalSquares[castleSide][0] - y;
209 let step = finDist / Math.max(1, Math.abs(finDist));
210 i = y;
211 do {
212 if (
213 this.isAttacked([x, i], oppCol) ||
214 (this.board[x][i] != V.EMPTY &&
215 (this.getColor(x, i) != c ||
216 ![V.KING, castlingPiece].includes(this.getPiece(x, i))))
217 ) {
218 continue castlingCheck;
219 }
220 i += step;
221 } while (i != finalSquares[castleSide][0]);
222
223 step = castleSide == 0 ? -1 : 1;
224 for (i = y + step; i != rookPos; i += step) {
225 if (this.board[x][i] != V.EMPTY) continue castlingCheck;
226 }
227
228 for (i = 0; i < 2; i++) {
229 if (
230 finalSquares[castleSide][i] != rookPos &&
231 this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
232 (
233 this.getPiece(x, finalSquares[castleSide][i]) != V.KING ||
234 this.getColor(x, finalSquares[castleSide][i]) != c
235 )
236 ) {
237 continue castlingCheck;
238 }
239 }
240
241 moves.push(
242 new Move({
243 appear: [
244 new PiPo({
245 x: x,
246 y: finalSquares[castleSide][0],
247 p: V.KING,
248 c: c
249 }),
250 new PiPo({
251 x: x,
252 y: finalSquares[castleSide][1],
253 p: castlingPiece,
254 c: c
255 })
256 ],
257 vanish: [
258 new PiPo({ x: x, y: y, p: V.KING, c: c }),
259 new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c })
260 ],
261 end:
262 Math.abs(y - rookPos) <= 2
263 ? { x: x, y: rookPos }
264 : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
265 })
266 );
267 }
268
269 return moves;
270 }
271
272 isAttacked(sq, color) {
273 return (
274 super.isAttacked(sq, color) ||
275 this.isAttackedByC_rook(sq, color) ||
276 this.isAttackedByC_knight(sq, color) ||
277 this.isAttackedByC_bishop(sq, color) ||
278 this.isAttackedByC_queen(sq, color)
279 );
280 }
281
282 isAttackedByC_rook(sq, color) {
283 return (
284 this.isAttackedBySlideNJump(sq, color, V.C_ROOK, V.steps[V.BISHOP]) ||
285 this.isAttackedBySlideNJump(
286 sq, color, V.C_ROOK, V.steps['d'], "oneStep")
287 );
288 }
289
290 isAttackedByC_knight(sq, color) {
291 return (
292 this.isAttackedBySlideNJump(
293 sq, color, V.C_KNIGHT, V.steps[V.ROOK], "oneStep") ||
294 this.isAttackedBySlideNJump(
295 sq, color, V.C_KNIGHT, V.steps['a'], "oneStep")
296 );
297 }
298
299 isAttackedByC_bishop(sq, color) {
300 return (
301 this.isAttackedBySlideNJump(
302 sq, color, V.C_BISHOP, V.steps['d'], "oneStep") ||
303 this.isAttackedBySlideNJump(
304 sq, color, V.C_BISHOP, V.steps['a'], "oneStep") ||
305 this.isAttackedBySlideNJump(
306 sq, color, V.C_BISHOP, V.steps['f'], "oneStep")
307 );
308 }
309
310 isAttackedByC_queen(sq, color) {
311 return (
312 this.isAttackedBySlideNJump(sq, color, V.C_QUEEN, V.steps[V.BISHOP]) ||
313 this.isAttackedBySlideNJump(
314 sq, color, V.C_ROOK, V.steps[V.KNIGHT], "oneStep")
315 );
316 }
317
318 static get VALUES() {
319 return Object.assign(
320 {},
321 ChessRules.VALUES,
322 {
323 d: 4,
324 h: 3,
325 a: 5,
326 s: 6
327 }
328 );
329 }
330 };