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