df781b9e09cb914475efb86db3970096a1beaa21
[vchess.git] / client / src / variants / Maxima.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { shuffle } from "@/utils/alea";
4
5 export class MaximaRules extends ChessRules {
6
7 static get Options() {
8 return null;
9 }
10
11 static get HasFlags() {
12 return false;
13 }
14
15 static get HasEnpassant() {
16 return false;
17 }
18
19 static get PIECES() {
20 return ChessRules.PIECES.concat([V.IMMOBILIZER, V.MAGE, V.GUARD]);
21 }
22
23 getPpath(b) {
24 if (b[0] == 'x') return "Maxima/nothing";
25 if (['m','d','g'].includes(b[1]))
26 return "Maxima/" + b;
27 return b;
28 }
29
30 // For space next to the palaces:
31 static get NOTHING() {
32 return "xx";
33 }
34
35 static board2fen(b) {
36 if (b[0] == 'x') return 'x';
37 return ChessRules.board2fen(b);
38 }
39
40 static fen2board(f) {
41 if (f == 'x') return V.NOTHING;
42 return ChessRules.fen2board(f);
43 }
44
45 // TODO: the wall position should be checked too
46 static IsGoodPosition(position) {
47 if (position.length == 0) return false;
48 const rows = position.split("/");
49 if (rows.length != V.size.x) return false;
50 let kings = { "k": 0, "K": 0 };
51 for (let row of rows) {
52 let sumElts = 0;
53 for (let i = 0; i < row.length; i++) {
54 if (['K','k'].includes(row[i])) kings[row[i]]++;
55 if (['x'].concat(V.PIECES).includes(row[i].toLowerCase())) sumElts++;
56 else {
57 const num = parseInt(row[i], 10);
58 if (isNaN(num)) return false;
59 sumElts += num;
60 }
61 }
62 if (sumElts != V.size.y) return false;
63 }
64 if (Object.values(kings).some(v => v != 1)) return false;
65 return true;
66 }
67
68 // No castling, but checks, so keep track of kings
69 setOtherVariables(fen) {
70 this.kingPos = { w: [-1, -1], b: [-1, -1] };
71 const fenParts = fen.split(" ");
72 const position = fenParts[0].split("/");
73 for (let i = 0; i < position.length; i++) {
74 let k = 0;
75 for (let j = 0; j < position[i].length; j++) {
76 switch (position[i].charAt(j)) {
77 case "k":
78 this.kingPos["b"] = [i, k];
79 break;
80 case "K":
81 this.kingPos["w"] = [i, k];
82 break;
83 default: {
84 const num = parseInt(position[i].charAt(j), 10);
85 if (!isNaN(num)) k += num - 1;
86 }
87 }
88 k++;
89 }
90 }
91 }
92
93 static get size() {
94 return { x: 11, y: 8 };
95 }
96
97 static OnBoard(x, y) {
98 return (
99 (x >= 1 && x <= 9 && y >= 0 && y <= 7) ||
100 ([3, 4].includes(y) && [0, 10].includes(x))
101 );
102 }
103
104 static get IMMOBILIZER() {
105 return "m";
106 }
107 static get MAGE() {
108 return 'g';
109 }
110 static get GUARD() {
111 return 'd';
112 }
113 // Although other pieces keep their names here for coding simplicity,
114 // keep in mind that:
115 // - a "rook" is a coordinator, capturing by coordinating with the king
116 // - a "knight" is a long-leaper, capturing as in draughts
117 // - a "bishop" is a chameleon, capturing as its prey
118 // - a "queen" is a withdrawer, capturing by moving away from pieces
119
120 // Is piece on square (x,y) immobilized?
121 isImmobilized([x, y]) {
122 const piece = this.getPiece(x, y);
123 if (piece == V.MAGE)
124 // Mages are not immobilized:
125 return false;
126 const oppCol = V.GetOppCol(this.getColor(x, y));
127 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
128 for (let step of adjacentSteps) {
129 const [i, j] = [x + step[0], y + step[1]];
130 if (
131 V.OnBoard(i, j) &&
132 this.board[i][j] != V.EMPTY &&
133 this.getColor(i, j) == oppCol
134 ) {
135 const oppPiece = this.getPiece(i, j);
136 if (oppPiece == V.IMMOBILIZER) return [i, j];
137 // Only immobilizers are immobilized by chameleons:
138 if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER) return [i, j];
139 }
140 }
141 return null;
142 }
143
144 getPotentialMovesFrom([x, y]) {
145 // Pre-check: is thing on this square immobilized?
146 const imSq = this.isImmobilized([x, y]);
147 const piece = this.getPiece(x, y);
148 if (!!imSq && piece != V.KING) {
149 // Only option is suicide, if I'm not a king:
150 return [
151 new Move({
152 start: { x: x, y: y },
153 end: { x: imSq[0], y: imSq[1] },
154 appear: [],
155 vanish: [
156 new PiPo({
157 x: x,
158 y: y,
159 c: this.getColor(x, y),
160 p: this.getPiece(x, y)
161 })
162 ]
163 })
164 ];
165 }
166 let moves = undefined;
167 switch (piece) {
168 case V.IMMOBILIZER:
169 moves = this.getPotentialImmobilizerMoves([x, y]);
170 break;
171 case V.GUARD:
172 moves = this.getPotentialGuardMoves([x, y]);
173 break;
174 case V.MAGE:
175 moves = this.getPotentialMageMoves([x, y]);
176 break;
177 default:
178 moves = super.getPotentialMovesFrom([x, y]);
179 }
180 const pX = (this.turn == 'w' ? 10 : 0);
181 if (this.board[pX][3] == V.EMPTY && this.board[pX][4] == V.EMPTY)
182 return moves;
183 // Filter out moves resulting in self palace occupation:
184 // NOTE: cannot invade own palace but still check the king there.
185 const pY = (this.board[pX][3] != V.EMPTY ? 4 : 3);
186 return moves.filter(m => m.end.x != pX || m.end.y != pY);
187 }
188
189 getSlideNJumpMoves([x, y], steps, oneStep, mageInitSquare) {
190 const piece = !mageInitSquare ? this.getPiece(x, y) : V.MAGE;
191 const initSquare = mageInitSquare || [x, y];
192 let moves = [];
193 outerLoop: for (let step of steps) {
194 let i = x + step[0];
195 let j = y + step[1];
196 if (piece == V.KING) j = j % V.size.y;
197 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
198 moves.push(this.getBasicMove(initSquare, [i, j]));
199 if (oneStep) continue outerLoop;
200 i += step[0];
201 j += step[1];
202 }
203 // Only king, guard and mage (+ chameleon) can take on occupied square:
204 if (
205 V.OnBoard(i, j) &&
206 [V.KING, V.GUARD, V.MAGE].includes(piece) &&
207 this.canTake(initSquare, [i, j])
208 ) {
209 moves.push(this.getBasicMove(initSquare, [i, j]));
210 }
211 }
212 return moves;
213 }
214
215 // Modify capturing moves among listed pawn moves
216 addPawnCaptures(moves, byChameleon) {
217 const steps = V.steps[V.ROOK];
218 const color = this.turn;
219 const oppCol = V.GetOppCol(color);
220 moves.forEach(m => {
221 if (!!byChameleon && m.start.x != m.end.x && m.start.y != m.end.y)
222 // Chameleon not moving as pawn
223 return;
224 // Try capturing in every direction
225 for (let step of steps) {
226 const sq2 = [m.end.x + 2 * step[0], m.end.y + 2 * step[1]];
227 if (
228 V.OnBoard(sq2[0], sq2[1]) &&
229 this.board[sq2[0]][sq2[1]] != V.EMPTY &&
230 this.getColor(sq2[0], sq2[1]) == color
231 ) {
232 // Potential capture
233 const sq1 = [m.end.x + step[0], m.end.y + step[1]];
234 if (
235 this.board[sq1[0]][sq1[1]] != V.EMPTY &&
236 this.getColor(sq1[0], sq1[1]) == oppCol
237 ) {
238 const piece1 = this.getPiece(sq1[0], sq1[1]);
239 if (!byChameleon || piece1 == V.PAWN) {
240 m.vanish.push(
241 new PiPo({
242 x: sq1[0],
243 y: sq1[1],
244 c: oppCol,
245 p: piece1
246 })
247 );
248 }
249 }
250 }
251 }
252 });
253 }
254
255 // "Pincer"
256 getPotentialPawnMoves([x, y]) {
257 let moves = super.getPotentialRookMoves([x, y]);
258 this.addPawnCaptures(moves);
259 return moves;
260 }
261
262 addRookCaptures(moves, byChameleon) {
263 const color = this.turn;
264 const oppCol = V.GetOppCol(color);
265 const kp = this.kingPos[color];
266 moves.forEach(m => {
267 // Check piece-king rectangle (if any) corners for enemy pieces
268 if (m.end.x == kp[0] || m.end.y == kp[1]) return; //"flat rectangle"
269 const corner1 = [m.end.x, kp[1]];
270 const corner2 = [kp[0], m.end.y];
271 for (let [i, j] of [corner1, corner2]) {
272 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == oppCol) {
273 const piece = this.getPiece(i, j);
274 if (!byChameleon || piece == V.ROOK) {
275 m.vanish.push(
276 new PiPo({
277 x: i,
278 y: j,
279 p: piece,
280 c: oppCol
281 })
282 );
283 }
284 }
285 }
286 });
287 }
288
289 // Coordinator
290 getPotentialRookMoves(sq) {
291 let moves = super.getPotentialQueenMoves(sq);
292 this.addRookCaptures(moves);
293 return moves;
294 }
295
296 getKnightCaptures(startSquare, byChameleon) {
297 // Look in every direction for captures
298 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
299 const color = this.turn;
300 const oppCol = V.GetOppCol(color);
301 let moves = [];
302 const [x, y] = [startSquare[0], startSquare[1]];
303 const piece = this.getPiece(x, y); //might be a chameleon!
304 outerLoop: for (let step of steps) {
305 let [i, j] = [x + step[0], y + step[1]];
306 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
307 i += step[0];
308 j += step[1];
309 }
310 if (
311 !V.OnBoard(i, j) ||
312 this.getColor(i, j) == color ||
313 (!!byChameleon && this.getPiece(i, j) != V.KNIGHT)
314 ) {
315 continue;
316 }
317 // last(thing), cur(thing) : stop if "cur" is our color,
318 // or beyond board limits, or if "last" isn't empty and cur neither.
319 // Otherwise, if cur is empty then add move until cur square;
320 // if cur is occupied then stop if !!byChameleon and the square not
321 // occupied by a leaper.
322 let last = [i, j];
323 let cur = [i + step[0], j + step[1]];
324 let vanished = [new PiPo({ x: x, y: y, c: color, p: piece })];
325 while (V.OnBoard(cur[0], cur[1])) {
326 if (this.board[last[0]][last[1]] != V.EMPTY) {
327 const oppPiece = this.getPiece(last[0], last[1]);
328 if (!!byChameleon && oppPiece != V.KNIGHT) continue outerLoop;
329 // Something to eat:
330 vanished.push(
331 new PiPo({ x: last[0], y: last[1], c: oppCol, p: oppPiece })
332 );
333 }
334 if (this.board[cur[0]][cur[1]] != V.EMPTY) {
335 if (
336 this.getColor(cur[0], cur[1]) == color ||
337 this.board[last[0]][last[1]] != V.EMPTY
338 ) {
339 //TODO: redundant test
340 continue outerLoop;
341 }
342 } else {
343 moves.push(
344 new Move({
345 appear: [new PiPo({ x: cur[0], y: cur[1], c: color, p: piece })],
346 vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
347 start: { x: x, y: y },
348 end: { x: cur[0], y: cur[1] }
349 })
350 );
351 }
352 last = [last[0] + step[0], last[1] + step[1]];
353 cur = [cur[0] + step[0], cur[1] + step[1]];
354 }
355 }
356 return moves;
357 }
358
359 // Long-leaper
360 getPotentialKnightMoves(sq) {
361 return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
362 }
363
364 // Chameleon
365 getPotentialBishopMoves([x, y]) {
366 let moves = super
367 .getPotentialQueenMoves([x, y])
368 .concat(this.getKnightCaptures([x, y], "asChameleon"))
369 // No "king capture" because king cannot remain under check
370 this.addPawnCaptures(moves, "asChameleon");
371 this.addRookCaptures(moves, "asChameleon");
372 this.addQueenCaptures(moves, "asChameleon");
373 // Manually add Guard and Mage captures (since cannot move like a Mage)
374 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => {
375 const [i, j] = [x + step[0], y + step[1]];
376 if (
377 V.OnBoard(i, j) &&
378 this.board[i][j] != V.EMPTY &&
379 this.canTake([x, y], [i, j]) &&
380 [V.GUARD, V.MAGE].includes(this.getPiece(i, j))
381 ) {
382 moves.push(this.getBasicMove([x, y], [i, j]));
383 }
384 });
385 // Post-processing: merge similar moves, concatenating vanish arrays
386 let mergedMoves = {};
387 moves.forEach(m => {
388 const key = m.end.x + V.size.x * m.end.y;
389 if (!mergedMoves[key]) mergedMoves[key] = m;
390 else {
391 for (let i = 1; i < m.vanish.length; i++)
392 mergedMoves[key].vanish.push(m.vanish[i]);
393 }
394 });
395 return Object.values(mergedMoves);
396 }
397
398 addQueenCaptures(moves, byChameleon) {
399 if (moves.length == 0) return;
400 const [x, y] = [moves[0].start.x, moves[0].start.y];
401 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
402 let capturingDirections = [];
403 const color = this.turn;
404 const oppCol = V.GetOppCol(color);
405 adjacentSteps.forEach(step => {
406 const [i, j] = [x + step[0], y + step[1]];
407 if (
408 V.OnBoard(i, j) &&
409 this.board[i][j] != V.EMPTY &&
410 this.getColor(i, j) == oppCol &&
411 (!byChameleon || this.getPiece(i, j) == V.QUEEN)
412 ) {
413 capturingDirections.push(step);
414 }
415 });
416 moves.forEach(m => {
417 const step = [
418 m.end.x != x ? (m.end.x - x) / Math.abs(m.end.x - x) : 0,
419 m.end.y != y ? (m.end.y - y) / Math.abs(m.end.y - y) : 0
420 ];
421 // TODO: this test should be done only once per direction
422 if (
423 capturingDirections.some(dir => {
424 return dir[0] == -step[0] && dir[1] == -step[1];
425 })
426 ) {
427 const [i, j] = [x - step[0], y - step[1]];
428 m.vanish.push(
429 new PiPo({
430 x: i,
431 y: j,
432 p: this.getPiece(i, j),
433 c: oppCol
434 })
435 );
436 }
437 });
438 }
439
440 // Withdrawer
441 getPotentialQueenMoves(sq) {
442 let moves = super.getPotentialQueenMoves(sq);
443 this.addQueenCaptures(moves);
444 return moves;
445 }
446
447 getPotentialImmobilizerMoves(sq) {
448 // Immobilizer doesn't capture
449 return super.getPotentialQueenMoves(sq);
450 }
451
452 getPotentialKingMoves(sq) {
453 return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
454 }
455
456 getPotentialGuardMoves(sq) {
457 return this.getSlideNJumpMoves(
458 sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
459 }
460
461 getNextMageSteps(step) {
462 if (step[0] == -1) {
463 if (step[1] == -1) return [[-1, 0], [0, -1]];
464 return [[-1, 0], [0, 1]];
465 }
466 if (step[1] == -1) return [[1, 0], [0, -1]];
467 return [[1, 0], [0, 1]];
468 }
469
470 getPotentialMageMoves([x, y]) {
471 const oppCol = V.GetOppCol(this.turn);
472 let moves = [];
473 for (let step of V.steps[V.BISHOP]) {
474 let [i, j] = [x + step[0], y + step[1]];
475 if (!V.OnBoard(i, j)) continue;
476 if (this.board[i][j] != V.EMPTY) {
477 if (this.getColor(i, j) == oppCol)
478 // Capture
479 moves.push(this.getBasicMove([x, y], [i, j]));
480 }
481 else {
482 moves.push(this.getBasicMove([x, y], [i, j]));
483 // Continue orthogonally:
484 const stepO = this.getNextMageSteps(step);
485 Array.prototype.push.apply(
486 moves,
487 this.getSlideNJumpMoves([i, j], stepO, null, [x, y])
488 );
489 }
490 }
491 return moves;
492 }
493
494 isAttacked(sq, color) {
495 return (
496 super.isAttacked(sq, color) ||
497 this.isAttackedByGuard(sq, color) ||
498 this.isAttackedByMage(sq, color)
499 );
500 }
501
502 isAttackedByPawn([x, y], color) {
503 // Square (x,y) must be surroundable by two enemy pieces,
504 // and one of them at least should be a pawn (moving).
505 const dirs = [
506 [1, 0],
507 [0, 1]
508 ];
509 const steps = V.steps[V.ROOK];
510 for (let dir of dirs) {
511 const [i1, j1] = [x - dir[0], y - dir[1]]; //"before"
512 const [i2, j2] = [x + dir[0], y + dir[1]]; //"after"
513 if (V.OnBoard(i1, j1) && V.OnBoard(i2, j2)) {
514 if (
515 (
516 this.board[i1][j1] != V.EMPTY &&
517 this.getColor(i1, j1) == color &&
518 this.board[i2][j2] == V.EMPTY
519 )
520 ||
521 (
522 this.board[i2][j2] != V.EMPTY &&
523 this.getColor(i2, j2) == color &&
524 this.board[i1][j1] == V.EMPTY
525 )
526 ) {
527 // Search a movable enemy pawn landing on the empty square
528 for (let step of steps) {
529 let [ii, jj] = this.board[i1][j1] == V.EMPTY ? [i1, j1] : [i2, j2];
530 let [i3, j3] = [ii + step[0], jj + step[1]];
531 while (V.OnBoard(i3, j3) && this.board[i3][j3] == V.EMPTY) {
532 i3 += step[0];
533 j3 += step[1];
534 }
535 if (
536 V.OnBoard(i3, j3) &&
537 this.getColor(i3, j3) == color &&
538 this.getPiece(i3, j3) == V.PAWN &&
539 !this.isImmobilized([i3, j3])
540 ) {
541 return true;
542 }
543 }
544 }
545 }
546 }
547 return false;
548 }
549
550 isAttackedByRook([x, y], color) {
551 // King must be on same column or row,
552 // and a rook should be able to reach a capturing square
553 const sameRow = x == this.kingPos[color][0];
554 const sameColumn = y == this.kingPos[color][1];
555 if (sameRow || sameColumn) {
556 // Look for the enemy rook (maximum 1)
557 for (let i = 0; i < V.size.x; i++) {
558 for (let j = 0; j < V.size.y; j++) {
559 if (
560 this.board[i][j] != V.EMPTY &&
561 this.getColor(i, j) == color &&
562 this.getPiece(i, j) == V.ROOK
563 ) {
564 if (this.isImmobilized([i, j]))
565 // Because only one rook:
566 return false;
567 // Can it reach a capturing square? Easy but quite suboptimal way
568 // (TODO: generate all moves (turn is OK))
569 const moves = this.getPotentialMovesFrom([i, j]);
570 for (let move of moves) {
571 if (
572 (sameRow && move.end.y == y) ||
573 (sameColumn && move.end.x == x)
574 ) {
575 return true;
576 }
577 }
578 }
579 }
580 }
581 }
582 return false;
583 }
584
585 isAttackedByKnight([x, y], color) {
586 // Square (x,y) must be on same line as a knight,
587 // and there must be empty square(s) behind.
588 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
589 outerLoop: for (let step of steps) {
590 const [i0, j0] = [x + step[0], y + step[1]];
591 if (V.OnBoard(i0, j0) && this.board[i0][j0] == V.EMPTY) {
592 // Try in opposite direction:
593 let [i, j] = [x - step[0], y - step[1]];
594 while (V.OnBoard(i, j)) {
595 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
596 i -= step[0];
597 j -= step[1];
598 }
599 if (V.OnBoard(i, j)) {
600 if (this.getColor(i, j) == color) {
601 if (
602 this.getPiece(i, j) == V.KNIGHT &&
603 !this.isImmobilized([i, j])
604 ) {
605 return true;
606 }
607 continue outerLoop;
608 }
609 // [else] Our color,
610 // could be captured *if there was an empty space*
611 if (this.board[i + step[0]][j + step[1]] != V.EMPTY)
612 continue outerLoop;
613 i -= step[0];
614 j -= step[1];
615 }
616 }
617 }
618 }
619 return false;
620 }
621
622 isAttackedByBishop([x, y], color) {
623 // We cheat a little here: since this function is used exclusively for
624 // the king, it's enough to check the immediate surrounding of the square.
625 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
626 for (let step of adjacentSteps) {
627 const [i, j] = [x + step[0], y + step[1]];
628 if (
629 V.OnBoard(i, j) &&
630 this.board[i][j] != V.EMPTY &&
631 this.getColor(i, j) == color &&
632 this.getPiece(i, j) == V.BISHOP &&
633 !this.isImmobilized([i, j])
634 ) {
635 return true;
636 }
637 }
638 return false;
639 }
640
641 isAttackedByQueen([x, y], color) {
642 // Square (x,y) must be adjacent to a queen, and the queen must have
643 // some free space in the opposite direction from (x,y)
644 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
645 for (let step of adjacentSteps) {
646 const sq2 = [x + 2 * step[0], y + 2 * step[1]];
647 if (V.OnBoard(sq2[0], sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY) {
648 const sq1 = [x + step[0], y + step[1]];
649 if (
650 this.board[sq1[0]][sq1[1]] != V.EMPTY &&
651 this.getColor(sq1[0], sq1[1]) == color &&
652 this.getPiece(sq1[0], sq1[1]) == V.QUEEN &&
653 !this.isImmobilized(sq1)
654 ) {
655 return true;
656 }
657 }
658 }
659 return false;
660 }
661
662 isAttackedByKing([x, y], color) {
663 for (let step of V.steps[V.KNIGHT]) {
664 let rx = x + step[0],
665 // Circular board for king-knight:
666 ry = (y + step[1]) % V.size.y;
667 if (
668 V.OnBoard(rx, ry) &&
669 this.getPiece(rx, ry) === V.KING &&
670 this.getColor(rx, ry) == color &&
671 !this.isImmobilized([rx, ry])
672 ) {
673 return true;
674 }
675 }
676 return false;
677 }
678
679 isAttackedByGuard(sq, color) {
680 return super.isAttackedBySlideNJump(
681 sq, color, V.GUARD, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1);
682 }
683
684 getNextMageCheck(step) {
685 if (step[0] == 0) {
686 if (step[1] == 1) return [[1, 1], [-1, 1]];
687 return [[-1, -1], [1, -1]];
688 }
689 if (step[0] == -1) return [[-1, -1], [-1, 1]];
690 return [[1, 1], [1, -1]];
691 }
692
693 isAttackedByMage([x, y], color) {
694 for (let step of V.steps[V.BISHOP]) {
695 const [i, j] = [x + step[0], y + step[1]];
696 if (
697 V.OnBoard(i, j) &&
698 this.board[i][j] != V.EMPTY &&
699 this.getColor(i, j) == color &&
700 this.getPiece(i, j) == V.MAGE
701 ) {
702 return true;
703 }
704 }
705 for (let step of V.steps[V.ROOK]) {
706 let [i, j] = [x + step[0], y + step[1]];
707 const stepM = this.getNextMageCheck(step);
708 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
709 for (let s of stepM) {
710 const [ii, jj] = [i + s[0], j + s[1]];
711 if (
712 V.OnBoard(ii, jj) &&
713 this.board[ii][jj] != V.EMPTY &&
714 this.getColor(ii, jj) == color &&
715 this.getPiece(ii, jj) == V.MAGE
716 ) {
717 return true;
718 }
719 }
720 i += step[0];
721 j += step[1];
722 }
723 }
724 return false;
725 }
726
727 getCurrentScore() {
728 const color = this.turn;
729 const getScoreLost = () => {
730 // Result if I lose:
731 return color == "w" ? "0-1" : "1-0";
732 };
733 if (!this.atLeastOneMove()) {
734 // No valid move: I lose or draw
735 if (this.underCheck(color)) return getScoreLost();
736 return "1/2";
737 }
738 // I lose also if no pieces left (except king)
739 let piecesLeft = 0;
740 outerLoop: for (let i=0; i<V.size.x; i++) {
741 for (let j=0; j<V.size.y; j++) {
742 if (
743 this.board[i][j] != V.EMPTY &&
744 this.getColor(i, j) == color &&
745 this.getPiece(i,j) != V.KING
746 ) {
747 piecesLeft++;
748 }
749 }
750 }
751 if (piecesLeft == 0) return getScoreLost();
752 // Check if my palace is invaded:
753 const pX = (color == 'w' ? 10 : 0);
754 const oppCol = V.GetOppCol(color);
755 if (
756 this.board[pX][3] != V.EMPTY &&
757 this.getColor(pX, 3) == oppCol &&
758 this.board[pX][4] != V.EMPTY &&
759 this.getColor(pX, 4) == oppCol
760 ) {
761 return getScoreLost();
762 }
763 return "*";
764 }
765
766 static GenRandInitFen() {
767 // Always deterministic:
768 return (
769 "xxx2xxx/1g1qk1g1/1bnmrnb1/dppppppd/8/8/8/" +
770 "DPPPPPPD/1BNMRNB1/1G1QK1G1/xxx2xxx w 0"
771 );
772 }
773
774 static get VALUES() {
775 return {
776 p: 1,
777 r: 2,
778 n: 5,
779 b: 4,
780 q: 2,
781 m: 5,
782 g: 7,
783 d: 4,
784 k: 1000
785 };
786 }
787
788 static get SEARCH_DEPTH() {
789 return 2;
790 }
791
792 evalPosition() {
793 let evaluation = 0;
794 for (let i = 0; i < V.size.x; i++) {
795 for (let j = 0; j < V.size.y; j++) {
796 if (![V.EMPTY,V.NOTHING].includes(this.board[i][j])) {
797 const sign = this.getColor(i, j) == "w" ? 1 : -1;
798 evaluation += sign * V.VALUES[this.getPiece(i, j)];
799 }
800 }
801 }
802 return evaluation;
803 }
804
805 getNotation(move) {
806 const initialSquare = V.CoordsToSquare(move.start);
807 const finalSquare = V.CoordsToSquare(move.end);
808 if (move.appear.length == 0)
809 // Suicide 'S'
810 return initialSquare + "S";
811 let notation = undefined;
812 if (move.appear[0].p == V.PAWN) {
813 // Pawn: generally ambiguous short notation, so we use full description
814 notation = "P" + initialSquare + finalSquare;
815 } else if (move.appear[0].p == V.KING)
816 notation = "K" + (move.vanish.length > 1 ? "x" : "") + finalSquare;
817 else notation = move.appear[0].p.toUpperCase() + finalSquare;
818 // Add a capture mark (not describing what is captured...):
819 if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X";
820 return notation;
821 }
822
823 };