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