Fix Maxima (immobilize kings too)
[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 {
7e8a7ea1 6
4313762d
BA
7 static get Options() {
8 return null;
9 }
10
e90bafa8
BA
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 {
e50a8025 57 const num = parseInt(row[i], 10);
e90bafa8
BA
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: {
e50a8025 84 const num = parseInt(position[i].charAt(j), 10);
e90bafa8
BA
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);
cc0f1cbf
BA
148 if (!!imSq) {
149 if (piece == V.KING) return [];
150 // Only option is suicide
e90bafa8
BA
151 return [
152 new Move({
153 start: { x: x, y: y },
154 end: { x: imSq[0], y: imSq[1] },
155 appear: [],
156 vanish: [
157 new PiPo({
158 x: x,
159 y: y,
160 c: this.getColor(x, y),
161 p: this.getPiece(x, y)
162 })
163 ]
164 })
165 ];
166 }
167 let moves = undefined;
168 switch (piece) {
169 case V.IMMOBILIZER:
170 moves = this.getPotentialImmobilizerMoves([x, y]);
171 break;
172 case V.GUARD:
173 moves = this.getPotentialGuardMoves([x, y]);
174 break;
175 case V.MAGE:
176 moves = this.getPotentialMageMoves([x, y]);
177 break;
178 default:
179 moves = super.getPotentialMovesFrom([x, y]);
180 }
181 const pX = (this.turn == 'w' ? 10 : 0);
182 if (this.board[pX][3] == V.EMPTY && this.board[pX][4] == V.EMPTY)
183 return moves;
184 // Filter out moves resulting in self palace occupation:
185 // NOTE: cannot invade own palace but still check the king there.
38408f63 186 const pY = (this.board[pX][3] != V.EMPTY ? 4 : 3);
e90bafa8
BA
187 return moves.filter(m => m.end.x != pX || m.end.y != pY);
188 }
189
38408f63 190 getSlideNJumpMoves([x, y], steps, oneStep, mageInitSquare) {
e90bafa8
BA
191 const piece = !mageInitSquare ? this.getPiece(x, y) : V.MAGE;
192 const initSquare = mageInitSquare || [x, y];
193 let moves = [];
194 outerLoop: for (let step of steps) {
195 let i = x + step[0];
196 let j = y + step[1];
197 if (piece == V.KING) j = j % V.size.y;
198 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
38408f63 199 moves.push(this.getBasicMove(initSquare, [i, j]));
4313762d 200 if (oneStep) continue outerLoop;
e90bafa8
BA
201 i += step[0];
202 j += step[1];
203 }
38408f63 204 // Only king, guard and mage (+ chameleon) can take on occupied square:
e90bafa8 205 if (
38408f63
BA
206 V.OnBoard(i, j) &&
207 [V.KING, V.GUARD, V.MAGE].includes(piece) &&
e90bafa8 208 this.canTake(initSquare, [i, j])
e90bafa8
BA
209 ) {
210 moves.push(this.getBasicMove(initSquare, [i, j]));
211 }
212 }
213 return moves;
214 }
215
216 // Modify capturing moves among listed pawn moves
217 addPawnCaptures(moves, byChameleon) {
218 const steps = V.steps[V.ROOK];
219 const color = this.turn;
220 const oppCol = V.GetOppCol(color);
221 moves.forEach(m => {
222 if (!!byChameleon && m.start.x != m.end.x && m.start.y != m.end.y)
223 // Chameleon not moving as pawn
224 return;
225 // Try capturing in every direction
226 for (let step of steps) {
227 const sq2 = [m.end.x + 2 * step[0], m.end.y + 2 * step[1]];
228 if (
229 V.OnBoard(sq2[0], sq2[1]) &&
230 this.board[sq2[0]][sq2[1]] != V.EMPTY &&
231 this.getColor(sq2[0], sq2[1]) == color
232 ) {
233 // Potential capture
234 const sq1 = [m.end.x + step[0], m.end.y + step[1]];
235 if (
236 this.board[sq1[0]][sq1[1]] != V.EMPTY &&
237 this.getColor(sq1[0], sq1[1]) == oppCol
238 ) {
239 const piece1 = this.getPiece(sq1[0], sq1[1]);
240 if (!byChameleon || piece1 == V.PAWN) {
241 m.vanish.push(
242 new PiPo({
243 x: sq1[0],
244 y: sq1[1],
245 c: oppCol,
246 p: piece1
247 })
248 );
249 }
250 }
251 }
252 }
253 });
254 }
255
256 // "Pincer"
257 getPotentialPawnMoves([x, y]) {
258 let moves = super.getPotentialRookMoves([x, y]);
259 this.addPawnCaptures(moves);
260 return moves;
261 }
262
263 addRookCaptures(moves, byChameleon) {
264 const color = this.turn;
265 const oppCol = V.GetOppCol(color);
266 const kp = this.kingPos[color];
267 moves.forEach(m => {
268 // Check piece-king rectangle (if any) corners for enemy pieces
269 if (m.end.x == kp[0] || m.end.y == kp[1]) return; //"flat rectangle"
270 const corner1 = [m.end.x, kp[1]];
271 const corner2 = [kp[0], m.end.y];
272 for (let [i, j] of [corner1, corner2]) {
273 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == oppCol) {
274 const piece = this.getPiece(i, j);
275 if (!byChameleon || piece == V.ROOK) {
276 m.vanish.push(
277 new PiPo({
278 x: i,
279 y: j,
280 p: piece,
281 c: oppCol
282 })
283 );
284 }
285 }
286 }
287 });
288 }
289
290 // Coordinator
291 getPotentialRookMoves(sq) {
292 let moves = super.getPotentialQueenMoves(sq);
293 this.addRookCaptures(moves);
294 return moves;
295 }
296
297 getKnightCaptures(startSquare, byChameleon) {
298 // Look in every direction for captures
299 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
300 const color = this.turn;
301 const oppCol = V.GetOppCol(color);
302 let moves = [];
303 const [x, y] = [startSquare[0], startSquare[1]];
304 const piece = this.getPiece(x, y); //might be a chameleon!
305 outerLoop: for (let step of steps) {
306 let [i, j] = [x + step[0], y + step[1]];
307 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
308 i += step[0];
309 j += step[1];
310 }
311 if (
312 !V.OnBoard(i, j) ||
313 this.getColor(i, j) == color ||
314 (!!byChameleon && this.getPiece(i, j) != V.KNIGHT)
315 ) {
316 continue;
317 }
318 // last(thing), cur(thing) : stop if "cur" is our color,
319 // or beyond board limits, or if "last" isn't empty and cur neither.
320 // Otherwise, if cur is empty then add move until cur square;
321 // if cur is occupied then stop if !!byChameleon and the square not
322 // occupied by a leaper.
323 let last = [i, j];
324 let cur = [i + step[0], j + step[1]];
325 let vanished = [new PiPo({ x: x, y: y, c: color, p: piece })];
326 while (V.OnBoard(cur[0], cur[1])) {
327 if (this.board[last[0]][last[1]] != V.EMPTY) {
328 const oppPiece = this.getPiece(last[0], last[1]);
329 if (!!byChameleon && oppPiece != V.KNIGHT) continue outerLoop;
330 // Something to eat:
331 vanished.push(
332 new PiPo({ x: last[0], y: last[1], c: oppCol, p: oppPiece })
333 );
334 }
335 if (this.board[cur[0]][cur[1]] != V.EMPTY) {
336 if (
337 this.getColor(cur[0], cur[1]) == color ||
338 this.board[last[0]][last[1]] != V.EMPTY
339 ) {
340 //TODO: redundant test
341 continue outerLoop;
342 }
343 } else {
344 moves.push(
345 new Move({
346 appear: [new PiPo({ x: cur[0], y: cur[1], c: color, p: piece })],
347 vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
348 start: { x: x, y: y },
349 end: { x: cur[0], y: cur[1] }
350 })
351 );
352 }
353 last = [last[0] + step[0], last[1] + step[1]];
354 cur = [cur[0] + step[0], cur[1] + step[1]];
355 }
356 }
357 return moves;
358 }
359
360 // Long-leaper
361 getPotentialKnightMoves(sq) {
362 return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
363 }
364
365 // Chameleon
366 getPotentialBishopMoves([x, y]) {
367 let moves = super
368 .getPotentialQueenMoves([x, y])
369 .concat(this.getKnightCaptures([x, y], "asChameleon"))
e90bafa8
BA
370 // No "king capture" because king cannot remain under check
371 this.addPawnCaptures(moves, "asChameleon");
372 this.addRookCaptures(moves, "asChameleon");
373 this.addQueenCaptures(moves, "asChameleon");
38408f63
BA
374 // Manually add Guard and Mage captures (since cannot move like a Mage)
375 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => {
376 const [i, j] = [x + step[0], y + step[1]];
377 if (
378 V.OnBoard(i, j) &&
379 this.board[i][j] != V.EMPTY &&
380 this.canTake([x, y], [i, j]) &&
381 [V.GUARD, V.MAGE].includes(this.getPiece(i, j))
382 ) {
383 moves.push(this.getBasicMove([x, y], [i, j]));
384 }
385 });
e90bafa8
BA
386 // Post-processing: merge similar moves, concatenating vanish arrays
387 let mergedMoves = {};
388 moves.forEach(m => {
389 const key = m.end.x + V.size.x * m.end.y;
390 if (!mergedMoves[key]) mergedMoves[key] = m;
391 else {
392 for (let i = 1; i < m.vanish.length; i++)
393 mergedMoves[key].vanish.push(m.vanish[i]);
394 }
395 });
396 return Object.values(mergedMoves);
397 }
398
399 addQueenCaptures(moves, byChameleon) {
400 if (moves.length == 0) return;
401 const [x, y] = [moves[0].start.x, moves[0].start.y];
402 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
403 let capturingDirections = [];
404 const color = this.turn;
405 const oppCol = V.GetOppCol(color);
406 adjacentSteps.forEach(step => {
407 const [i, j] = [x + step[0], y + step[1]];
408 if (
409 V.OnBoard(i, j) &&
410 this.board[i][j] != V.EMPTY &&
411 this.getColor(i, j) == oppCol &&
412 (!byChameleon || this.getPiece(i, j) == V.QUEEN)
413 ) {
414 capturingDirections.push(step);
415 }
416 });
417 moves.forEach(m => {
418 const step = [
419 m.end.x != x ? (m.end.x - x) / Math.abs(m.end.x - x) : 0,
420 m.end.y != y ? (m.end.y - y) / Math.abs(m.end.y - y) : 0
421 ];
422 // TODO: this test should be done only once per direction
423 if (
424 capturingDirections.some(dir => {
425 return dir[0] == -step[0] && dir[1] == -step[1];
426 })
427 ) {
428 const [i, j] = [x - step[0], y - step[1]];
429 m.vanish.push(
430 new PiPo({
431 x: i,
432 y: j,
433 p: this.getPiece(i, j),
434 c: oppCol
435 })
436 );
437 }
438 });
439 }
440
441 // Withdrawer
442 getPotentialQueenMoves(sq) {
443 let moves = super.getPotentialQueenMoves(sq);
444 this.addQueenCaptures(moves);
445 return moves;
446 }
447
448 getPotentialImmobilizerMoves(sq) {
449 // Immobilizer doesn't capture
450 return super.getPotentialQueenMoves(sq);
451 }
452
453 getPotentialKingMoves(sq) {
454 return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
455 }
456
38408f63 457 getPotentialGuardMoves(sq) {
4313762d
BA
458 return this.getSlideNJumpMoves(
459 sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
e90bafa8
BA
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) {
4313762d
BA
681 return super.isAttackedBySlideNJump(
682 sq, color, V.GUARD, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1);
e90bafa8
BA
683 }
684
685 getNextMageCheck(step) {
686 if (step[0] == 0) {
687 if (step[1] == 1) return [[1, 1], [-1, 1]];
688 return [[-1, -1], [1, -1]];
689 }
690 if (step[0] == -1) return [[-1, -1], [-1, 1]];
691 return [[1, 1], [1, -1]];
692 }
693
694 isAttackedByMage([x, y], color) {
695 for (let step of V.steps[V.BISHOP]) {
696 const [i, j] = [x + step[0], y + step[1]];
697 if (
698 V.OnBoard(i, j) &&
699 this.board[i][j] != V.EMPTY &&
700 this.getColor(i, j) == color &&
701 this.getPiece(i, j) == V.MAGE
702 ) {
703 return true;
704 }
705 }
706 for (let step of V.steps[V.ROOK]) {
707 let [i, j] = [x + step[0], y + step[1]];
708 const stepM = this.getNextMageCheck(step);
709 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
710 for (let s of stepM) {
711 const [ii, jj] = [i + s[0], j + s[1]];
712 if (
713 V.OnBoard(ii, jj) &&
714 this.board[ii][jj] != V.EMPTY &&
715 this.getColor(ii, jj) == color &&
716 this.getPiece(ii, jj) == V.MAGE
717 ) {
718 return true;
719 }
720 }
721 i += step[0];
722 j += step[1];
723 }
724 }
725 return false;
726 }
727
728 getCurrentScore() {
729 const color = this.turn;
730 const getScoreLost = () => {
731 // Result if I lose:
732 return color == "w" ? "0-1" : "1-0";
733 };
734 if (!this.atLeastOneMove()) {
735 // No valid move: I lose or draw
736 if (this.underCheck(color)) return getScoreLost();
737 return "1/2";
738 }
739 // I lose also if no pieces left (except king)
740 let piecesLeft = 0;
741 outerLoop: for (let i=0; i<V.size.x; i++) {
742 for (let j=0; j<V.size.y; j++) {
743 if (
744 this.board[i][j] != V.EMPTY &&
745 this.getColor(i, j) == color &&
746 this.getPiece(i,j) != V.KING
747 ) {
748 piecesLeft++;
749 }
750 }
751 }
752 if (piecesLeft == 0) return getScoreLost();
753 // Check if my palace is invaded:
754 const pX = (color == 'w' ? 10 : 0);
755 const oppCol = V.GetOppCol(color);
756 if (
757 this.board[pX][3] != V.EMPTY &&
758 this.getColor(pX, 3) == oppCol &&
759 this.board[pX][4] != V.EMPTY &&
760 this.getColor(pX, 4) == oppCol
761 ) {
762 return getScoreLost();
763 }
764 return "*";
765 }
766
767 static GenRandInitFen() {
768 // Always deterministic:
769 return (
770 "xxx2xxx/1g1qk1g1/1bnmrnb1/dppppppd/8/8/8/" +
771 "DPPPPPPD/1BNMRNB1/1G1QK1G1/xxx2xxx w 0"
772 );
773 }
774
775 static get VALUES() {
776 return {
777 p: 1,
778 r: 2,
779 n: 5,
780 b: 4,
781 q: 2,
782 m: 5,
783 g: 7,
784 d: 4,
785 k: 1000
786 };
787 }
788
789 static get SEARCH_DEPTH() {
790 return 2;
791 }
792
793 evalPosition() {
794 let evaluation = 0;
795 for (let i = 0; i < V.size.x; i++) {
796 for (let j = 0; j < V.size.y; j++) {
797 if (![V.EMPTY,V.NOTHING].includes(this.board[i][j])) {
798 const sign = this.getColor(i, j) == "w" ? 1 : -1;
799 evaluation += sign * V.VALUES[this.getPiece(i, j)];
800 }
801 }
802 }
803 return evaluation;
804 }
805
806 getNotation(move) {
807 const initialSquare = V.CoordsToSquare(move.start);
808 const finalSquare = V.CoordsToSquare(move.end);
809 if (move.appear.length == 0)
810 // Suicide 'S'
811 return initialSquare + "S";
812 let notation = undefined;
813 if (move.appear[0].p == V.PAWN) {
814 // Pawn: generally ambiguous short notation, so we use full description
815 notation = "P" + initialSquare + finalSquare;
816 } else if (move.appear[0].p == V.KING)
817 notation = "K" + (move.vanish.length > 1 ? "x" : "") + finalSquare;
818 else notation = move.appear[0].p.toUpperCase() + finalSquare;
819 // Add a capture mark (not describing what is captured...):
820 if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X";
821 return notation;
822 }
7e8a7ea1 823
e90bafa8 824};