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
{
7 static get HasFlags() {
11 static get HasEnpassant() {
16 return ChessRules
.PIECES
.concat([V
.IMMOBILIZER
, V
.MAGE
, V
.GUARD
]);
20 if (b
[0] == 'x') return "Maxima/nothing";
21 if (['m','d','g'].includes(b
[1]))
26 // For space next to the palaces:
27 static get NOTHING() {
32 if (b
[0] == 'x') return 'x';
33 return ChessRules
.board2fen(b
);
37 if (f
== 'x') return V
.NOTHING
;
38 return ChessRules
.fen2board(f
);
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
) {
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
++;
53 const num
= parseInt(row
[i
], 10);
54 if (isNaN(num
)) return false;
58 if (sumElts
!= V
.size
.y
) return false;
60 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
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
++) {
71 for (let j
= 0; j
< position
[i
].length
; j
++) {
72 switch (position
[i
].charAt(j
)) {
74 this.kingPos
["b"] = [i
, k
];
77 this.kingPos
["w"] = [i
, k
];
80 const num
= parseInt(position
[i
].charAt(j
), 10);
81 if (!isNaN(num
)) k
+= num
- 1;
90 return { x: 11, y: 8 };
93 static OnBoard(x
, y
) {
95 (x
>= 1 && x
<= 9 && y
>= 0 && y
<= 7) ||
96 ([3, 4].includes(y
) && [0, 10].includes(x
))
100 static get IMMOBILIZER() {
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
116 // Is piece on square (x,y) immobilized?
117 isImmobilized([x
, y
]) {
118 const piece
= this.getPiece(x
, y
);
120 // Mages are not immobilized:
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]];
128 this.board
[i
][j
] != V
.EMPTY
&&
129 this.getColor(i
, j
) == oppCol
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
];
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:
148 start: { x: x
, y: y
},
149 end: { x: imSq
[0], y: imSq
[1] },
155 c: this.getColor(x
, y
),
156 p: this.getPiece(x
, y
)
162 let moves
= undefined;
165 moves
= this.getPotentialImmobilizerMoves([x
, y
]);
168 moves
= this.getPotentialGuardMoves([x
, y
]);
171 moves
= this.getPotentialMageMoves([x
, y
]);
174 moves
= super.getPotentialMovesFrom([x
, y
]);
176 const pX
= (this.turn
== 'w' ? 10 : 0);
177 if (this.board
[pX
][3] == V
.EMPTY
&& this.board
[pX
][4] == V
.EMPTY
)
179 // Filter out moves resulting in self palace occupation:
180 // NOTE: cannot invade own palace but still check the king there.
181 const pY
= (this.board
[pX
][3] != V
.EMPTY
? 4 : 3);
182 return moves
.filter(m
=> m
.end
.x
!= pX
|| m
.end
.y
!= pY
);
185 getSlideNJumpMoves([x
, y
], steps
, oneStep
, mageInitSquare
) {
186 const piece
= !mageInitSquare
? this.getPiece(x
, y
) : V
.MAGE
;
187 const initSquare
= mageInitSquare
|| [x
, y
];
189 outerLoop: for (let step
of steps
) {
192 if (piece
== V
.KING
) j
= j
% V
.size
.y
;
193 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
194 moves
.push(this.getBasicMove(initSquare
, [i
, j
]));
195 if (!!oneStep
) continue outerLoop
;
199 // Only king, guard and mage (+ chameleon) can take on occupied square:
202 [V
.KING
, V
.GUARD
, V
.MAGE
].includes(piece
) &&
203 this.canTake(initSquare
, [i
, j
])
205 moves
.push(this.getBasicMove(initSquare
, [i
, j
]));
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
);
217 if (!!byChameleon
&& m
.start
.x
!= m
.end
.x
&& m
.start
.y
!= m
.end
.y
)
218 // Chameleon not moving as pawn
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]];
224 V
.OnBoard(sq2
[0], sq2
[1]) &&
225 this.board
[sq2
[0]][sq2
[1]] != V
.EMPTY
&&
226 this.getColor(sq2
[0], sq2
[1]) == color
229 const sq1
= [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
231 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
232 this.getColor(sq1
[0], sq1
[1]) == oppCol
234 const piece1
= this.getPiece(sq1
[0], sq1
[1]);
235 if (!byChameleon
|| piece1
== V
.PAWN
) {
252 getPotentialPawnMoves([x
, y
]) {
253 let moves
= super.getPotentialRookMoves([x
, y
]);
254 this.addPawnCaptures(moves
);
258 addRookCaptures(moves
, byChameleon
) {
259 const color
= this.turn
;
260 const oppCol
= V
.GetOppCol(color
);
261 const kp
= this.kingPos
[color
];
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
) {
286 getPotentialRookMoves(sq
) {
287 let moves
= super.getPotentialQueenMoves(sq
);
288 this.addRookCaptures(moves
);
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
);
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
) {
308 this.getColor(i
, j
) == color
||
309 (!!byChameleon
&& this.getPiece(i
, j
) != V
.KNIGHT
)
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.
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
;
327 new PiPo({ x: last
[0], y: last
[1], c: oppCol
, p: oppPiece
})
330 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
) {
332 this.getColor(cur
[0], cur
[1]) == color
||
333 this.board
[last
[0]][last
[1]] != V
.EMPTY
335 //TODO: redundant test
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] }
348 last
= [last
[0] + step
[0], last
[1] + step
[1]];
349 cur
= [cur
[0] + step
[0], cur
[1] + step
[1]];
356 getPotentialKnightMoves(sq
) {
357 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
361 getPotentialBishopMoves([x
, y
]) {
363 .getPotentialQueenMoves([x
, y
])
364 .concat(this.getKnightCaptures([x
, y
], "asChameleon"))
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");
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]];
374 this.board
[i
][j
] != V
.EMPTY
&&
375 this.canTake([x
, y
], [i
, j
]) &&
376 [V
.GUARD
, V
.MAGE
].includes(this.getPiece(i
, j
))
378 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
381 // Post-processing: merge similar moves, concatenating vanish arrays
382 let mergedMoves
= {};
384 const key
= m
.end
.x
+ V
.size
.x
* m
.end
.y
;
385 if (!mergedMoves
[key
]) mergedMoves
[key
] = m
;
387 for (let i
= 1; i
< m
.vanish
.length
; i
++)
388 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
391 return Object
.values(mergedMoves
);
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]];
405 this.board
[i
][j
] != V
.EMPTY
&&
406 this.getColor(i
, j
) == oppCol
&&
407 (!byChameleon
|| this.getPiece(i
, j
) == V
.QUEEN
)
409 capturingDirections
.push(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
417 // TODO: this test should be done only once per direction
419 capturingDirections
.some(dir
=> {
420 return dir
[0] == -step
[0] && dir
[1] == -step
[1];
423 const [i
, j
] = [x
- step
[0], y
- step
[1]];
428 p: this.getPiece(i
, j
),
437 getPotentialQueenMoves(sq
) {
438 let moves
= super.getPotentialQueenMoves(sq
);
439 this.addQueenCaptures(moves
);
443 getPotentialImmobilizerMoves(sq
) {
444 // Immobilizer doesn't capture
445 return super.getPotentialQueenMoves(sq
);
448 getPotentialKingMoves(sq
) {
449 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep");
452 getPotentialGuardMoves(sq
) {
454 this.getSlideNJumpMoves(
456 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
463 getNextMageSteps(step
) {
465 if (step
[1] == -1) return [[-1, 0], [0, -1]];
466 return [[-1, 0], [0, 1]];
468 if (step
[1] == -1) return [[1, 0], [0, -1]];
469 return [[1, 0], [0, 1]];
472 getPotentialMageMoves([x
, y
]) {
473 const oppCol
= V
.GetOppCol(this.turn
);
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
) {
479 if (this.getColor(i
, j
) == oppCol
)
481 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
484 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
485 // Continue orthogonally:
486 const stepO
= this.getNextMageSteps(step
);
487 Array
.prototype.push
.apply(
489 this.getSlideNJumpMoves([i
, j
], stepO
, null, [x
, y
])
496 isAttacked(sq
, color
) {
498 super.isAttacked(sq
, color
) ||
499 this.isAttackedByGuard(sq
, color
) ||
500 this.isAttackedByMage(sq
, color
)
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).
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
)) {
518 this.board
[i1
][j1
] != V
.EMPTY
&&
519 this.getColor(i1
, j1
) == color
&&
520 this.board
[i2
][j2
] == V
.EMPTY
524 this.board
[i2
][j2
] != V
.EMPTY
&&
525 this.getColor(i2
, j2
) == color
&&
526 this.board
[i1
][j1
] == V
.EMPTY
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
) {
539 this.getColor(i3
, j3
) == color
&&
540 this.getPiece(i3
, j3
) == V
.PAWN
&&
541 !this.isImmobilized([i3
, j3
])
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
++) {
562 this.board
[i
][j
] != V
.EMPTY
&&
563 this.getColor(i
, j
) == color
&&
564 this.getPiece(i
, j
) == V
.ROOK
566 if (this.isImmobilized([i
, j
]))
567 // Because only one rook:
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
) {
574 (sameRow
&& move.end
.y
== y
) ||
575 (sameColumn
&& move.end
.x
== x
)
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
) {
601 if (V
.OnBoard(i
, j
)) {
602 if (this.getColor(i
, j
) == color
) {
604 this.getPiece(i
, j
) == V
.KNIGHT
&&
605 !this.isImmobilized([i
, j
])
612 // could be captured *if there was an empty space*
613 if (this.board
[i
+ step
[0]][j
+ step
[1]] != V
.EMPTY
)
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]];
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
])
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]];
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
)
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
;
671 this.getPiece(rx
, ry
) === V
.KING
&&
672 this.getColor(rx
, ry
) == color
&&
673 !this.isImmobilized([rx
, ry
])
681 isAttackedByGuard(sq
, color
) {
683 super.isAttackedBySlideNJump(
687 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
693 getNextMageCheck(step
) {
695 if (step
[1] == 1) return [[1, 1], [-1, 1]];
696 return [[-1, -1], [1, -1]];
698 if (step
[0] == -1) return [[-1, -1], [-1, 1]];
699 return [[1, 1], [1, -1]];
702 isAttackedByMage([x
, y
], color
) {
703 for (let step
of V
.steps
[V
.BISHOP
]) {
704 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
707 this.board
[i
][j
] != V
.EMPTY
&&
708 this.getColor(i
, j
) == color
&&
709 this.getPiece(i
, j
) == V
.MAGE
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]];
722 this.board
[ii
][jj
] != V
.EMPTY
&&
723 this.getColor(ii
, jj
) == color
&&
724 this.getPiece(ii
, jj
) == V
.MAGE
737 const color
= this.turn
;
738 const getScoreLost
= () => {
740 return color
== "w" ? "0-1" : "1-0";
742 if (!this.atLeastOneMove()) {
743 // No valid move: I lose or draw
744 if (this.underCheck(color
)) return getScoreLost();
747 // I lose also if no pieces left (except king)
749 outerLoop: for (let i
=0; i
<V
.size
.x
; i
++) {
750 for (let j
=0; j
<V
.size
.y
; j
++) {
752 this.board
[i
][j
] != V
.EMPTY
&&
753 this.getColor(i
, j
) == color
&&
754 this.getPiece(i
,j
) != V
.KING
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
);
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
770 return getScoreLost();
775 static GenRandInitFen() {
776 // Always deterministic:
778 "xxx2xxx/1g1qk1g1/1bnmrnb1/dppppppd/8/8/8/" +
779 "DPPPPPPD/1BNMRNB1/1G1QK1G1/xxx2xxx w 0"
783 static get VALUES() {
797 static get SEARCH_DEPTH() {
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
)];
815 const initialSquare
= V
.CoordsToSquare(move.start
);
816 const finalSquare
= V
.CoordsToSquare(move.end
);
817 if (move.appear
.length
== 0)
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";