1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { shuffle
} from "@/utils/alea";
5 export class MaximaRules
extends ChessRules
{
6 static get HasFlags() {
10 static get HasEnpassant() {
15 return ChessRules
.PIECES
.concat([V
.IMMOBILIZER
, V
.MAGE
, V
.GUARD
]);
19 if (b
[0] == 'x') return "Maxima/nothing";
20 if (['m','d','g'].includes(b
[1]))
25 // For space next to the palaces:
26 static get NOTHING() {
31 if (b
[0] == 'x') return 'x';
32 return ChessRules
.board2fen(b
);
36 if (f
== 'x') return V
.NOTHING
;
37 return ChessRules
.fen2board(f
);
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
) {
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
++;
52 const num
= parseInt(row
[i
]);
53 if (isNaN(num
)) return false;
57 if (sumElts
!= V
.size
.y
) return false;
59 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
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
++) {
70 for (let j
= 0; j
< position
[i
].length
; j
++) {
71 switch (position
[i
].charAt(j
)) {
73 this.kingPos
["b"] = [i
, k
];
76 this.kingPos
["w"] = [i
, k
];
79 const num
= parseInt(position
[i
].charAt(j
));
80 if (!isNaN(num
)) k
+= num
- 1;
89 return { x: 11, y: 8 };
92 static OnBoard(x
, y
) {
94 (x
>= 1 && x
<= 9 && y
>= 0 && y
<= 7) ||
95 ([3, 4].includes(y
) && [0, 10].includes(x
))
99 static get IMMOBILIZER() {
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
115 // Is piece on square (x,y) immobilized?
116 isImmobilized([x
, y
]) {
117 const piece
= this.getPiece(x
, y
);
119 // Mages are not immobilized:
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]];
127 this.board
[i
][j
] != V
.EMPTY
&&
128 this.getColor(i
, j
) == oppCol
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
];
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:
147 start: { x: x
, y: y
},
148 end: { x: imSq
[0], y: imSq
[1] },
154 c: this.getColor(x
, y
),
155 p: this.getPiece(x
, y
)
161 let moves
= undefined;
164 moves
= this.getPotentialImmobilizerMoves([x
, y
]);
167 moves
= this.getPotentialGuardMoves([x
, y
]);
170 moves
= this.getPotentialMageMoves([x
, y
]);
173 moves
= super.getPotentialMovesFrom([x
, y
]);
175 const pX
= (this.turn
== 'w' ? 10 : 0);
176 if (this.board
[pX
][3] == V
.EMPTY
&& this.board
[pX
][4] == V
.EMPTY
)
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
);
184 getSlideNJumpMoves([x
, y
], steps
, oneStep
, mageInitSquare
, onlyTake
) {
185 const piece
= !mageInitSquare
? this.getPiece(x
, y
) : V
.MAGE
;
186 const initSquare
= mageInitSquare
|| [x
, y
];
188 outerLoop: for (let step
of steps
) {
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
;
198 // Only king, guard and mage + chameleon can take on occupied square:
202 this.canTake(initSquare
, [i
, j
])
205 [V
.KING
, V
.GUARD
, V
.MAGE
].includes(piece
) ||
206 (piece
== V
.BISHOP
&& this.getPiece(i
, j
) === onlyTake
)
209 moves
.push(this.getBasicMove(initSquare
, [i
, j
]));
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
);
221 if (!!byChameleon
&& m
.start
.x
!= m
.end
.x
&& m
.start
.y
!= m
.end
.y
)
222 // Chameleon not moving as pawn
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]];
228 V
.OnBoard(sq2
[0], sq2
[1]) &&
229 this.board
[sq2
[0]][sq2
[1]] != V
.EMPTY
&&
230 this.getColor(sq2
[0], sq2
[1]) == color
233 const sq1
= [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
235 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
236 this.getColor(sq1
[0], sq1
[1]) == oppCol
238 const piece1
= this.getPiece(sq1
[0], sq1
[1]);
239 if (!byChameleon
|| piece1
== V
.PAWN
) {
256 getPotentialPawnMoves([x
, y
]) {
257 let moves
= super.getPotentialRookMoves([x
, y
]);
258 this.addPawnCaptures(moves
);
262 addRookCaptures(moves
, byChameleon
) {
263 const color
= this.turn
;
264 const oppCol
= V
.GetOppCol(color
);
265 const kp
= this.kingPos
[color
];
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
) {
290 getPotentialRookMoves(sq
) {
291 let moves
= super.getPotentialQueenMoves(sq
);
292 this.addRookCaptures(moves
);
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
);
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
) {
312 this.getColor(i
, j
) == color
||
313 (!!byChameleon
&& this.getPiece(i
, j
) != V
.KNIGHT
)
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.
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
;
331 new PiPo({ x: last
[0], y: last
[1], c: oppCol
, p: oppPiece
})
334 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
) {
336 this.getColor(cur
[0], cur
[1]) == color
||
337 this.board
[last
[0]][last
[1]] != V
.EMPTY
339 //TODO: redundant test
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] }
352 last
= [last
[0] + step
[0], last
[1] + step
[1]];
353 cur
= [cur
[0] + step
[0], cur
[1] + step
[1]];
360 getPotentialKnightMoves(sq
) {
361 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
365 getPotentialBishopMoves([x
, y
]) {
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
= {};
378 const key
= m
.end
.x
+ V
.size
.x
* m
.end
.y
;
379 if (!mergedMoves
[key
]) mergedMoves
[key
] = m
;
381 for (let i
= 1; i
< m
.vanish
.length
; i
++)
382 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
385 return Object
.values(mergedMoves
);
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]];
399 this.board
[i
][j
] != V
.EMPTY
&&
400 this.getColor(i
, j
) == oppCol
&&
401 (!byChameleon
|| this.getPiece(i
, j
) == V
.QUEEN
)
403 capturingDirections
.push(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
411 // TODO: this test should be done only once per direction
413 capturingDirections
.some(dir
=> {
414 return dir
[0] == -step
[0] && dir
[1] == -step
[1];
417 const [i
, j
] = [x
- step
[0], y
- step
[1]];
422 p: this.getPiece(i
, j
),
431 getPotentialQueenMoves(sq
) {
432 let moves
= super.getPotentialQueenMoves(sq
);
433 this.addQueenCaptures(moves
);
437 getPotentialImmobilizerMoves(sq
) {
438 // Immobilizer doesn't capture
439 return super.getPotentialQueenMoves(sq
);
442 getPotentialKingMoves(sq
) {
443 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep");
446 getPotentialGuardMoves(sq
, byChameleon
) {
447 const onlyTake
= !byChameleon
? null : V
.GUARD
;
449 this.getSlideNJumpMoves(
451 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
459 getNextMageSteps(step
) {
461 if (step
[1] == -1) return [[-1, 0], [0, -1]];
462 return [[-1, 0], [0, 1]];
464 if (step
[1] == -1) return [[1, 0], [0, -1]];
465 return [[1, 0], [0, 1]];
468 getPotentialMageMoves([x
, y
], byChameleon
) {
469 const oppCol
= V
.GetOppCol(this.turn
);
470 const onlyTake
= !byChameleon
? null : V
.MAGE
;
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
) {
477 this.getColor(i
, j
) == oppCol
&&
478 (!onlyTake
|| this.getPiece(i
, j
) == V
.MAGE
)
481 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
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(
490 this.getSlideNJumpMoves([i
, j
], stepO
, null, [x
, y
], onlyTake
)
497 isAttacked(sq
, color
) {
499 super.isAttacked(sq
, color
) ||
500 this.isAttackedByGuard(sq
, color
) ||
501 this.isAttackedByMage(sq
, color
)
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).
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
)) {
519 this.board
[i1
][j1
] != V
.EMPTY
&&
520 this.getColor(i1
, j1
) == color
&&
521 this.board
[i2
][j2
] == V
.EMPTY
525 this.board
[i2
][j2
] != V
.EMPTY
&&
526 this.getColor(i2
, j2
) == color
&&
527 this.board
[i1
][j1
] == V
.EMPTY
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
) {
540 this.getColor(i3
, j3
) == color
&&
541 this.getPiece(i3
, j3
) == V
.PAWN
&&
542 !this.isImmobilized([i3
, j3
])
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
++) {
563 this.board
[i
][j
] != V
.EMPTY
&&
564 this.getColor(i
, j
) == color
&&
565 this.getPiece(i
, j
) == V
.ROOK
567 if (this.isImmobilized([i
, j
]))
568 // Because only one rook:
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
) {
575 (sameRow
&& move.end
.y
== y
) ||
576 (sameColumn
&& move.end
.x
== x
)
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
) {
602 if (V
.OnBoard(i
, j
)) {
603 if (this.getColor(i
, j
) == color
) {
605 this.getPiece(i
, j
) == V
.KNIGHT
&&
606 !this.isImmobilized([i
, j
])
613 // could be captured *if there was an empty space*
614 if (this.board
[i
+ step
[0]][j
+ step
[1]] != V
.EMPTY
)
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]];
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
])
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]];
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
)
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
;
672 this.getPiece(rx
, ry
) === V
.KING
&&
673 this.getColor(rx
, ry
) == color
&&
674 !this.isImmobilized([rx
, ry
])
682 isAttackedByGuard(sq
, color
) {
684 super.isAttackedBySlideNJump(
688 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
694 getNextMageCheck(step
) {
696 if (step
[1] == 1) return [[1, 1], [-1, 1]];
697 return [[-1, -1], [1, -1]];
699 if (step
[0] == -1) return [[-1, -1], [-1, 1]];
700 return [[1, 1], [1, -1]];
703 isAttackedByMage([x
, y
], color
) {
704 for (let step
of V
.steps
[V
.BISHOP
]) {
705 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
708 this.board
[i
][j
] != V
.EMPTY
&&
709 this.getColor(i
, j
) == color
&&
710 this.getPiece(i
, j
) == V
.MAGE
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]];
723 this.board
[ii
][jj
] != V
.EMPTY
&&
724 this.getColor(ii
, jj
) == color
&&
725 this.getPiece(ii
, jj
) == V
.MAGE
738 const color
= this.turn
;
739 const getScoreLost
= () => {
741 return color
== "w" ? "0-1" : "1-0";
743 if (!this.atLeastOneMove()) {
744 // No valid move: I lose or draw
745 if (this.underCheck(color
)) return getScoreLost();
748 // I lose also if no pieces left (except king)
750 outerLoop: for (let i
=0; i
<V
.size
.x
; i
++) {
751 for (let j
=0; j
<V
.size
.y
; j
++) {
753 this.board
[i
][j
] != V
.EMPTY
&&
754 this.getColor(i
, j
) == color
&&
755 this.getPiece(i
,j
) != V
.KING
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
);
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
771 return getScoreLost();
776 static GenRandInitFen() {
777 // Always deterministic:
779 "xxx2xxx/1g1qk1g1/1bnmrnb1/dppppppd/8/8/8/" +
780 "DPPPPPPD/1BNMRNB1/1G1QK1G1/xxx2xxx w 0"
784 static get VALUES() {
798 static get SEARCH_DEPTH() {
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
)];
816 const initialSquare
= V
.CoordsToSquare(move.start
);
817 const finalSquare
= V
.CoordsToSquare(move.end
);
818 if (move.appear
.length
== 0)
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";