Fix and describe Maxima rules
[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.
38408f63 180 const pY = (this.board[pX][3] != V.EMPTY ? 4 : 3);
e90bafa8
BA
181 return moves.filter(m => m.end.x != pX || m.end.y != pY);
182 }
183
38408f63 184 getSlideNJumpMoves([x, y], steps, oneStep, mageInitSquare) {
e90bafa8
BA
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) {
38408f63 193 moves.push(this.getBasicMove(initSquare, [i, j]));
e90bafa8
BA
194 if (!!oneStep) continue outerLoop;
195 i += step[0];
196 j += step[1];
197 }
38408f63 198 // Only king, guard and mage (+ chameleon) can take on occupied square:
e90bafa8 199 if (
38408f63
BA
200 V.OnBoard(i, j) &&
201 [V.KING, V.GUARD, V.MAGE].includes(piece) &&
e90bafa8 202 this.canTake(initSquare, [i, j])
e90bafa8
BA
203 ) {
204 moves.push(this.getBasicMove(initSquare, [i, j]));
205 }
206 }
207 return moves;
208 }
209
210 // Modify capturing moves among listed pawn moves
211 addPawnCaptures(moves, byChameleon) {
212 const steps = V.steps[V.ROOK];
213 const color = this.turn;
214 const oppCol = V.GetOppCol(color);
215 moves.forEach(m => {
216 if (!!byChameleon && m.start.x != m.end.x && m.start.y != m.end.y)
217 // Chameleon not moving as pawn
218 return;
219 // Try capturing in every direction
220 for (let step of steps) {
221 const sq2 = [m.end.x + 2 * step[0], m.end.y + 2 * step[1]];
222 if (
223 V.OnBoard(sq2[0], sq2[1]) &&
224 this.board[sq2[0]][sq2[1]] != V.EMPTY &&
225 this.getColor(sq2[0], sq2[1]) == color
226 ) {
227 // Potential capture
228 const sq1 = [m.end.x + step[0], m.end.y + step[1]];
229 if (
230 this.board[sq1[0]][sq1[1]] != V.EMPTY &&
231 this.getColor(sq1[0], sq1[1]) == oppCol
232 ) {
233 const piece1 = this.getPiece(sq1[0], sq1[1]);
234 if (!byChameleon || piece1 == V.PAWN) {
235 m.vanish.push(
236 new PiPo({
237 x: sq1[0],
238 y: sq1[1],
239 c: oppCol,
240 p: piece1
241 })
242 );
243 }
244 }
245 }
246 }
247 });
248 }
249
250 // "Pincer"
251 getPotentialPawnMoves([x, y]) {
252 let moves = super.getPotentialRookMoves([x, y]);
253 this.addPawnCaptures(moves);
254 return moves;
255 }
256
257 addRookCaptures(moves, byChameleon) {
258 const color = this.turn;
259 const oppCol = V.GetOppCol(color);
260 const kp = this.kingPos[color];
261 moves.forEach(m => {
262 // Check piece-king rectangle (if any) corners for enemy pieces
263 if (m.end.x == kp[0] || m.end.y == kp[1]) return; //"flat rectangle"
264 const corner1 = [m.end.x, kp[1]];
265 const corner2 = [kp[0], m.end.y];
266 for (let [i, j] of [corner1, corner2]) {
267 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == oppCol) {
268 const piece = this.getPiece(i, j);
269 if (!byChameleon || piece == V.ROOK) {
270 m.vanish.push(
271 new PiPo({
272 x: i,
273 y: j,
274 p: piece,
275 c: oppCol
276 })
277 );
278 }
279 }
280 }
281 });
282 }
283
284 // Coordinator
285 getPotentialRookMoves(sq) {
286 let moves = super.getPotentialQueenMoves(sq);
287 this.addRookCaptures(moves);
288 return moves;
289 }
290
291 getKnightCaptures(startSquare, byChameleon) {
292 // Look in every direction for captures
293 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
294 const color = this.turn;
295 const oppCol = V.GetOppCol(color);
296 let moves = [];
297 const [x, y] = [startSquare[0], startSquare[1]];
298 const piece = this.getPiece(x, y); //might be a chameleon!
299 outerLoop: for (let step of steps) {
300 let [i, j] = [x + step[0], y + step[1]];
301 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
302 i += step[0];
303 j += step[1];
304 }
305 if (
306 !V.OnBoard(i, j) ||
307 this.getColor(i, j) == color ||
308 (!!byChameleon && this.getPiece(i, j) != V.KNIGHT)
309 ) {
310 continue;
311 }
312 // last(thing), cur(thing) : stop if "cur" is our color,
313 // or beyond board limits, or if "last" isn't empty and cur neither.
314 // Otherwise, if cur is empty then add move until cur square;
315 // if cur is occupied then stop if !!byChameleon and the square not
316 // occupied by a leaper.
317 let last = [i, j];
318 let cur = [i + step[0], j + step[1]];
319 let vanished = [new PiPo({ x: x, y: y, c: color, p: piece })];
320 while (V.OnBoard(cur[0], cur[1])) {
321 if (this.board[last[0]][last[1]] != V.EMPTY) {
322 const oppPiece = this.getPiece(last[0], last[1]);
323 if (!!byChameleon && oppPiece != V.KNIGHT) continue outerLoop;
324 // Something to eat:
325 vanished.push(
326 new PiPo({ x: last[0], y: last[1], c: oppCol, p: oppPiece })
327 );
328 }
329 if (this.board[cur[0]][cur[1]] != V.EMPTY) {
330 if (
331 this.getColor(cur[0], cur[1]) == color ||
332 this.board[last[0]][last[1]] != V.EMPTY
333 ) {
334 //TODO: redundant test
335 continue outerLoop;
336 }
337 } else {
338 moves.push(
339 new Move({
340 appear: [new PiPo({ x: cur[0], y: cur[1], c: color, p: piece })],
341 vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
342 start: { x: x, y: y },
343 end: { x: cur[0], y: cur[1] }
344 })
345 );
346 }
347 last = [last[0] + step[0], last[1] + step[1]];
348 cur = [cur[0] + step[0], cur[1] + step[1]];
349 }
350 }
351 return moves;
352 }
353
354 // Long-leaper
355 getPotentialKnightMoves(sq) {
356 return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
357 }
358
359 // Chameleon
360 getPotentialBishopMoves([x, y]) {
361 let moves = super
362 .getPotentialQueenMoves([x, y])
363 .concat(this.getKnightCaptures([x, y], "asChameleon"))
e90bafa8
BA
364 // No "king capture" because king cannot remain under check
365 this.addPawnCaptures(moves, "asChameleon");
366 this.addRookCaptures(moves, "asChameleon");
367 this.addQueenCaptures(moves, "asChameleon");
38408f63
BA
368 // Manually add Guard and Mage captures (since cannot move like a Mage)
369 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => {
370 const [i, j] = [x + step[0], y + step[1]];
371 if (
372 V.OnBoard(i, j) &&
373 this.board[i][j] != V.EMPTY &&
374 this.canTake([x, y], [i, j]) &&
375 [V.GUARD, V.MAGE].includes(this.getPiece(i, j))
376 ) {
377 moves.push(this.getBasicMove([x, y], [i, j]));
378 }
379 });
e90bafa8
BA
380 // Post-processing: merge similar moves, concatenating vanish arrays
381 let mergedMoves = {};
382 moves.forEach(m => {
383 const key = m.end.x + V.size.x * m.end.y;
384 if (!mergedMoves[key]) mergedMoves[key] = m;
385 else {
386 for (let i = 1; i < m.vanish.length; i++)
387 mergedMoves[key].vanish.push(m.vanish[i]);
388 }
389 });
390 return Object.values(mergedMoves);
391 }
392
393 addQueenCaptures(moves, byChameleon) {
394 if (moves.length == 0) return;
395 const [x, y] = [moves[0].start.x, moves[0].start.y];
396 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
397 let capturingDirections = [];
398 const color = this.turn;
399 const oppCol = V.GetOppCol(color);
400 adjacentSteps.forEach(step => {
401 const [i, j] = [x + step[0], y + step[1]];
402 if (
403 V.OnBoard(i, j) &&
404 this.board[i][j] != V.EMPTY &&
405 this.getColor(i, j) == oppCol &&
406 (!byChameleon || this.getPiece(i, j) == V.QUEEN)
407 ) {
408 capturingDirections.push(step);
409 }
410 });
411 moves.forEach(m => {
412 const step = [
413 m.end.x != x ? (m.end.x - x) / Math.abs(m.end.x - x) : 0,
414 m.end.y != y ? (m.end.y - y) / Math.abs(m.end.y - y) : 0
415 ];
416 // TODO: this test should be done only once per direction
417 if (
418 capturingDirections.some(dir => {
419 return dir[0] == -step[0] && dir[1] == -step[1];
420 })
421 ) {
422 const [i, j] = [x - step[0], y - step[1]];
423 m.vanish.push(
424 new PiPo({
425 x: i,
426 y: j,
427 p: this.getPiece(i, j),
428 c: oppCol
429 })
430 );
431 }
432 });
433 }
434
435 // Withdrawer
436 getPotentialQueenMoves(sq) {
437 let moves = super.getPotentialQueenMoves(sq);
438 this.addQueenCaptures(moves);
439 return moves;
440 }
441
442 getPotentialImmobilizerMoves(sq) {
443 // Immobilizer doesn't capture
444 return super.getPotentialQueenMoves(sq);
445 }
446
447 getPotentialKingMoves(sq) {
448 return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
449 }
450
38408f63 451 getPotentialGuardMoves(sq) {
e90bafa8
BA
452 return (
453 this.getSlideNJumpMoves(
454 sq,
455 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
456 "oneStep",
38408f63 457 null
e90bafa8
BA
458 )
459 );
460 }
461
462 getNextMageSteps(step) {
463 if (step[0] == -1) {
464 if (step[1] == -1) return [[-1, 0], [0, -1]];
465 return [[-1, 0], [0, 1]];
466 }
467 if (step[1] == -1) return [[1, 0], [0, -1]];
468 return [[1, 0], [0, 1]];
469 }
470
38408f63 471 getPotentialMageMoves([x, y]) {
e90bafa8 472 const oppCol = V.GetOppCol(this.turn);
e90bafa8
BA
473 let moves = [];
474 for (let step of V.steps[V.BISHOP]) {
475 let [i, j] = [x + step[0], y + step[1]];
476 if (!V.OnBoard(i, j)) continue;
477 if (this.board[i][j] != V.EMPTY) {
38408f63 478 if (this.getColor(i, j) == oppCol)
e90bafa8
BA
479 // Capture
480 moves.push(this.getBasicMove([x, y], [i, j]));
e90bafa8
BA
481 }
482 else {
38408f63 483 moves.push(this.getBasicMove([x, y], [i, j]));
e90bafa8
BA
484 // Continue orthogonally:
485 const stepO = this.getNextMageSteps(step);
486 Array.prototype.push.apply(
487 moves,
38408f63 488 this.getSlideNJumpMoves([i, j], stepO, null, [x, y])
e90bafa8
BA
489 );
490 }
491 }
492 return moves;
493 }
494
495 isAttacked(sq, color) {
496 return (
497 super.isAttacked(sq, color) ||
498 this.isAttackedByGuard(sq, color) ||
499 this.isAttackedByMage(sq, color)
500 );
501 }
502
503 isAttackedByPawn([x, y], color) {
504 // Square (x,y) must be surroundable by two enemy pieces,
505 // and one of them at least should be a pawn (moving).
506 const dirs = [
507 [1, 0],
508 [0, 1]
509 ];
510 const steps = V.steps[V.ROOK];
511 for (let dir of dirs) {
512 const [i1, j1] = [x - dir[0], y - dir[1]]; //"before"
513 const [i2, j2] = [x + dir[0], y + dir[1]]; //"after"
514 if (V.OnBoard(i1, j1) && V.OnBoard(i2, j2)) {
515 if (
516 (
517 this.board[i1][j1] != V.EMPTY &&
518 this.getColor(i1, j1) == color &&
519 this.board[i2][j2] == V.EMPTY
520 )
521 ||
522 (
523 this.board[i2][j2] != V.EMPTY &&
524 this.getColor(i2, j2) == color &&
525 this.board[i1][j1] == V.EMPTY
526 )
527 ) {
528 // Search a movable enemy pawn landing on the empty square
529 for (let step of steps) {
530 let [ii, jj] = this.board[i1][j1] == V.EMPTY ? [i1, j1] : [i2, j2];
531 let [i3, j3] = [ii + step[0], jj + step[1]];
532 while (V.OnBoard(i3, j3) && this.board[i3][j3] == V.EMPTY) {
533 i3 += step[0];
534 j3 += step[1];
535 }
536 if (
537 V.OnBoard(i3, j3) &&
538 this.getColor(i3, j3) == color &&
539 this.getPiece(i3, j3) == V.PAWN &&
540 !this.isImmobilized([i3, j3])
541 ) {
542 return true;
543 }
544 }
545 }
546 }
547 }
548 return false;
549 }
550
551 isAttackedByRook([x, y], color) {
552 // King must be on same column or row,
553 // and a rook should be able to reach a capturing square
554 const sameRow = x == this.kingPos[color][0];
555 const sameColumn = y == this.kingPos[color][1];
556 if (sameRow || sameColumn) {
557 // Look for the enemy rook (maximum 1)
558 for (let i = 0; i < V.size.x; i++) {
559 for (let j = 0; j < V.size.y; j++) {
560 if (
561 this.board[i][j] != V.EMPTY &&
562 this.getColor(i, j) == color &&
563 this.getPiece(i, j) == V.ROOK
564 ) {
565 if (this.isImmobilized([i, j]))
566 // Because only one rook:
567 return false;
568 // Can it reach a capturing square? Easy but quite suboptimal way
569 // (TODO: generate all moves (turn is OK))
570 const moves = this.getPotentialMovesFrom([i, j]);
571 for (let move of moves) {
572 if (
573 (sameRow && move.end.y == y) ||
574 (sameColumn && move.end.x == x)
575 ) {
576 return true;
577 }
578 }
579 }
580 }
581 }
582 }
583 return false;
584 }
585
586 isAttackedByKnight([x, y], color) {
587 // Square (x,y) must be on same line as a knight,
588 // and there must be empty square(s) behind.
589 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
590 outerLoop: for (let step of steps) {
591 const [i0, j0] = [x + step[0], y + step[1]];
592 if (V.OnBoard(i0, j0) && this.board[i0][j0] == V.EMPTY) {
593 // Try in opposite direction:
594 let [i, j] = [x - step[0], y - step[1]];
595 while (V.OnBoard(i, j)) {
596 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
597 i -= step[0];
598 j -= step[1];
599 }
600 if (V.OnBoard(i, j)) {
601 if (this.getColor(i, j) == color) {
602 if (
603 this.getPiece(i, j) == V.KNIGHT &&
604 !this.isImmobilized([i, j])
605 ) {
606 return true;
607 }
608 continue outerLoop;
609 }
610 // [else] Our color,
611 // could be captured *if there was an empty space*
612 if (this.board[i + step[0]][j + step[1]] != V.EMPTY)
613 continue outerLoop;
614 i -= step[0];
615 j -= step[1];
616 }
617 }
618 }
619 }
620 return false;
621 }
622
623 isAttackedByBishop([x, y], color) {
624 // We cheat a little here: since this function is used exclusively for
625 // the king, it's enough to check the immediate surrounding of the square.
626 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
627 for (let step of adjacentSteps) {
628 const [i, j] = [x + step[0], y + step[1]];
629 if (
630 V.OnBoard(i, j) &&
631 this.board[i][j] != V.EMPTY &&
632 this.getColor(i, j) == color &&
633 this.getPiece(i, j) == V.BISHOP &&
634 !this.isImmobilized([i, j])
635 ) {
636 return true;
637 }
638 }
639 return false;
640 }
641
642 isAttackedByQueen([x, y], color) {
643 // Square (x,y) must be adjacent to a queen, and the queen must have
644 // some free space in the opposite direction from (x,y)
645 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
646 for (let step of adjacentSteps) {
647 const sq2 = [x + 2 * step[0], y + 2 * step[1]];
648 if (V.OnBoard(sq2[0], sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY) {
649 const sq1 = [x + step[0], y + step[1]];
650 if (
651 this.board[sq1[0]][sq1[1]] != V.EMPTY &&
652 this.getColor(sq1[0], sq1[1]) == color &&
653 this.getPiece(sq1[0], sq1[1]) == V.QUEEN &&
654 !this.isImmobilized(sq1)
655 ) {
656 return true;
657 }
658 }
659 }
660 return false;
661 }
662
663 isAttackedByKing([x, y], color) {
664 for (let step of V.steps[V.KNIGHT]) {
665 let rx = x + step[0],
666 // Circular board for king-knight:
667 ry = (y + step[1]) % V.size.y;
668 if (
669 V.OnBoard(rx, ry) &&
670 this.getPiece(rx, ry) === V.KING &&
671 this.getColor(rx, ry) == color &&
672 !this.isImmobilized([rx, ry])
673 ) {
674 return true;
675 }
676 }
677 return false;
678 }
679
680 isAttackedByGuard(sq, color) {
681 return (
682 super.isAttackedBySlideNJump(
683 sq,
684 color,
685 V.GUARD,
686 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
687 "oneStep"
688 )
689 );
690 }
691
692 getNextMageCheck(step) {
693 if (step[0] == 0) {
694 if (step[1] == 1) return [[1, 1], [-1, 1]];
695 return [[-1, -1], [1, -1]];
696 }
697 if (step[0] == -1) return [[-1, -1], [-1, 1]];
698 return [[1, 1], [1, -1]];
699 }
700
701 isAttackedByMage([x, y], color) {
702 for (let step of V.steps[V.BISHOP]) {
703 const [i, j] = [x + step[0], y + step[1]];
704 if (
705 V.OnBoard(i, j) &&
706 this.board[i][j] != V.EMPTY &&
707 this.getColor(i, j) == color &&
708 this.getPiece(i, j) == V.MAGE
709 ) {
710 return true;
711 }
712 }
713 for (let step of V.steps[V.ROOK]) {
714 let [i, j] = [x + step[0], y + step[1]];
715 const stepM = this.getNextMageCheck(step);
716 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
717 for (let s of stepM) {
718 const [ii, jj] = [i + s[0], j + s[1]];
719 if (
720 V.OnBoard(ii, jj) &&
721 this.board[ii][jj] != V.EMPTY &&
722 this.getColor(ii, jj) == color &&
723 this.getPiece(ii, jj) == V.MAGE
724 ) {
725 return true;
726 }
727 }
728 i += step[0];
729 j += step[1];
730 }
731 }
732 return false;
733 }
734
735 getCurrentScore() {
736 const color = this.turn;
737 const getScoreLost = () => {
738 // Result if I lose:
739 return color == "w" ? "0-1" : "1-0";
740 };
741 if (!this.atLeastOneMove()) {
742 // No valid move: I lose or draw
743 if (this.underCheck(color)) return getScoreLost();
744 return "1/2";
745 }
746 // I lose also if no pieces left (except king)
747 let piecesLeft = 0;
748 outerLoop: for (let i=0; i<V.size.x; i++) {
749 for (let j=0; j<V.size.y; j++) {
750 if (
751 this.board[i][j] != V.EMPTY &&
752 this.getColor(i, j) == color &&
753 this.getPiece(i,j) != V.KING
754 ) {
755 piecesLeft++;
756 }
757 }
758 }
759 if (piecesLeft == 0) return getScoreLost();
760 // Check if my palace is invaded:
761 const pX = (color == 'w' ? 10 : 0);
762 const oppCol = V.GetOppCol(color);
763 if (
764 this.board[pX][3] != V.EMPTY &&
765 this.getColor(pX, 3) == oppCol &&
766 this.board[pX][4] != V.EMPTY &&
767 this.getColor(pX, 4) == oppCol
768 ) {
769 return getScoreLost();
770 }
771 return "*";
772 }
773
774 static GenRandInitFen() {
775 // Always deterministic:
776 return (
777 "xxx2xxx/1g1qk1g1/1bnmrnb1/dppppppd/8/8/8/" +
778 "DPPPPPPD/1BNMRNB1/1G1QK1G1/xxx2xxx w 0"
779 );
780 }
781
782 static get VALUES() {
783 return {
784 p: 1,
785 r: 2,
786 n: 5,
787 b: 4,
788 q: 2,
789 m: 5,
790 g: 7,
791 d: 4,
792 k: 1000
793 };
794 }
795
796 static get SEARCH_DEPTH() {
797 return 2;
798 }
799
800 evalPosition() {
801 let evaluation = 0;
802 for (let i = 0; i < V.size.x; i++) {
803 for (let j = 0; j < V.size.y; j++) {
804 if (![V.EMPTY,V.NOTHING].includes(this.board[i][j])) {
805 const sign = this.getColor(i, j) == "w" ? 1 : -1;
806 evaluation += sign * V.VALUES[this.getPiece(i, j)];
807 }
808 }
809 }
810 return evaluation;
811 }
812
813 getNotation(move) {
814 const initialSquare = V.CoordsToSquare(move.start);
815 const finalSquare = V.CoordsToSquare(move.end);
816 if (move.appear.length == 0)
817 // Suicide 'S'
818 return initialSquare + "S";
819 let notation = undefined;
820 if (move.appear[0].p == V.PAWN) {
821 // Pawn: generally ambiguous short notation, so we use full description
822 notation = "P" + initialSquare + finalSquare;
823 } else if (move.appear[0].p == V.KING)
824 notation = "K" + (move.vanish.length > 1 ? "x" : "") + finalSquare;
825 else notation = move.appear[0].p.toUpperCase() + finalSquare;
826 // Add a capture mark (not describing what is captured...):
827 if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X";
828 return notation;
829 }
830};