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