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