d8347ac550cac952f59e95241338a5b1f0be9ee3
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
{
11 static get HasFlags() {
15 static get HasEnpassant() {
20 return ChessRules
.PIECES
.concat([V
.IMMOBILIZER
, V
.MAGE
, V
.GUARD
]);
24 if (b
[0] == 'x') return "Maxima/nothing";
25 if (['m','d','g'].includes(b
[1]))
30 // For space next to the palaces:
31 static get NOTHING() {
36 if (b
[0] == 'x') return 'x';
37 return ChessRules
.board2fen(b
);
41 if (f
== 'x') return V
.NOTHING
;
42 return ChessRules
.fen2board(f
);
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
) {
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
++;
57 const num
= parseInt(row
[i
], 10);
58 if (isNaN(num
)) return false;
62 if (sumElts
!= V
.size
.y
) return false;
64 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
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
++) {
75 for (let j
= 0; j
< position
[i
].length
; j
++) {
76 switch (position
[i
].charAt(j
)) {
78 this.kingPos
["b"] = [i
, k
];
81 this.kingPos
["w"] = [i
, k
];
84 const num
= parseInt(position
[i
].charAt(j
), 10);
85 if (!isNaN(num
)) k
+= num
- 1;
94 return { x: 11, y: 8 };
97 static OnBoard(x
, y
) {
99 (x
>= 1 && x
<= 9 && y
>= 0 && y
<= 7) ||
100 ([3, 4].includes(y
) && [0, 10].includes(x
))
104 static get IMMOBILIZER() {
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
120 // Is piece on square (x,y) immobilized?
121 isImmobilized([x
, y
]) {
122 const piece
= this.getPiece(x
, y
);
124 // Mages are not immobilized:
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]];
132 this.board
[i
][j
] != V
.EMPTY
&&
133 this.getColor(i
, j
) == oppCol
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
];
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
);
149 if (piece
== V
.KING
) return [];
150 // Only option is suicide
153 start: { x: x
, y: y
},
154 end: { x: imSq
[0], y: imSq
[1] },
160 c: this.getColor(x
, y
),
161 p: this.getPiece(x
, y
)
167 let moves
= undefined;
170 moves
= this.getPotentialImmobilizerMoves([x
, y
]);
173 moves
= this.getPotentialGuardMoves([x
, y
]);
176 moves
= this.getPotentialMageMoves([x
, y
]);
179 moves
= super.getPotentialMovesFrom([x
, y
]);
181 const pX
= (this.turn
== 'w' ? 10 : 0);
182 if (this.board
[pX
][3] == V
.EMPTY
&& this.board
[pX
][4] == V
.EMPTY
)
184 // Filter out moves resulting in self palace occupation:
185 // NOTE: cannot invade own palace but still check the king there.
186 const pY
= (this.board
[pX
][3] != V
.EMPTY
? 4 : 3);
187 return moves
.filter(m
=> m
.end
.x
!= pX
|| m
.end
.y
!= pY
);
190 getSlideNJumpMoves([x
, y
], steps
, oneStep
, mageInitSquare
) {
191 const piece
= !mageInitSquare
? this.getPiece(x
, y
) : V
.MAGE
;
192 const initSquare
= mageInitSquare
|| [x
, y
];
194 outerLoop: for (let step
of steps
) {
197 if (piece
== V
.KING
) j
= j
% V
.size
.y
;
198 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
199 moves
.push(this.getBasicMove(initSquare
, [i
, j
]));
200 if (oneStep
) continue outerLoop
;
204 // Only king, guard and mage (+ chameleon) can take on occupied square:
207 [V
.KING
, V
.GUARD
, V
.MAGE
].includes(piece
) &&
208 this.canTake(initSquare
, [i
, j
])
210 moves
.push(this.getBasicMove(initSquare
, [i
, j
]));
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
);
222 if (!!byChameleon
&& m
.start
.x
!= m
.end
.x
&& m
.start
.y
!= m
.end
.y
)
223 // Chameleon not moving as pawn
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]];
229 V
.OnBoard(sq2
[0], sq2
[1]) &&
230 this.board
[sq2
[0]][sq2
[1]] != V
.EMPTY
&&
231 this.getColor(sq2
[0], sq2
[1]) == color
234 const sq1
= [m
.end
.x
+ step
[0], m
.end
.y
+ step
[1]];
236 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
237 this.getColor(sq1
[0], sq1
[1]) == oppCol
239 const piece1
= this.getPiece(sq1
[0], sq1
[1]);
240 if (!byChameleon
|| piece1
== V
.PAWN
) {
257 getPotentialPawnMoves([x
, y
]) {
258 let moves
= super.getPotentialRookMoves([x
, y
]);
259 this.addPawnCaptures(moves
);
263 addRookCaptures(moves
, byChameleon
) {
264 const color
= this.turn
;
265 const oppCol
= V
.GetOppCol(color
);
266 const kp
= this.kingPos
[color
];
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
) {
291 getPotentialRookMoves(sq
) {
292 let moves
= super.getPotentialQueenMoves(sq
);
293 this.addRookCaptures(moves
);
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
);
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
) {
313 this.getColor(i
, j
) == color
||
314 (!!byChameleon
&& this.getPiece(i
, j
) != V
.KNIGHT
)
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.
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
;
332 new PiPo({ x: last
[0], y: last
[1], c: oppCol
, p: oppPiece
})
335 if (this.board
[cur
[0]][cur
[1]] != V
.EMPTY
) {
337 this.getColor(cur
[0], cur
[1]) == color
||
338 this.board
[last
[0]][last
[1]] != V
.EMPTY
340 //TODO: redundant test
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] }
353 last
= [last
[0] + step
[0], last
[1] + step
[1]];
354 cur
= [cur
[0] + step
[0], cur
[1] + step
[1]];
361 getPotentialKnightMoves(sq
) {
362 return super.getPotentialQueenMoves(sq
).concat(this.getKnightCaptures(sq
));
366 getPotentialBishopMoves([x
, y
]) {
368 .getPotentialQueenMoves([x
, y
])
369 .concat(this.getKnightCaptures([x
, y
], "asChameleon"))
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");
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]];
379 this.board
[i
][j
] != V
.EMPTY
&&
380 this.canTake([x
, y
], [i
, j
]) &&
381 [V
.GUARD
, V
.MAGE
].includes(this.getPiece(i
, j
))
383 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
386 // Post-processing: merge similar moves, concatenating vanish arrays
387 let mergedMoves
= {};
389 const key
= m
.end
.x
+ V
.size
.x
* m
.end
.y
;
390 if (!mergedMoves
[key
]) mergedMoves
[key
] = m
;
392 for (let i
= 1; i
< m
.vanish
.length
; i
++)
393 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
396 return Object
.values(mergedMoves
);
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]];
410 this.board
[i
][j
] != V
.EMPTY
&&
411 this.getColor(i
, j
) == oppCol
&&
412 (!byChameleon
|| this.getPiece(i
, j
) == V
.QUEEN
)
414 capturingDirections
.push(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
422 // TODO: this test should be done only once per direction
424 capturingDirections
.some(dir
=> {
425 return dir
[0] == -step
[0] && dir
[1] == -step
[1];
428 const [i
, j
] = [x
- step
[0], y
- step
[1]];
433 p: this.getPiece(i
, j
),
442 getPotentialQueenMoves(sq
) {
443 let moves
= super.getPotentialQueenMoves(sq
);
444 this.addQueenCaptures(moves
);
448 getPotentialImmobilizerMoves(sq
) {
449 // Immobilizer doesn't capture
450 return super.getPotentialQueenMoves(sq
);
453 getPotentialKingMoves(sq
) {
454 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep");
457 getPotentialGuardMoves(sq
) {
458 return this.getSlideNJumpMoves(
459 sq
, V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep");
462 getNextMageSteps(step
) {
464 if (step
[1] == -1) return [[-1, 0], [0, -1]];
465 return [[-1, 0], [0, 1]];
467 if (step
[1] == -1) return [[1, 0], [0, -1]];
468 return [[1, 0], [0, 1]];
471 getPotentialMageMoves([x
, y
]) {
472 const oppCol
= V
.GetOppCol(this.turn
);
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
) {
478 if (this.getColor(i
, j
) == oppCol
)
480 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
483 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
484 // Continue orthogonally:
485 const stepO
= this.getNextMageSteps(step
);
486 Array
.prototype.push
.apply(
488 this.getSlideNJumpMoves([i
, j
], stepO
, null, [x
, y
])
495 isAttacked(sq
, color
) {
497 super.isAttacked(sq
, color
) ||
498 this.isAttackedByGuard(sq
, color
) ||
499 this.isAttackedByMage(sq
, color
)
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).
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
)) {
517 this.board
[i1
][j1
] != V
.EMPTY
&&
518 this.getColor(i1
, j1
) == color
&&
519 this.board
[i2
][j2
] == V
.EMPTY
523 this.board
[i2
][j2
] != V
.EMPTY
&&
524 this.getColor(i2
, j2
) == color
&&
525 this.board
[i1
][j1
] == V
.EMPTY
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
) {
538 this.getColor(i3
, j3
) == color
&&
539 this.getPiece(i3
, j3
) == V
.PAWN
&&
540 !this.isImmobilized([i3
, j3
])
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
++) {
561 this.board
[i
][j
] != V
.EMPTY
&&
562 this.getColor(i
, j
) == color
&&
563 this.getPiece(i
, j
) == V
.ROOK
565 if (this.isImmobilized([i
, j
]))
566 // Because only one rook:
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
) {
573 (sameRow
&& move.end
.y
== y
) ||
574 (sameColumn
&& move.end
.x
== x
)
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
) {
600 if (V
.OnBoard(i
, j
)) {
601 if (this.getColor(i
, j
) == color
) {
603 this.getPiece(i
, j
) == V
.KNIGHT
&&
604 !this.isImmobilized([i
, j
])
611 // could be captured *if there was an empty space*
612 if (this.board
[i
+ step
[0]][j
+ step
[1]] != V
.EMPTY
)
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]];
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
])
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]];
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
)
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
;
670 this.getPiece(rx
, ry
) === V
.KING
&&
671 this.getColor(rx
, ry
) == color
&&
672 !this.isImmobilized([rx
, ry
])
680 isAttackedByGuard(sq
, color
) {
681 return super.isAttackedBySlideNJump(
682 sq
, color
, V
.GUARD
, V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), 1);
685 getNextMageCheck(step
) {
687 if (step
[1] == 1) return [[1, 1], [-1, 1]];
688 return [[-1, -1], [1, -1]];
690 if (step
[0] == -1) return [[-1, -1], [-1, 1]];
691 return [[1, 1], [1, -1]];
694 isAttackedByMage([x
, y
], color
) {
695 for (let step
of V
.steps
[V
.BISHOP
]) {
696 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
699 this.board
[i
][j
] != V
.EMPTY
&&
700 this.getColor(i
, j
) == color
&&
701 this.getPiece(i
, j
) == V
.MAGE
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]];
714 this.board
[ii
][jj
] != V
.EMPTY
&&
715 this.getColor(ii
, jj
) == color
&&
716 this.getPiece(ii
, jj
) == V
.MAGE
729 const color
= this.turn
;
730 const getScoreLost
= () => {
732 return color
== "w" ? "0-1" : "1-0";
734 if (!this.atLeastOneMove()) {
735 // No valid move: I lose or draw
736 if (this.underCheck(color
)) return getScoreLost();
739 // I lose also if no pieces left (except king)
741 outerLoop: for (let i
=0; i
<V
.size
.x
; i
++) {
742 for (let j
=0; j
<V
.size
.y
; j
++) {
744 this.board
[i
][j
] != V
.EMPTY
&&
745 this.getColor(i
, j
) == color
&&
746 this.getPiece(i
,j
) != V
.KING
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
);
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
762 return getScoreLost();
767 static GenRandInitFen() {
768 // Always deterministic:
770 "xxx2xxx/1g1qk1g1/1bnmrnb1/dppppppd/8/8/8/" +
771 "DPPPPPPD/1BNMRNB1/1G1QK1G1/xxx2xxx w 0"
775 static get VALUES() {
789 static get SEARCH_DEPTH() {
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
)];
807 const initialSquare
= V
.CoordsToSquare(move.start
);
808 const finalSquare
= V
.CoordsToSquare(move.end
);
809 if (move.appear
.length
== 0)
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";