Forgot arg of atLeastOneCapture()
[vchess.git] / client / src / variants / Empire.js
1 import { ChessRules } from "@/base_rules";
2
3 export class EmpireRules extends ChessRules {
4
5 static get PawnSpecs() {
6 return Object.assign(
7 {},
8 ChessRules.PawnSpecs,
9 { promotions: [V.QUEEN] }
10 );
11 }
12
13 static get LoseOnRepetition() {
14 return true;
15 }
16
17 static IsGoodFlags(flags) {
18 // Only black can castle
19 return !!flags.match(/^[a-z]{2,2}$/);
20 }
21
22 getPpath(b) {
23 return (b[0] == 'w' ? "Empire/" : "") + b;
24 }
25
26 static GenRandInitFen(randomness) {
27 if (randomness == 0)
28 return "rnbqkbnr/pppppppp/8/8/8/PPPSSPPP/8/TECDKCET w 0 ah -";
29
30 // Mapping kingdom --> empire:
31 const piecesMap = {
32 'R': 'T',
33 'N': 'E',
34 'B': 'C',
35 'Q': 'D',
36 'K': 'K'
37 };
38
39 const baseFen = ChessRules.GenRandInitFen(randomness);
40 return (
41 baseFen.substr(0, 24) + "PPPSSPPP/8/" +
42 baseFen.substr(35, 8).split('').map(p => piecesMap[p]).join('') +
43 baseFen.substr(43, 5) + baseFen.substr(50)
44 );
45 }
46
47 getFlagsFen() {
48 return this.castleFlags['b'].map(V.CoordToColumn).join("");
49 }
50
51 setFlags(fenflags) {
52 this.castleFlags = { 'b': [-1, -1] };
53 for (let i = 0; i < 2; i++)
54 this.castleFlags['b'][i] = V.ColumnToCoord(fenflags.charAt(i));
55 }
56
57 static get TOWER() {
58 return 't';
59 }
60 static get EAGLE() {
61 return 'e';
62 }
63 static get CARDINAL() {
64 return 'c';
65 }
66 static get DUKE() {
67 return 'd';
68 }
69 static get SOLDIER() {
70 return 's';
71 }
72 // Kaiser is technically a King, so let's keep things simple.
73
74 static get PIECES() {
75 return ChessRules.PIECES.concat(
76 [V.TOWER, V.EAGLE, V.CARDINAL, V.DUKE, V.SOLDIER]);
77 }
78
79 getPotentialMovesFrom(sq) {
80 let moves = [];
81 const piece = this.getPiece(sq[0], sq[1]);
82 switch (piece) {
83 case V.TOWER:
84 moves = this.getPotentialTowerMoves(sq);
85 break;
86 case V.EAGLE:
87 moves = this.getPotentialEagleMoves(sq);
88 break;
89 case V.CARDINAL:
90 moves = this.getPotentialCardinalMoves(sq);
91 break;
92 case V.DUKE:
93 moves = this.getPotentialDukeMoves(sq);
94 break;
95 case V.SOLDIER:
96 moves = this.getPotentialSoldierMoves(sq);
97 break;
98 default:
99 moves = super.getPotentialMovesFrom(sq);
100 }
101 if (
102 piece != V.KING &&
103 this.kingPos['w'][0] != this.kingPos['b'][0] &&
104 this.kingPos['w'][1] != this.kingPos['b'][1]
105 ) {
106 return moves;
107 }
108 // TODO: factor two next "if" into one (rank/column...)
109 if (this.kingPos['w'][1] == this.kingPos['b'][1]) {
110 const colKing = this.kingPos['w'][1];
111 let intercept = 0; //count intercepting pieces
112 let [kingPos1, kingPos2] = [this.kingPos['w'][0], this.kingPos['b'][0]];
113 if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1];
114 for (let i = kingPos1 + 1; i < kingPos2; i++) {
115 if (this.board[i][colKing] != V.EMPTY) intercept++;
116 }
117 if (intercept >= 2) return moves;
118 // intercept == 1 (0 is impossible):
119 // Any move not removing intercept is OK
120 return moves.filter(m => {
121 return (
122 // From another column?
123 m.start.y != colKing ||
124 // From behind a king? (including kings themselves!)
125 m.start.x <= kingPos1 ||
126 m.start.x >= kingPos2 ||
127 // Intercept piece moving: must remain in-between
128 (
129 m.end.y == colKing &&
130 m.end.x > kingPos1 &&
131 m.end.x < kingPos2
132 )
133 );
134 });
135 }
136 if (this.kingPos['w'][0] == this.kingPos['b'][0]) {
137 const rowKing = this.kingPos['w'][0];
138 let intercept = 0; //count intercepting pieces
139 let [kingPos1, kingPos2] = [this.kingPos['w'][1], this.kingPos['b'][1]];
140 if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1];
141 for (let i = kingPos1 + 1; i < kingPos2; i++) {
142 if (this.board[rowKing][i] != V.EMPTY) intercept++;
143 }
144 if (intercept >= 2) return moves;
145 // intercept == 1 (0 is impossible):
146 // Any move not removing intercept is OK
147 return moves.filter(m => {
148 return (
149 // From another row?
150 m.start.x != rowKing ||
151 // From "behind" a king? (including kings themselves!)
152 m.start.y <= kingPos1 ||
153 m.start.y >= kingPos2 ||
154 // Intercept piece moving: must remain in-between
155 (
156 m.end.x == rowKing &&
157 m.end.y > kingPos1 &&
158 m.end.y < kingPos2
159 )
160 );
161 });
162 }
163 // piece == king: check only if move.end.y == enemy king column,
164 // or if move.end.x == enemy king rank.
165 const color = this.getColor(sq[0], sq[1]);
166 const oppCol = V.GetOppCol(color);
167 // check == -1 if (row, or col) unchecked, 1 if checked and occupied,
168 // 0 if checked and clear
169 let check = [-1, -1];
170 return moves.filter(m => {
171 if (
172 m.end.y != this.kingPos[oppCol][1] &&
173 m.end.x != this.kingPos[oppCol][0]
174 ) {
175 return true;
176 }
177 // TODO: factor two next "if"...
178 if (m.end.x == this.kingPos[oppCol][0]) {
179 if (check[0] < 0) {
180 // Do the check:
181 check[0] = 0;
182 let [kingPos1, kingPos2] =
183 [this.kingPos[color][1], this.kingPos[oppCol][1]];
184 if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1];
185 for (let i = kingPos1 + 1; i < kingPos2; i++) {
186 if (this.board[m.end.x][i] != V.EMPTY) {
187 check[0]++;
188 break;
189 }
190 }
191 return check[0] == 1;
192 }
193 // Check already done:
194 return check[0] == 1;
195 }
196 //if (m.end.y == this.kingPos[oppCol][1]) //true...
197 if (check[1] < 0) {
198 // Do the check:
199 check[1] = 0;
200 let [kingPos1, kingPos2] =
201 [this.kingPos[color][0], this.kingPos[oppCol][0]];
202 if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1];
203 for (let i = kingPos1 + 1; i < kingPos2; i++) {
204 if (this.board[i][m.end.y] != V.EMPTY) {
205 check[1]++;
206 break;
207 }
208 }
209 return check[1] == 1;
210 }
211 // Check already done:
212 return check[1] == 1;
213 });
214 }
215
216 getSlideNJumpMoves_([x, y], steps, oneStep) {
217 let moves = [];
218 outerLoop: for (let step of steps) {
219 const s = step.s;
220 let i = x + s[0];
221 let j = y + s[1];
222 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
223 if (!step.onlyTake) moves.push(this.getBasicMove([x, y], [i, j]));
224 // NOTE: (bad) HACK here, since onlyTake is true only for Eagle
225 // capturing moves, which are oneStep...
226 if (!!oneStep || !!step.onlyTake) continue outerLoop;
227 i += s[0];
228 j += s[1];
229 }
230 if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]) && !step.onlyMove)
231 moves.push(this.getBasicMove([x, y], [i, j]));
232 }
233 return moves;
234 }
235
236 static get steps() {
237 return (
238 Object.assign(
239 {
240 t: [
241 { s: [-1, 0] },
242 { s: [1, 0] },
243 { s: [0, -1] },
244 { s: [0, 1] },
245 { s: [-1, -1], onlyMove: true },
246 { s: [-1, 1], onlyMove: true },
247 { s: [1, -1], onlyMove: true },
248 { s: [1, 1], onlyMove: true }
249 ],
250 c: [
251 { s: [-1, 0], onlyMove: true },
252 { s: [1, 0], onlyMove: true },
253 { s: [0, -1], onlyMove: true },
254 { s: [0, 1], onlyMove: true },
255 { s: [-1, -1] },
256 { s: [-1, 1] },
257 { s: [1, -1] },
258 { s: [1, 1] }
259 ],
260 e: [
261 { s: [-1, 0], onlyMove: true },
262 { s: [1, 0], onlyMove: true },
263 { s: [0, -1], onlyMove: true },
264 { s: [0, 1], onlyMove: true },
265 { s: [-1, -1], onlyMove: true },
266 { s: [-1, 1], onlyMove: true },
267 { s: [1, -1], onlyMove: true },
268 { s: [1, 1], onlyMove: true },
269 { s: [-2, -1], onlyTake: true },
270 { s: [-2, 1], onlyTake: true },
271 { s: [-1, -2], onlyTake: true },
272 { s: [-1, 2], onlyTake: true },
273 { s: [1, -2], onlyTake: true },
274 { s: [1, 2], onlyTake: true },
275 { s: [2, -1], onlyTake: true },
276 { s: [2, 1], onlyTake: true }
277 ]
278 },
279 ChessRules.steps
280 )
281 );
282 }
283
284 getPotentialTowerMoves(sq) {
285 return this.getSlideNJumpMoves_(sq, V.steps[V.TOWER]);
286 }
287
288 getPotentialCardinalMoves(sq) {
289 return this.getSlideNJumpMoves_(sq, V.steps[V.CARDINAL]);
290 }
291
292 getPotentialEagleMoves(sq) {
293 return this.getSlideNJumpMoves_(sq, V.steps[V.EAGLE]);
294 }
295
296 getPotentialDukeMoves([x, y]) {
297 // Anything to capture around? mark other steps to explore after
298 let steps = [];
299 const oppCol = V.GetOppCol(this.getColor(x, y));
300 let moves = [];
301 for (let s of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
302 const [i, j] = [x + s[0], y + s[1]];
303 if (
304 V.OnBoard(i, j) &&
305 this.board[i][j] != V.EMPTY &&
306 this.getColor(i, j) == oppCol
307 ) {
308 moves.push(super.getBasicMove([x, y], [i, j]));
309 }
310 else steps.push({ s: s, onlyMove: true });
311 }
312 if (steps.length > 0) {
313 const noncapturingMoves = this.getSlideNJumpMoves_([x, y], steps);
314 Array.prototype.push.apply(moves, noncapturingMoves);
315 }
316 return moves;
317 }
318
319 getPotentialKingMoves([x, y]) {
320 if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]);
321 // Empire doesn't castle:
322 return super.getSlideNJumpMoves(
323 [x, y],
324 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
325 "oneStep"
326 );
327 }
328
329 getPotentialSoldierMoves([x, y]) {
330 const c = this.getColor(x, y);
331 const shiftX = (c == 'w' ? -1 : 1);
332 const lastRank = (c == 'w' && x == 0 || c == 'b' && x == 9);
333 let steps = [];
334 if (!lastRank) steps.push([shiftX, 0]);
335 if (y > 0) steps.push([0, -1]);
336 if (y < 9) steps.push([0, 1]);
337 return super.getSlideNJumpMoves([x, y], steps, "oneStep");
338 }
339
340 isAttacked(sq, color) {
341 if (color == 'b') return super.isAttacked(sq, color);
342 // Empire: only pawn and king (+ queen if promotion) in common:
343 return (
344 super.isAttackedByPawn(sq, color) ||
345 this.isAttackedByTower(sq, color) ||
346 this.isAttackedByEagle(sq, color) ||
347 this.isAttackedByCardinal(sq, color) ||
348 this.isAttackedByDuke(sq, color) ||
349 this.isAttackedBySoldier(sq, color) ||
350 super.isAttackedByKing(sq, color) ||
351 super.isAttackedByQueen(sq, color)
352 );
353 }
354
355 isAttackedByTower(sq, color) {
356 return super.isAttackedBySlideNJump(sq, color, V.TOWER, V.steps[V.ROOK]);
357 }
358
359 isAttackedByEagle(sq, color) {
360 return super.isAttackedBySlideNJump(
361 sq, color, V.EAGLE, V.steps[V.KNIGHT], "oneStep");
362 }
363
364 isAttackedByCardinal(sq, color) {
365 return super.isAttackedBySlideNJump(
366 sq, color, V.CARDINAL, V.steps[V.BISHOP]);
367 }
368
369 isAttackedByDuke(sq, color) {
370 return (
371 super.isAttackedBySlideNJump(
372 sq, color, V.DUKE,
373 V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"
374 )
375 );
376 }
377
378 isAttackedBySoldier([x, y], color) {
379 const shiftX = (color == 'w' ? 1 : -1); //shift from king
380 return super.isAttackedBySlideNJump(
381 [x, y], color, V.SOLDIER, [[shiftX, 0], [0, 1], [0, -1]], "oneStep");
382 }
383
384 updateCastleFlags(move, piece) {
385 // Only black can castle:
386 const firstRank = 0;
387 if (piece == V.KING && move.appear[0].c == 'b')
388 this.castleFlags['b'] = [8, 8];
389 else if (
390 move.start.x == firstRank &&
391 this.castleFlags['b'].includes(move.start.y)
392 ) {
393 const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
394 this.castleFlags['b'][flagIdx] = 8;
395 }
396 else if (
397 move.end.x == firstRank &&
398 this.castleFlags['b'].includes(move.end.y)
399 ) {
400 const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
401 this.castleFlags['b'][flagIdx] = 8;
402 }
403 }
404
405 getCurrentScore() {
406 // Turn has changed:
407 const color = V.GetOppCol(this.turn);
408 const lastRank = (color == 'w' ? 0 : 7);
409 if (this.kingPos[color][0] == lastRank)
410 // The opposing edge is reached!
411 return color == "w" ? "1-0" : "0-1";
412 if (this.atLeastOneMove()) return "*";
413 // Game over
414 const oppCol = this.turn;
415 return (oppCol == "w" ? "0-1" : "1-0");
416 }
417
418 static get VALUES() {
419 return Object.assign(
420 {},
421 ChessRules.VALUES,
422 {
423 t: 7,
424 e: 7,
425 c: 4,
426 d: 4,
427 s: 2
428 }
429 );
430 }
431
432 static get SEARCH_DEPTH() {
433 return 2;
434 }
435
436 };