Fix Maxima (immobilize kings too)
[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(options) {
27 if (options.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(options);
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 return moves.filter(m => {
168 if (
169 m.end.y != this.kingPos[oppCol][1] &&
170 m.end.x != this.kingPos[oppCol][0]
171 ) {
172 return true;
173 }
174 // check == -1 if (row, or col) unchecked, 1 if checked and occupied,
175 // 0 if checked and clear
176 let check = [-1, -1];
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] = [m.end.y, this.kingPos[oppCol][1]];
183 if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1];
184 for (let i = kingPos1 + 1; i < kingPos2; i++) {
185 if (this.board[m.end.x][i] != V.EMPTY) {
186 check[0]++;
187 break;
188 }
189 }
190 return check[0] == 1;
191 }
192 // Check already done:
193 return check[0] == 1;
194 }
195 //if (m.end.y == this.kingPos[oppCol][1]) //true...
196 if (check[1] < 0) {
197 // Do the check:
198 check[1] = 0;
199 let [kingPos1, kingPos2] = [m.end.x, this.kingPos[oppCol][0]];
200 if (kingPos1 > kingPos2) [kingPos1, kingPos2] = [kingPos2, kingPos1];
201 for (let i = kingPos1 + 1; i < kingPos2; i++) {
202 if (this.board[i][m.end.y] != V.EMPTY) {
203 check[1]++;
204 break;
205 }
206 }
207 return check[1] == 1;
208 }
209 // Check already done:
210 return check[1] == 1;
211 });
212 }
213
214 // TODO: some merging to do with Orda method (and into base_rules.js)
215 getSlideNJumpMoves_([x, y], steps, oneStep) {
216 let moves = [];
217 outerLoop: for (let step of steps) {
218 const s = step.s;
219 let i = x + s[0];
220 let j = y + s[1];
221 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
222 if (!step.onlyTake) moves.push(this.getBasicMove([x, y], [i, j]));
223 // NOTE: (bad) HACK here, since onlyTake is true only for Eagle
224 // capturing moves, which are oneStep...
225 if (oneStep || step.onlyTake) continue outerLoop;
226 i += s[0];
227 j += s[1];
228 }
229 if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]) && !step.onlyMove)
230 moves.push(this.getBasicMove([x, y], [i, j]));
231 }
232 return moves;
233 }
234
235 static get steps() {
236 return (
237 Object.assign(
238 {
239 t: [
240 { s: [-1, 0] },
241 { s: [1, 0] },
242 { s: [0, -1] },
243 { s: [0, 1] },
244 { s: [-1, -1], onlyMove: true },
245 { s: [-1, 1], onlyMove: true },
246 { s: [1, -1], onlyMove: true },
247 { s: [1, 1], onlyMove: true }
248 ],
249 c: [
250 { s: [-1, 0], onlyMove: true },
251 { s: [1, 0], onlyMove: true },
252 { s: [0, -1], onlyMove: true },
253 { s: [0, 1], onlyMove: true },
254 { s: [-1, -1] },
255 { s: [-1, 1] },
256 { s: [1, -1] },
257 { s: [1, 1] }
258 ],
259 e: [
260 { s: [-1, 0], onlyMove: true },
261 { s: [1, 0], onlyMove: true },
262 { s: [0, -1], onlyMove: true },
263 { s: [0, 1], onlyMove: true },
264 { s: [-1, -1], onlyMove: true },
265 { s: [-1, 1], onlyMove: true },
266 { s: [1, -1], onlyMove: true },
267 { s: [1, 1], onlyMove: true },
268 { s: [-2, -1], onlyTake: true },
269 { s: [-2, 1], onlyTake: true },
270 { s: [-1, -2], onlyTake: true },
271 { s: [-1, 2], onlyTake: true },
272 { s: [1, -2], onlyTake: true },
273 { s: [1, 2], onlyTake: true },
274 { s: [2, -1], onlyTake: true },
275 { s: [2, 1], onlyTake: true }
276 ]
277 },
278 ChessRules.steps
279 )
280 );
281 }
282
283 getPotentialTowerMoves(sq) {
284 return this.getSlideNJumpMoves_(sq, V.steps[V.TOWER]);
285 }
286
287 getPotentialCardinalMoves(sq) {
288 return this.getSlideNJumpMoves_(sq, V.steps[V.CARDINAL]);
289 }
290
291 getPotentialEagleMoves(sq) {
292 return this.getSlideNJumpMoves_(sq, V.steps[V.EAGLE]);
293 }
294
295 getPotentialDukeMoves([x, y]) {
296 // Anything to capture around? mark other steps to explore after
297 let steps = [];
298 const oppCol = V.GetOppCol(this.getColor(x, y));
299 let moves = [];
300 for (let s of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
301 const [i, j] = [x + s[0], y + s[1]];
302 if (
303 V.OnBoard(i, j) &&
304 this.board[i][j] != V.EMPTY &&
305 this.getColor(i, j) == oppCol
306 ) {
307 moves.push(super.getBasicMove([x, y], [i, j]));
308 }
309 else steps.push({ s: s, onlyMove: true });
310 }
311 if (steps.length > 0) {
312 const noncapturingMoves = this.getSlideNJumpMoves_([x, y], steps);
313 Array.prototype.push.apply(moves, noncapturingMoves);
314 }
315 return moves;
316 }
317
318 getPotentialKingMoves([x, y]) {
319 if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]);
320 // Empire doesn't castle:
321 return super.getSlideNJumpMoves(
322 [x, y], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1);
323 }
324
325 getPotentialSoldierMoves([x, y]) {
326 const c = this.getColor(x, y);
327 const shiftX = (c == 'w' ? -1 : 1);
328 const lastRank = (c == 'w' && x == 0 || c == 'b' && x == 9);
329 let steps = [];
330 if (!lastRank) steps.push([shiftX, 0]);
331 if (y > 0) steps.push([0, -1]);
332 if (y < 9) steps.push([0, 1]);
333 return super.getSlideNJumpMoves([x, y], steps, 1);
334 }
335
336 isAttacked(sq, color) {
337 if (color == 'b') return super.isAttacked(sq, color);
338 // Empire: only pawn and king (+ queen if promotion) in common:
339 return (
340 super.isAttackedByPawn(sq, color) ||
341 this.isAttackedByTower(sq, color) ||
342 this.isAttackedByEagle(sq, color) ||
343 this.isAttackedByCardinal(sq, color) ||
344 this.isAttackedByDuke(sq, color) ||
345 this.isAttackedBySoldier(sq, color) ||
346 super.isAttackedByKing(sq, color) ||
347 super.isAttackedByQueen(sq, color)
348 );
349 }
350
351 isAttackedByTower(sq, color) {
352 return super.isAttackedBySlideNJump(sq, color, V.TOWER, V.steps[V.ROOK]);
353 }
354
355 isAttackedByEagle(sq, color) {
356 return super.isAttackedBySlideNJump(
357 sq, color, V.EAGLE, V.steps[V.KNIGHT], 1);
358 }
359
360 isAttackedByCardinal(sq, color) {
361 return super.isAttackedBySlideNJump(
362 sq, color, V.CARDINAL, V.steps[V.BISHOP]);
363 }
364
365 isAttackedByDuke(sq, color) {
366 return (
367 super.isAttackedBySlideNJump(
368 sq, color, V.DUKE,
369 V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1
370 )
371 );
372 }
373
374 isAttackedBySoldier([x, y], color) {
375 const shiftX = (color == 'w' ? 1 : -1); //shift from king
376 return super.isAttackedBySlideNJump(
377 [x, y], color, V.SOLDIER, [[shiftX, 0], [0, 1], [0, -1]], 1);
378 }
379
380 updateCastleFlags(move, piece) {
381 // Only black can castle:
382 const firstRank = 0;
383 if (piece == V.KING && move.appear[0].c == 'b')
384 this.castleFlags['b'] = [8, 8];
385 else if (
386 move.start.x == firstRank &&
387 this.castleFlags['b'].includes(move.start.y)
388 ) {
389 const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
390 this.castleFlags['b'][flagIdx] = 8;
391 }
392 else if (
393 move.end.x == firstRank &&
394 this.castleFlags['b'].includes(move.end.y)
395 ) {
396 const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
397 this.castleFlags['b'][flagIdx] = 8;
398 }
399 }
400
401 getCurrentScore() {
402 // Turn has changed:
403 const color = V.GetOppCol(this.turn);
404 const lastRank = (color == 'w' ? 0 : 7);
405 if (this.kingPos[color][0] == lastRank)
406 // The opposing edge is reached!
407 return color == "w" ? "1-0" : "0-1";
408 if (this.atLeastOneMove()) return "*";
409 // Game over
410 const oppCol = this.turn;
411 return (oppCol == "w" ? "0-1" : "1-0");
412 }
413
414 static get VALUES() {
415 return Object.assign(
416 {},
417 ChessRules.VALUES,
418 {
419 t: 7,
420 e: 7,
421 c: 4,
422 d: 4,
423 s: 2
424 }
425 );
426 }
427
428 static get SEARCH_DEPTH() {
429 return 2;
430 }
431
432 };