df781b9e09cb914475efb86db3970096a1beaa21
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
);
148 if (!!imSq
&& piece
!= V
.KING
) {
149 // Only option is suicide, if I'm not a king:
152 start: { x: x
, y: y
},
153 end: { x: imSq
[0], y: imSq
[1] },
159 c: this.getColor(x
, y
),
160 p: this.getPiece(x
, y
)
166 let moves
= undefined;
169 moves
= this.getPotentialImmobilizerMoves([x
, y
]);
172 moves
= this.getPotentialGuardMoves([x
, y
]);
175 moves
= this.getPotentialMageMoves([x
, y
]);
178 moves
= super.getPotentialMovesFrom([x
, y
]);
180 const pX
= (this.turn
== 'w' ? 10 : 0);
181 if (this.board
[pX
][3] == V
.EMPTY
&& this.board
[pX
][4] == V
.EMPTY
)
183 // Filter out moves resulting in self palace occupation:
184 // NOTE: cannot invade own palace but still check the king there.
185 const pY
= (this.board
[pX
][3] != V
.EMPTY
? 4 : 3);
186 return moves
.filter(m
=> m
.end
.x
!= pX
|| m
.end
.y
!= pY
);
189 getSlideNJumpMoves([x
, y
], steps
, oneStep
, mageInitSquare
) {
190 const piece
= !mageInitSquare
? this.getPiece(x
, y
) : V
.MAGE
;
191 const initSquare
= mageInitSquare
|| [x
, y
];
193 outerLoop: for (let step
of steps
) {
196 if (piece
== V
.KING
) j
= j
% V
.size
.y
;
197 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
198 moves
.push(this.getBasicMove(initSquare
, [i
, j
]));
199 if (oneStep
) continue outerLoop
;
203 // Only king, guard and mage (+ chameleon) can take on occupied square:
206 [V
.KING
, V
.GUARD
, V
.MAGE
].includes(piece
) &&
207 this.canTake(initSquare
, [i
, j
])
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 // No "king capture" because king cannot remain under check
370 this.addPawnCaptures(moves
, "asChameleon");
371 this.addRookCaptures(moves
, "asChameleon");
372 this.addQueenCaptures(moves
, "asChameleon");
373 // Manually add Guard and Mage captures (since cannot move like a Mage)
374 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
375 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
378 this.board
[i
][j
] != V
.EMPTY
&&
379 this.canTake([x
, y
], [i
, j
]) &&
380 [V
.GUARD
, V
.MAGE
].includes(this.getPiece(i
, j
))
382 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
385 // Post-processing: merge similar moves, concatenating vanish arrays
386 let mergedMoves
= {};
388 const key
= m
.end
.x
+ V
.size
.x
* m
.end
.y
;
389 if (!mergedMoves
[key
]) mergedMoves
[key
] = m
;
391 for (let i
= 1; i
< m
.vanish
.length
; i
++)
392 mergedMoves
[key
].vanish
.push(m
.vanish
[i
]);
395 return Object
.values(mergedMoves
);
398 addQueenCaptures(moves
, byChameleon
) {
399 if (moves
.length
== 0) return;
400 const [x
, y
] = [moves
[0].start
.x
, moves
[0].start
.y
];
401 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
402 let capturingDirections
= [];
403 const color
= this.turn
;
404 const oppCol
= V
.GetOppCol(color
);
405 adjacentSteps
.forEach(step
=> {
406 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
409 this.board
[i
][j
] != V
.EMPTY
&&
410 this.getColor(i
, j
) == oppCol
&&
411 (!byChameleon
|| this.getPiece(i
, j
) == V
.QUEEN
)
413 capturingDirections
.push(step
);
418 m
.end
.x
!= x
? (m
.end
.x
- x
) / Math
.abs(m
.end
.x
- x
) : 0,
419 m
.end
.y
!= y
? (m
.end
.y
- y
) / Math
.abs(m
.end
.y
- y
) : 0
421 // TODO: this test should be done only once per direction
423 capturingDirections
.some(dir
=> {
424 return dir
[0] == -step
[0] && dir
[1] == -step
[1];
427 const [i
, j
] = [x
- step
[0], y
- step
[1]];
432 p: this.getPiece(i
, j
),
441 getPotentialQueenMoves(sq
) {
442 let moves
= super.getPotentialQueenMoves(sq
);
443 this.addQueenCaptures(moves
);
447 getPotentialImmobilizerMoves(sq
) {
448 // Immobilizer doesn't capture
449 return super.getPotentialQueenMoves(sq
);
452 getPotentialKingMoves(sq
) {
453 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep");
456 getPotentialGuardMoves(sq
) {
457 return this.getSlideNJumpMoves(
458 sq
, V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep");
461 getNextMageSteps(step
) {
463 if (step
[1] == -1) return [[-1, 0], [0, -1]];
464 return [[-1, 0], [0, 1]];
466 if (step
[1] == -1) return [[1, 0], [0, -1]];
467 return [[1, 0], [0, 1]];
470 getPotentialMageMoves([x
, y
]) {
471 const oppCol
= V
.GetOppCol(this.turn
);
473 for (let step
of V
.steps
[V
.BISHOP
]) {
474 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
475 if (!V
.OnBoard(i
, j
)) continue;
476 if (this.board
[i
][j
] != V
.EMPTY
) {
477 if (this.getColor(i
, j
) == oppCol
)
479 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
482 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
483 // Continue orthogonally:
484 const stepO
= this.getNextMageSteps(step
);
485 Array
.prototype.push
.apply(
487 this.getSlideNJumpMoves([i
, j
], stepO
, null, [x
, y
])
494 isAttacked(sq
, color
) {
496 super.isAttacked(sq
, color
) ||
497 this.isAttackedByGuard(sq
, color
) ||
498 this.isAttackedByMage(sq
, color
)
502 isAttackedByPawn([x
, y
], color
) {
503 // Square (x,y) must be surroundable by two enemy pieces,
504 // and one of them at least should be a pawn (moving).
509 const steps
= V
.steps
[V
.ROOK
];
510 for (let dir
of dirs
) {
511 const [i1
, j1
] = [x
- dir
[0], y
- dir
[1]]; //"before"
512 const [i2
, j2
] = [x
+ dir
[0], y
+ dir
[1]]; //"after"
513 if (V
.OnBoard(i1
, j1
) && V
.OnBoard(i2
, j2
)) {
516 this.board
[i1
][j1
] != V
.EMPTY
&&
517 this.getColor(i1
, j1
) == color
&&
518 this.board
[i2
][j2
] == V
.EMPTY
522 this.board
[i2
][j2
] != V
.EMPTY
&&
523 this.getColor(i2
, j2
) == color
&&
524 this.board
[i1
][j1
] == V
.EMPTY
527 // Search a movable enemy pawn landing on the empty square
528 for (let step
of steps
) {
529 let [ii
, jj
] = this.board
[i1
][j1
] == V
.EMPTY
? [i1
, j1
] : [i2
, j2
];
530 let [i3
, j3
] = [ii
+ step
[0], jj
+ step
[1]];
531 while (V
.OnBoard(i3
, j3
) && this.board
[i3
][j3
] == V
.EMPTY
) {
537 this.getColor(i3
, j3
) == color
&&
538 this.getPiece(i3
, j3
) == V
.PAWN
&&
539 !this.isImmobilized([i3
, j3
])
550 isAttackedByRook([x
, y
], color
) {
551 // King must be on same column or row,
552 // and a rook should be able to reach a capturing square
553 const sameRow
= x
== this.kingPos
[color
][0];
554 const sameColumn
= y
== this.kingPos
[color
][1];
555 if (sameRow
|| sameColumn
) {
556 // Look for the enemy rook (maximum 1)
557 for (let i
= 0; i
< V
.size
.x
; i
++) {
558 for (let j
= 0; j
< V
.size
.y
; j
++) {
560 this.board
[i
][j
] != V
.EMPTY
&&
561 this.getColor(i
, j
) == color
&&
562 this.getPiece(i
, j
) == V
.ROOK
564 if (this.isImmobilized([i
, j
]))
565 // Because only one rook:
567 // Can it reach a capturing square? Easy but quite suboptimal way
568 // (TODO: generate all moves (turn is OK))
569 const moves
= this.getPotentialMovesFrom([i
, j
]);
570 for (let move of moves
) {
572 (sameRow
&& move.end
.y
== y
) ||
573 (sameColumn
&& move.end
.x
== x
)
585 isAttackedByKnight([x
, y
], color
) {
586 // Square (x,y) must be on same line as a knight,
587 // and there must be empty square(s) behind.
588 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
589 outerLoop: for (let step
of steps
) {
590 const [i0
, j0
] = [x
+ step
[0], y
+ step
[1]];
591 if (V
.OnBoard(i0
, j0
) && this.board
[i0
][j0
] == V
.EMPTY
) {
592 // Try in opposite direction:
593 let [i
, j
] = [x
- step
[0], y
- step
[1]];
594 while (V
.OnBoard(i
, j
)) {
595 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
599 if (V
.OnBoard(i
, j
)) {
600 if (this.getColor(i
, j
) == color
) {
602 this.getPiece(i
, j
) == V
.KNIGHT
&&
603 !this.isImmobilized([i
, j
])
610 // could be captured *if there was an empty space*
611 if (this.board
[i
+ step
[0]][j
+ step
[1]] != V
.EMPTY
)
622 isAttackedByBishop([x
, y
], color
) {
623 // We cheat a little here: since this function is used exclusively for
624 // the king, it's enough to check the immediate surrounding of the square.
625 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
626 for (let step
of adjacentSteps
) {
627 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
630 this.board
[i
][j
] != V
.EMPTY
&&
631 this.getColor(i
, j
) == color
&&
632 this.getPiece(i
, j
) == V
.BISHOP
&&
633 !this.isImmobilized([i
, j
])
641 isAttackedByQueen([x
, y
], color
) {
642 // Square (x,y) must be adjacent to a queen, and the queen must have
643 // some free space in the opposite direction from (x,y)
644 const adjacentSteps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
645 for (let step
of adjacentSteps
) {
646 const sq2
= [x
+ 2 * step
[0], y
+ 2 * step
[1]];
647 if (V
.OnBoard(sq2
[0], sq2
[1]) && this.board
[sq2
[0]][sq2
[1]] == V
.EMPTY
) {
648 const sq1
= [x
+ step
[0], y
+ step
[1]];
650 this.board
[sq1
[0]][sq1
[1]] != V
.EMPTY
&&
651 this.getColor(sq1
[0], sq1
[1]) == color
&&
652 this.getPiece(sq1
[0], sq1
[1]) == V
.QUEEN
&&
653 !this.isImmobilized(sq1
)
662 isAttackedByKing([x
, y
], color
) {
663 for (let step
of V
.steps
[V
.KNIGHT
]) {
664 let rx
= x
+ step
[0],
665 // Circular board for king-knight:
666 ry
= (y
+ step
[1]) % V
.size
.y
;
669 this.getPiece(rx
, ry
) === V
.KING
&&
670 this.getColor(rx
, ry
) == color
&&
671 !this.isImmobilized([rx
, ry
])
679 isAttackedByGuard(sq
, color
) {
680 return super.isAttackedBySlideNJump(
681 sq
, color
, V
.GUARD
, V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), 1);
684 getNextMageCheck(step
) {
686 if (step
[1] == 1) return [[1, 1], [-1, 1]];
687 return [[-1, -1], [1, -1]];
689 if (step
[0] == -1) return [[-1, -1], [-1, 1]];
690 return [[1, 1], [1, -1]];
693 isAttackedByMage([x
, y
], color
) {
694 for (let step
of V
.steps
[V
.BISHOP
]) {
695 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
698 this.board
[i
][j
] != V
.EMPTY
&&
699 this.getColor(i
, j
) == color
&&
700 this.getPiece(i
, j
) == V
.MAGE
705 for (let step
of V
.steps
[V
.ROOK
]) {
706 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
707 const stepM
= this.getNextMageCheck(step
);
708 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
709 for (let s
of stepM
) {
710 const [ii
, jj
] = [i
+ s
[0], j
+ s
[1]];
713 this.board
[ii
][jj
] != V
.EMPTY
&&
714 this.getColor(ii
, jj
) == color
&&
715 this.getPiece(ii
, jj
) == V
.MAGE
728 const color
= this.turn
;
729 const getScoreLost
= () => {
731 return color
== "w" ? "0-1" : "1-0";
733 if (!this.atLeastOneMove()) {
734 // No valid move: I lose or draw
735 if (this.underCheck(color
)) return getScoreLost();
738 // I lose also if no pieces left (except king)
740 outerLoop: for (let i
=0; i
<V
.size
.x
; i
++) {
741 for (let j
=0; j
<V
.size
.y
; j
++) {
743 this.board
[i
][j
] != V
.EMPTY
&&
744 this.getColor(i
, j
) == color
&&
745 this.getPiece(i
,j
) != V
.KING
751 if (piecesLeft
== 0) return getScoreLost();
752 // Check if my palace is invaded:
753 const pX
= (color
== 'w' ? 10 : 0);
754 const oppCol
= V
.GetOppCol(color
);
756 this.board
[pX
][3] != V
.EMPTY
&&
757 this.getColor(pX
, 3) == oppCol
&&
758 this.board
[pX
][4] != V
.EMPTY
&&
759 this.getColor(pX
, 4) == oppCol
761 return getScoreLost();
766 static GenRandInitFen() {
767 // Always deterministic:
769 "xxx2xxx/1g1qk1g1/1bnmrnb1/dppppppd/8/8/8/" +
770 "DPPPPPPD/1BNMRNB1/1G1QK1G1/xxx2xxx w 0"
774 static get VALUES() {
788 static get SEARCH_DEPTH() {
794 for (let i
= 0; i
< V
.size
.x
; i
++) {
795 for (let j
= 0; j
< V
.size
.y
; j
++) {
796 if (![V
.EMPTY
,V
.NOTHING
].includes(this.board
[i
][j
])) {
797 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
798 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
806 const initialSquare
= V
.CoordsToSquare(move.start
);
807 const finalSquare
= V
.CoordsToSquare(move.end
);
808 if (move.appear
.length
== 0)
810 return initialSquare
+ "S";
811 let notation
= undefined;
812 if (move.appear
[0].p
== V
.PAWN
) {
813 // Pawn: generally ambiguous short notation, so we use full description
814 notation
= "P" + initialSquare
+ finalSquare
;
815 } else if (move.appear
[0].p
== V
.KING
)
816 notation
= "K" + (move.vanish
.length
> 1 ? "x" : "") + finalSquare
;
817 else notation
= move.appear
[0].p
.toUpperCase() + finalSquare
;
818 // Add a capture mark (not describing what is captured...):
819 if (move.vanish
.length
> 1 && move.appear
[0].p
!= V
.KING
) notation
+= "X";