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