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