Commit | Line | Data |
---|---|---|
dbc79ee6 BA |
1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
2 | ||
3 | export class JangqiRules extends ChessRules { | |
4 | ||
5 | static get Monochrome() { | |
6 | return true; | |
7 | } | |
8 | ||
9 | static get Notoodark() { | |
10 | return true; | |
11 | } | |
12 | ||
13 | static get Lines() { | |
14 | let lines = []; | |
15 | // Draw all inter-squares lines, shifted: | |
16 | for (let i = 0; i < V.size.x; i++) | |
17 | lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]); | |
18 | for (let j = 0; j < V.size.y; j++) | |
19 | lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]); | |
20 | // Add palaces: | |
21 | lines.push([[0.5, 3.5], [2.5, 5.5]]); | |
22 | lines.push([[0.5, 5.5], [2.5, 3.5]]); | |
23 | lines.push([[9.5, 3.5], [7.5, 5.5]]); | |
24 | lines.push([[9.5, 5.5], [7.5, 3.5]]); | |
25 | return lines; | |
26 | } | |
27 | ||
28 | // No castle, but flag: bikjang | |
29 | static get HasCastle() { | |
30 | return false; | |
31 | } | |
32 | ||
33 | static get HasEnpassant() { | |
34 | return false; | |
35 | } | |
36 | ||
37 | static get LoseOnRepetition() { | |
38 | return true; | |
39 | } | |
40 | ||
41 | static get ELEPHANT() { | |
42 | return "e"; | |
43 | } | |
44 | ||
45 | static get CANNON() { | |
46 | return "c"; | |
47 | } | |
48 | ||
49 | static get ADVISOR() { | |
50 | return "a"; | |
51 | } | |
52 | ||
53 | static get PIECES() { | |
54 | return [V.PAWN, V.ROOK, V.KNIGHT, V.ELEPHANT, V.ADVISOR, V.KING, V.CANNON]; | |
55 | } | |
56 | ||
57 | getPpath(b) { | |
58 | return "Jiangqi/" + b; | |
59 | } | |
60 | ||
61 | static get size() { | |
62 | return { x: 10, y: 9}; | |
63 | } | |
64 | ||
65 | getPotentialMovesFrom(sq) { | |
66 | switch (this.getPiece(sq[0], sq[1])) { | |
67 | case V.PAWN: return this.getPotentialPawnMoves(sq); | |
68 | case V.ROOK: return this.getPotentialRookMoves(sq); | |
69 | case V.KNIGHT: return this.getPotentialKnightMoves(sq); | |
70 | case V.ELEPHANT: return this.getPotentialElephantMoves(sq); | |
71 | case V.ADVISOR: return this.getPotentialAdvisorMoves(sq); | |
72 | case V.KING: return this.getPotentialKingMoves(sq); | |
73 | case V.CANNON: return this.getPotentialCannonMoves(sq); | |
74 | } | |
75 | return []; //never reached | |
76 | } | |
77 | ||
78 | static IsGoodFlags(flags) { | |
79 | // bikjang status of last move + pass | |
80 | return !!flags.match(/^[0-2]{2,2}$/); | |
81 | } | |
82 | ||
83 | aggregateFlags() { | |
84 | return [this.bikjangFlag, this.passFlag]; | |
85 | } | |
86 | ||
87 | disaggregateFlags(flags) { | |
88 | this.bikjangFlag = flags[0]; | |
89 | this.passFlag = flags[1]; | |
90 | } | |
91 | ||
92 | getFlagsFen() { | |
93 | return this.bikjangFlag.toString() + this.passFlag.toString() | |
94 | } | |
95 | ||
96 | setFlags(fenflags) { | |
97 | this.bikjangFlag = parseInt(fenflags.charAt(0), 10); | |
98 | this.passFlag = parseInt(fenflags.charAt(1), 10); | |
99 | } | |
100 | ||
101 | setOtherVariables(fen) { | |
102 | super.setOtherVariables(fen); | |
103 | // Sub-turn is useful only at first move... | |
104 | this.subTurn = 1; | |
105 | } | |
106 | ||
107 | getPotentialMovesFrom([x, y]) { | |
108 | let moves = []; | |
109 | const c = this.getColor(x, y); | |
110 | const oppCol = V.GetOppCol(c); | |
111 | if (this.kingPos[c][0] == x && this.kingPos[c][1] == y) { | |
112 | // Add pass move (might be impossible if undercheck) | |
113 | moves.push( | |
114 | new Move({ | |
115 | appear: [], | |
116 | vanish: [], | |
117 | start: { x: this.kingPos[c][0], y: this.kingPos[c][1] }, | |
118 | end: { x: this.kingPos[oppCol][0], y: this.kingPos[oppCol][1] } | |
119 | }) | |
120 | ); | |
121 | } | |
122 | // TODO: next "if" is mutually exclusive with the block above | |
123 | if (this.movesCount <= 1) { | |
124 | const firstRank = (this.movesCount == 0 ? 9 : 0); | |
125 | const [initFile, destFile] = (this.subTurn == 1 ? [1, 2] : [7, 6]); | |
126 | // Only option is knight / elephant swap: | |
127 | if (x == firstRank && y == initFile) { | |
128 | moves.push( | |
129 | new Move({ | |
130 | appear: [ | |
131 | new PiPo({ | |
132 | x: x, | |
133 | y: destFile, | |
134 | c: c, | |
135 | p: V.KNIGHT | |
136 | }), | |
137 | new PiPo({ | |
138 | x: x, | |
139 | y: y, | |
140 | c: c, | |
141 | p: V.ELEPHANT | |
142 | }) | |
143 | ], | |
144 | vanish: [ | |
145 | new PiPo({ | |
146 | x: x, | |
147 | y: y, | |
148 | c: c, | |
149 | p: V.KNIGHT | |
150 | }), | |
151 | new PiPo({ | |
152 | x: x, | |
153 | y: destFile, | |
154 | c: c, | |
155 | p: V.ELEPHANT | |
156 | }) | |
157 | ], | |
158 | start: { x: x, y: y }, | |
159 | end: { x: x, y: destFile } | |
160 | }) | |
161 | ); | |
162 | } | |
163 | } | |
164 | else | |
165 | Array.prototype.push.apply(moves, super.getPotentialMovesFrom([x, y])); | |
166 | return moves; | |
167 | } | |
168 | ||
169 | getPotentialPawnMoves([x, y]) { | |
170 | const c = this.getColor(x, y); | |
171 | const oppCol = V.GetOppCol(c); | |
172 | const shiftX = (c == 'w' ? -1 : 1); | |
173 | const rank23 = (oppCol == 'w' ? [8, 7] : [1, 2]); | |
174 | let steps = [[shiftX, 0], [0, -1], [0, 1]]; | |
175 | // Diagonal moves inside enemy palace: | |
176 | if (y == 4 && x == rank23[0]) | |
177 | Array.prototype.push.apply(steps, [[shiftX, 1], [shiftX, -1]]); | |
178 | else if (x == rank23[1]) { | |
179 | if (y == 3) steps.push([shiftX, 1]); | |
180 | else if (y == 5) steps.push([shiftX, -1]); | |
181 | } | |
182 | return super.getSlideNJumpMoves([x, y], steps, "oneStep"); | |
183 | } | |
184 | ||
185 | knightStepsFromRookStep(step) { | |
186 | if (step[0] == 0) return [ [1, 2*step[1]], [-1, 2*step[1]] ]; | |
187 | return [ [2*step[0], 1], [2*step[0], -1] ]; | |
188 | } | |
189 | ||
190 | getPotentialKnightMoves([x, y]) { | |
191 | let steps = []; | |
192 | for (let rookStep of ChessRules.steps[V.ROOK]) { | |
193 | const [i, j] = [x + rookStep[0], y + rookStep[1]]; | |
194 | if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
195 | Array.prototype.push.apply(steps, | |
196 | // These moves might be impossible, but need to be checked: | |
197 | this.knightStepsFromRookStep(rookStep)); | |
198 | } | |
199 | } | |
200 | return super.getSlideNJumpMoves([x, y], steps, "oneStep"); | |
201 | } | |
202 | ||
203 | elephantStepsFromRookStep(step) { | |
204 | if (step[0] == 0) return [ [2, 3*step[1]], [-2, 3*step[1]] ]; | |
205 | return [ [3*step[0], 2], [3*step[0], -2] ]; | |
206 | } | |
207 | ||
208 | getPotentialElephantMoves([x, y]) { | |
209 | let steps = []; | |
210 | for (let rookStep of ChessRules.steps[V.ROOK]) { | |
211 | const eSteps = this.elephantStepsFromRookStep(rookStep); | |
212 | const [i, j] = [x + rookStep[0], y + rookStep[1]]; | |
213 | if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
214 | // Check second crossing: | |
215 | const knightSteps = this.knightStepsFromRookStep(rookStep); | |
216 | for (let k of [0, 1]) { | |
217 | const [ii, jj] = [x + knightSteps[k][0], y + knightSteps[k][1]]; | |
218 | if (V.OnBoard(ii, jj) && this.board[ii][jj] == V.EMPTY) | |
219 | steps.push(eSteps[k]); //ok: same ordering | |
220 | } | |
221 | } | |
222 | } | |
223 | return super.getSlideNJumpMoves([x, y], steps, "oneStep"); | |
224 | } | |
225 | ||
226 | palacePeopleMoves([x, y]) { | |
227 | const c = this.getColor(x, y); | |
228 | let steps = []; | |
229 | // Orthogonal steps: | |
230 | if (x < (c == 'w' ? 9 : 2)) steps.push([1, 0]); | |
231 | if (x > (c == 'w' ? 7 : 0)) steps.push([-1, 0]); | |
232 | if (y > 3) steps.push([0, -1]); | |
233 | if (y < 5) steps.push([0, 1]); | |
234 | // Diagonal steps, if in the middle or corner: | |
235 | if ( | |
236 | y != 4 && | |
237 | ( | |
238 | (c == 'w' && x != 8) || | |
239 | (c == 'b' && x != 1) | |
240 | ) | |
241 | ) { | |
242 | // In a corner: maximum one diagonal step available | |
243 | let step = null; | |
244 | const direction = (c == 'w' ? -1 : 1); | |
245 | if ((c == 'w' && x == 9) || (c == 'b' && x == 0)) { | |
246 | // On first line | |
247 | if (y == 3) step = [direction, 1]; | |
248 | else step = [direction, -1]; | |
249 | } | |
250 | else if ((c == 'w' && x == 7) || (c == 'b' && x == 2)) { | |
251 | // On third line | |
252 | if (y == 3) step = [-direction, 1]; | |
253 | else step = [-direction, -1]; | |
254 | } | |
255 | steps.push(step); | |
256 | } | |
257 | else if ( | |
258 | y == 4 && | |
259 | ( | |
260 | (c == 'w' && x == 8) || | |
261 | (c == 'b' && x == 1) | |
262 | ) | |
263 | ) { | |
264 | // At the middle: all directions available | |
265 | Array.prototype.push.apply(steps, ChessRules.steps[V.BISHOP]); | |
266 | } | |
267 | return super.getSlideNJumpMoves([x, y], steps, "oneStep"); | |
268 | } | |
269 | ||
270 | getPotentialAdvisorMoves(sq) { | |
271 | return this.palacePeopleMoves(sq); | |
272 | } | |
273 | ||
274 | getPotentialKingMoves(sq) { | |
275 | return this.palacePeopleMoves(sq); | |
276 | } | |
277 | ||
278 | getPotentialRookMoves([x, y]) { | |
279 | let moves = super.getPotentialRookMoves([x, y]); | |
280 | if ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) { | |
281 | // In a corner of a palace: move along diagonal | |
282 | const step = [[0, 7].includes(x) ? 1 : -1, 4 - y]; | |
283 | const oppCol = V.GetOppCol(this.getColor(x, y)); | |
284 | for (let i of [1, 2]) { | |
285 | const [xx, yy] = [x + i * step[0], y + i * step[1]]; | |
286 | if (this.board[xx][yy] == V.EMPTY) | |
287 | moves.push(this.getBasicMove([x, y], [xx, yy])); | |
288 | else { | |
289 | if (this.getColor(xx, yy) == oppCol) | |
290 | moves.push(this.getBasicMove([x, y], [xx, yy])); | |
291 | break; | |
292 | } | |
293 | } | |
294 | } | |
295 | else if (y == 4 && [1, 8].includes(x)) { | |
296 | // In the middle of a palace: 4 one-diagonal-step to check | |
297 | Array.prototype.push.apply( | |
298 | moves, | |
299 | super.getSlideNJumpMoves([x, y], | |
300 | ChessRules.steps[V.BISHOP], | |
301 | "oneStep") | |
302 | ); | |
303 | } | |
304 | return moves; | |
305 | } | |
306 | ||
307 | // NOTE: (mostly) duplicated from Shako (TODO?) | |
308 | getPotentialCannonMoves([x, y]) { | |
309 | const oppCol = V.GetOppCol(this.turn); | |
310 | let moves = []; | |
311 | // Look in every direction until an obstacle (to jump) is met | |
312 | for (const step of V.steps[V.ROOK]) { | |
313 | let i = x + step[0]; | |
314 | let j = y + step[1]; | |
315 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
316 | i += step[0]; | |
317 | j += step[1]; | |
318 | } | |
319 | // Then, search for an enemy (if jumped piece isn't a cannon) | |
320 | if (V.OnBoard(i, j) && this.getPiece(i, j) != V.CANNON) { | |
321 | i += step[0]; | |
322 | j += step[1]; | |
323 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
324 | moves.push(this.getBasicMove([x, y], [i, j])); | |
325 | i += step[0]; | |
326 | j += step[1]; | |
327 | } | |
328 | if ( | |
329 | V.OnBoard(i, j) && | |
330 | this.getColor(i, j) == oppCol && | |
331 | this.getPiece(i, j) != V.CANNON | |
332 | ) { | |
333 | moves.push(this.getBasicMove([x, y], [i, j])); | |
334 | } | |
335 | } | |
336 | } | |
337 | if ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) { | |
338 | // In a corner of a palace: hop over next obstacle if possible | |
339 | const step = [[0, 7].includes(x) ? 1 : -1, 4 - y]; | |
340 | const [x1, y1] = [x + step[0], y + step[1]]; | |
341 | const [x2, y2] = [x + 2 * step[0], y + 2 * step[1]]; | |
342 | if ( | |
343 | this.board[x1][y1] != V.EMPTY && | |
344 | this.getPiece(x1, y1) != V.CANNON && | |
345 | ( | |
346 | this.board[x2][y2] == V.EMPTY || | |
347 | ( | |
348 | this.getColor(x2, y2) == oppCol && | |
349 | this.getPiece(x2, y2) != V.CANNON | |
350 | ) | |
351 | ) | |
352 | ) { | |
353 | moves.push(this.getBasicMove([x, y], [x2, y2])); | |
354 | } | |
355 | } | |
356 | return moves; | |
357 | } | |
358 | ||
359 | // (King) Never attacked by advisor, since it stays in the palace | |
360 | isAttacked(sq, color) { | |
361 | return ( | |
362 | this.isAttackedByPawn(sq, color) || | |
363 | this.isAttackedByRook(sq, color) || | |
364 | this.isAttackedByKnight(sq, color) || | |
365 | this.isAttackedByElephant(sq, color) || | |
366 | this.isAttackedByCannon(sq, color) | |
367 | ); | |
368 | } | |
369 | ||
370 | onPalaceDiagonal([x, y]) { | |
371 | return ( | |
372 | (y == 4 && [1, 8].includes(x)) || | |
373 | ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) | |
374 | ); | |
375 | } | |
376 | ||
377 | isAttackedByPawn([x, y], color) { | |
378 | const shiftX = (color == 'w' ? 1 : -1); //shift from king | |
379 | if (super.isAttackedBySlideNJump( | |
380 | [x, y], color, V.PAWN, [[shiftX, 0], [0, 1], [0, -1]], "oneStep") | |
381 | ) { | |
382 | return true; | |
383 | } | |
384 | if (this.onPalaceDiagonal([x, y])) { | |
385 | for (let yStep of [-1, 1]) { | |
386 | const [xx, yy] = [x + shiftX, y + yStep]; | |
387 | if ( | |
388 | this.onPalaceDiagonal([xx,yy]) && | |
389 | this.board[xx][yy] != V.EMPTY && | |
390 | this.getColor(xx, yy) == color && | |
391 | this.getPiece(xx, yy) == V.PAWN | |
392 | ) { | |
393 | return true; | |
394 | } | |
395 | } | |
396 | } | |
397 | return false; | |
398 | } | |
399 | ||
400 | knightStepsFromBishopStep(step) { | |
401 | return [ [2*step[0], step[1]], [step[0], 2*step[1]] ]; | |
402 | } | |
403 | ||
404 | isAttackedByKnight([x, y], color) { | |
405 | // Check bishop steps: if empty, look continuation knight step | |
406 | let steps = []; | |
407 | for (let s of ChessRules.steps[V.BISHOP]) { | |
408 | const [i, j] = [x + s[0], y + s[1]]; | |
409 | if ( | |
410 | V.OnBoard(i, j) && | |
411 | this.board[i][j] == V.EMPTY | |
412 | ) { | |
413 | Array.prototype.push.apply(steps, this.knightStepsFromBishopStep(s)); | |
414 | } | |
415 | } | |
416 | return ( | |
417 | super.isAttackedBySlideNJump([x, y], color, V.KNIGHT, steps, "oneStep") | |
418 | ); | |
419 | } | |
420 | ||
421 | elephantStepsFromBishopStep(step) { | |
422 | return [ [3*step[0], 2*step[1]], [2*step[0], 3*step[1]] ]; | |
423 | } | |
424 | ||
425 | isAttackedByElephant([x, y], color) { | |
426 | // Check bishop steps: if empty, look continuation elephant step | |
427 | let steps = []; | |
428 | for (let s of ChessRules.steps[V.BISHOP]) { | |
429 | const [i1, j1] = [x + s[0], y + s[1]]; | |
430 | const [i2, j2] = [x + 2*s[0], y + 2*s[1]]; | |
431 | if ( | |
432 | V.OnBoard(i2, j2) && this.board[i2][j2] == V.EMPTY && | |
433 | V.OnBoard(i1, j1) && this.board[i1][j1] == V.EMPTY | |
434 | ) { | |
435 | Array.prototype.push.apply(steps, this.elephantStepsFromBishopStep(s)); | |
436 | } | |
437 | } | |
438 | return ( | |
439 | super.isAttackedBySlideNJump([x, y], color, V.ELEPHANT, steps, "oneStep") | |
440 | ); | |
441 | } | |
442 | ||
443 | isAttackedByRook([x, y], color) { | |
444 | if (super.isAttackedByRook([x, y], color)) return true; | |
445 | // Also check diagonals, if inside palace | |
446 | if (this.onPalaceDiagonal([x, y])) { | |
447 | // TODO: next scan is clearly suboptimal | |
448 | for (let s of ChessRules.steps[V.BISHOP]) { | |
449 | for (let i of [1, 2]) { | |
450 | const [xx, yy] = [x + i * s[0], y + i * s[1]]; | |
451 | if ( | |
452 | V.OnBoard(xx, yy) && | |
453 | this.onPalaceDiagonal([xx, yy]) | |
454 | ) { | |
455 | if (this.board[xx][yy] != V.EMPTY) { | |
456 | if ( | |
457 | this.getColor(xx, yy) == color && | |
458 | this.getPiece(xx, yy) == V.ROOK | |
459 | ) { | |
460 | return true; | |
461 | } | |
462 | break; | |
463 | } | |
464 | } | |
465 | else continue; | |
466 | } | |
467 | } | |
468 | } | |
469 | return false; | |
470 | } | |
471 | ||
472 | // NOTE: (mostly) duplicated from Shako (TODO?) | |
473 | isAttackedByCannon([x, y], color) { | |
474 | // Reversed process: is there an obstacle in line, | |
475 | // and a cannon next in the same line? | |
476 | for (const step of V.steps[V.ROOK]) { | |
477 | let [i, j] = [x+step[0], y+step[1]]; | |
478 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
479 | i += step[0]; | |
480 | j += step[1]; | |
481 | } | |
482 | if (V.OnBoard(i, j) && this.getPiece(i, j) != V.CANNON) { | |
483 | // Keep looking in this direction | |
484 | i += step[0]; | |
485 | j += step[1]; | |
486 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { | |
487 | i += step[0]; | |
488 | j += step[1]; | |
489 | } | |
490 | if ( | |
491 | V.OnBoard(i, j) && | |
492 | this.getPiece(i, j) == V.CANNON && | |
493 | this.getColor(i, j) == color | |
494 | ) { | |
495 | return true; | |
496 | } | |
497 | } | |
498 | } | |
499 | return false; | |
500 | } | |
501 | ||
502 | getCurrentScore() { | |
503 | if ([this.bikjangFlag, this.passFlag].includes(2)) return "1/2"; | |
504 | const color = this.turn; | |
505 | // super.atLeastOneMove() does not consider passing (OK) | |
506 | if (this.underCheck(color) && !super.atLeastOneMove()) | |
507 | return (color == "w" ? "0-1" : "1-0"); | |
508 | return "*"; | |
509 | } | |
510 | ||
511 | static get VALUES() { | |
512 | return { | |
513 | p: 2, | |
514 | r: 13, | |
515 | n: 5, | |
516 | e: 3, | |
517 | a: 3, | |
518 | c: 7, | |
519 | k: 1000 | |
520 | }; | |
521 | } | |
522 | ||
523 | static get SEARCH_DEPTH() { | |
524 | return 2; | |
525 | } | |
526 | ||
527 | static GenRandInitFen() { | |
528 | // No randomization here (but initial setup choice) | |
529 | return ( | |
530 | "rnea1aenr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNEA1AENR w 0 00" | |
531 | ); | |
532 | } | |
533 | ||
534 | play(move) { | |
535 | move.subTurn = this.subTurn; //much easier | |
536 | if (this.movesCount >= 2 || this.subTurn == 2 || move.vanish.length == 0) { | |
537 | this.turn = V.GetOppCol(this.turn); | |
538 | this.subTurn = 1; | |
539 | this.movesCount++; | |
540 | } | |
541 | else this.subTurn = 2; | |
542 | move.flags = JSON.stringify(this.aggregateFlags()); | |
543 | V.PlayOnBoard(this.board, move); | |
544 | this.postPlay(move); | |
545 | } | |
546 | ||
547 | postPlay(move) { | |
548 | if (move.vanish.length > 0) super.postPlay(move); | |
549 | else if (this.movesCount > 2) this.passFlag++; | |
550 | // Update bikjang flag | |
551 | if (this.kingPos['w'][1] == this.kingPos['b'][1]) { | |
552 | const y = this.kingPos['w'][1]; | |
553 | let bikjang = true; | |
554 | for (let x = this.kingPos['b'][0] + 1; x < this.kingPos['w'][0]; x++) { | |
555 | if (this.board[x][y] != V.EMPTY) { | |
556 | bikjang = false; | |
557 | break; | |
558 | } | |
559 | } | |
560 | if (bikjang) this.bikjangFlag++; | |
561 | else this.bikjangFlag = 0; | |
562 | } | |
563 | else this.bikjangFlag = 0; | |
564 | } | |
565 | ||
566 | undo(move) { | |
567 | this.disaggregateFlags(JSON.parse(move.flags)); | |
568 | V.UndoOnBoard(this.board, move); | |
569 | this.postUndo(move); | |
570 | if (this.movesCount >= 2 || this.subTurn == 1 || move.vanish.length == 0) { | |
571 | this.turn = V.GetOppCol(this.turn); | |
572 | this.movesCount--; | |
573 | } | |
574 | this.subTurn = move.subTurn; | |
575 | } | |
576 | ||
577 | postUndo(move) { | |
578 | if (move.vanish.length > 0) super.postUndo(move); | |
579 | } | |
580 | ||
581 | getNotation(move) { | |
582 | if (move.vanish.length == 0) return "pass"; | |
583 | if (move.appear.length == 2) return "S"; //"swap" | |
584 | let notation = super.getNotation(move); | |
585 | if (move.vanish.length == 2 && move.vanish[0].p == V.PAWN) | |
586 | notation = "P" + notation.substr(1); | |
587 | return notation; | |
588 | } | |
589 | ||
590 | }; |