Commit | Line | Data |
---|---|---|
173f11dc BA |
1 | import { ChessRules, PiPo, Move } from "@/base_rules"; |
2 | import { randInt } from "@/utils/alea"; | |
3 | ||
4 | export class PacosakoRules extends ChessRules { | |
5 | ||
6 | static get IMAGE_EXTENSION() { | |
7 | return ".png"; | |
8 | } | |
9 | ||
10 | // Unions (left = white if upperCase, black otherwise) | |
11 | static get UNIONS() { | |
12 | return { | |
13 | a: ['p', 'p'], | |
14 | c: ['p', 'r'], | |
15 | d: ['p', 'n'], | |
16 | e: ['p', 'b'], | |
17 | f: ['p', 'q'], | |
18 | g: ['p', 'k'], | |
19 | h: ['r', 'r'], | |
20 | i: ['r', 'n'], | |
21 | j: ['r', 'b'], | |
22 | l: ['r', 'q'], | |
23 | m: ['r', 'k'], | |
24 | o: ['n', 'n'], | |
25 | s: ['n', 'b'], | |
26 | t: ['n', 'q'], | |
27 | u: ['n', 'k'], | |
28 | v: ['b', 'b'], | |
29 | w: ['b', 'q'], | |
30 | x: ['b', 'k'], | |
31 | y: ['q', 'q'], | |
32 | z: ['q', 'k'] | |
33 | }; | |
34 | } | |
35 | ||
36 | static IsGoodPosition(position) { | |
37 | if (position.length == 0) return false; | |
38 | const rows = position.split("/"); | |
39 | if (rows.length != V.size.x) return false; | |
40 | let kingSymb = ['k', 'g', 'm', 'u', 'x']; | |
41 | let kings = { 'k': 0, 'K': 0 }; | |
42 | for (let row of rows) { | |
43 | let sumElts = 0; | |
44 | for (let i = 0; i < row.length; i++) { | |
45 | const lowR = row[i].toLowerCase | |
46 | if (!!(row[i].toLowerCase().match(/[a-z]/))) { | |
47 | sumElts++; | |
48 | if (kingSymb.includes(row[i])) kings['k']++; | |
49 | else if (kingSymb.some(s => row[i] == s.toUpperCase())) kings['K']++; | |
50 | } | |
51 | else { | |
52 | const num = parseInt(row[i], 10); | |
53 | if (isNaN(num) || num <= 0) return false; | |
54 | sumElts += num; | |
55 | } | |
56 | } | |
57 | if (sumElts != V.size.y) return false; | |
58 | } | |
59 | // Both kings should be on board. Exactly one per color. | |
60 | if (Object.values(kings).some(v => v != 1)) return false; | |
61 | return true; | |
62 | } | |
63 | ||
64 | getPpath(b) { | |
65 | return "Pacosako/" + b; | |
66 | } | |
67 | ||
68 | getPPath(m) { | |
69 | if (ChessRules.PIECES.includes(m.appear[0].p)) return super.getPPpath(m); | |
70 | // For an union, show only relevant piece: | |
71 | // The color must be deduced from the move: reaching final rank of who? | |
72 | const color = (m.appear[0].x == 0 ? 'b' : 'w'); | |
73 | const up = this.getUnionPieces(color, m.appear[0].p); | |
74 | return color + up[color]; | |
75 | } | |
76 | ||
77 | canTake([x1, y1], [x2, y2]) { | |
78 | const c1 = this.getColor(x1, y1); | |
79 | const c2 = this.getColor(x2, y2); | |
80 | return (c1 != 'u' && c2 != c1); | |
81 | } | |
82 | ||
83 | canIplay(side, [x, y]) { | |
84 | return this.turn == side && this.getColor(x, y) != V.GetOppCol(side); | |
85 | } | |
86 | ||
87 | scanKings(fen) { | |
88 | this.kingPos = { w: [-1, -1], b: [-1, -1] }; | |
89 | const fenRows = V.ParseFen(fen).position.split("/"); | |
90 | const startRow = { 'w': V.size.x - 1, 'b': 0 }; | |
91 | const kingSymb = ['k', 'g', 'm', 'u', 'x']; | |
92 | for (let i = 0; i < fenRows.length; i++) { | |
93 | let k = 0; | |
94 | for (let j = 0; j < fenRows[i].length; j++) { | |
95 | const c = fenRows[i].charAt(j); | |
96 | if (kingSymb.includes(c)) | |
97 | this.kingPos["b"] = [i, k]; | |
98 | else if (kingSymb.some(s => c == s.toUpperCase())) | |
99 | this.kingPos["w"] = [i, k]; | |
100 | else { | |
101 | const num = parseInt(fenRows[i].charAt(j), 10); | |
102 | if (!isNaN(num)) k += num - 1; | |
103 | } | |
104 | k++; | |
105 | } | |
106 | } | |
107 | } | |
108 | ||
109 | setOtherVariables(fen) { | |
110 | super.setOtherVariables(fen); | |
111 | // Stack of "last move" only for intermediate chaining | |
112 | this.lastMoveEnd = [null]; | |
113 | } | |
114 | ||
115 | getColor(i, j) { | |
116 | const p = this.board[i][j].charAt(1); | |
117 | if (ChessRules.PIECES.includes(p)) return super.getColor(i, j); | |
118 | return 'u'; //union | |
119 | } | |
120 | ||
121 | getPiece(i, j, color) { | |
122 | const p = this.board[i][j].charAt(1); | |
173f11dc BA |
123 | if (ChessRules.PIECES.includes(p)) return p; |
124 | const c = this.board[i][j].charAt(0); | |
125 | // NOTE: this.turn == HACK, but should work... | |
126 | color = color || this.turn; | |
127 | return V.UNIONS[p][c == color ? 0 : 1]; | |
128 | } | |
129 | ||
130 | getUnionPieces(color, code) { | |
131 | const pieces = V.UNIONS[code]; | |
132 | return { | |
133 | w: pieces[color == 'w' ? 0 : 1], | |
134 | b: pieces[color == 'b' ? 0 : 1] | |
135 | }; | |
136 | } | |
137 | ||
138 | getUnionCode(p1, p2) { | |
139 | let uIdx = ( | |
140 | Object.values(V.UNIONS).findIndex(v => v[0] == p1 && v[1] == p2) | |
141 | ); | |
142 | const c = (uIdx >= 0 ? 'w' : 'b'); | |
143 | if (uIdx == -1) { | |
144 | uIdx = ( | |
145 | Object.values(V.UNIONS).findIndex(v => v[0] == p2 && v[1] == p1) | |
146 | ); | |
147 | } | |
148 | return { c: c, p: Object.keys(V.UNIONS)[uIdx] }; | |
149 | } | |
150 | ||
151 | getBasicMove([sx, sy], [ex, ey], tr) { | |
152 | const initColor = this.board[sx][sy].charAt(0); | |
153 | const initPiece = this.board[sx][sy].charAt(1); | |
154 | // 4 cases : moving | |
155 | // - union to free square (other cases are illegal: return null) | |
156 | // - normal piece to free square, | |
157 | // to enemy normal piece, or | |
158 | // to union (releasing our piece) | |
159 | let mv = new Move({ | |
160 | vanish: [ | |
161 | new PiPo({ | |
162 | x: sx, | |
163 | y: sy, | |
164 | c: initColor, | |
165 | p: initPiece | |
166 | }) | |
167 | ], | |
168 | end: { x: ex, y: ey } | |
169 | }); | |
170 | // Treat free square cases first: | |
171 | if (this.board[ex][ey] == V.EMPTY) { | |
172 | mv.appear = [ | |
173 | new PiPo({ | |
174 | x: ex, | |
175 | y: ey, | |
176 | c: initColor, | |
177 | p: !!tr ? tr.p : initPiece | |
178 | }) | |
179 | ]; | |
180 | return mv; | |
181 | } | |
182 | // Now the two cases with union / release: | |
183 | const destColor = this.board[ex][ey].charAt(0); | |
184 | const destPiece = this.board[ex][ey].charAt(1); | |
185 | mv.vanish.push( | |
186 | new PiPo({ | |
187 | x: ex, | |
188 | y: ey, | |
189 | c: destColor, | |
190 | p: destPiece | |
191 | }) | |
192 | ); | |
193 | if (ChessRules.PIECES.includes(destPiece)) { | |
194 | // Normal piece: just create union | |
195 | const cp = this.getUnionCode(!!tr ? tr.p : initPiece, destPiece); | |
196 | mv.appear = [ | |
197 | new PiPo({ | |
198 | x: ex, | |
199 | y: ey, | |
200 | c: cp.c, | |
201 | p: cp.p | |
202 | }) | |
203 | ]; | |
204 | return mv; | |
205 | } | |
206 | // Releasing a piece in an union: keep track of released piece | |
207 | const up = this.getUnionPieces(destColor, destPiece); | |
208 | const c = this.turn; | |
209 | const oppCol = V.GetOppCol(c); | |
210 | const cp = this.getUnionCode(!!tr ? tr.p : initPiece, up[oppCol]) | |
211 | mv.appear = [ | |
212 | new PiPo({ | |
213 | x: ex, | |
214 | y: ey, | |
215 | c: cp.c, | |
216 | p: cp.p | |
217 | }) | |
218 | ]; | |
219 | mv.released = up[c]; | |
220 | return mv; | |
221 | } | |
222 | ||
223 | getPotentialMoves([x, y]) { | |
224 | const L = this.lastMoveEnd.length; | |
225 | const lm = this.lastMoveEnd[L-1]; | |
226 | let piece = null; | |
227 | if (!!lm) { | |
228 | if (x != lm.x || y != lm.y) return []; | |
229 | piece = lm.p; | |
230 | } | |
231 | if (!!piece) { | |
232 | var unionOnBoard = this.board[x][y]; | |
233 | this.board[x][y] = this.turn + piece; | |
234 | } | |
235 | let baseMoves = []; | |
236 | switch (piece || this.getPiece(x, y)) { | |
237 | case V.PAWN: | |
238 | baseMoves = this.getPotentialPawnMoves([x, y]); | |
239 | break; | |
240 | case V.ROOK: | |
241 | baseMoves = this.getPotentialRookMoves([x, y]); | |
242 | break; | |
243 | case V.KNIGHT: | |
244 | baseMoves = this.getPotentialKnightMoves([x, y]); | |
245 | break; | |
246 | case V.BISHOP: | |
247 | baseMoves = this.getPotentialBishopMoves([x, y]); | |
248 | break; | |
249 | case V.QUEEN: | |
250 | baseMoves = this.getPotentialQueenMoves([x, y]); | |
251 | break; | |
252 | case V.KING: | |
253 | baseMoves = this.getPotentialKingMoves([x, y]); | |
254 | break; | |
255 | } | |
256 | // When a pawn in an union reaches final rank with a non-standard | |
257 | // promotion move: apply promotion anyway | |
258 | let moves = []; | |
259 | baseMoves.forEach(m => { | |
260 | // (move to first rank, which is last rank for opponent [pawn]), should show promotion choices. | |
261 | //if (m. //bring enemy pawn to his first rank ==> union types involved... color... | |
262 | moves.push(m); //TODO | |
263 | }); | |
264 | if (!!piece) this.board[x][y] = unionOnBoard; | |
265 | return moves; | |
266 | } | |
267 | ||
268 | play(move) { | |
269 | this.epSquares.push(this.getEpSquare(move)); | |
270 | // Check if the move is the last of the turn: all cases except releases | |
271 | move.last = ( | |
272 | move.vanish.length == 1 || | |
273 | ChessRules.PIECES.includes(move.vanish[1].p) | |
274 | ); | |
275 | if (move.last) { | |
276 | // No more union releases available | |
277 | this.turn = V.GetOppCol(this.turn); | |
278 | this.movesCount++; | |
279 | this.lastMoveEnd.push(null); | |
280 | } | |
281 | else { | |
282 | const color = this.board[move.end.x][move.end.y].charAt(0); | |
283 | const oldUnion = this.board[move.end.x][move.end.y].charAt(1); | |
284 | const released = this.getUnionPieces(color, oldUnion)[this.turn]; | |
285 | this.lastMoveEnd.push(Object.assign({}, move.end, { p: released })); | |
286 | } | |
287 | V.PlayOnBoard(this.board, move); | |
3cf54395 | 288 | this.postPlay(move); |
173f11dc BA |
289 | } |
290 | ||
291 | undo(move) { | |
292 | this.epSquares.pop(); | |
293 | V.UndoOnBoard(this.board, move); | |
294 | this.lastMoveEnd.pop(); | |
295 | if (move.last) { | |
296 | this.turn = V.GetOppCol(this.turn); | |
297 | this.movesCount--; | |
298 | } | |
3cf54395 | 299 | this.postUndo(move); |
173f11dc BA |
300 | } |
301 | ||
302 | getCurrentScore() { | |
303 | // Check kings: if one is dancing, the side lost | |
304 | const [kpW, kpB] = [this.kingPos['w'], this.kingPos['b']]; | |
305 | if (this.board[kpB[0]][kpB[1]].charAt(1) != 'k') return "1-0"; | |
306 | if (this.board[kpW[0]][kpW[1]].charAt(1) != 'k') return "0-1"; | |
307 | return "*"; | |
308 | } | |
309 | ||
310 | getComputerMove() { | |
311 | let moves = this.getAllValidMoves(); | |
312 | if (moves.length == 0) return null; | |
313 | // Just play random moves (for now at least. TODO?) | |
314 | let mvArray = []; | |
315 | while (moves.length > 0) { | |
316 | const mv = moves[randInt(moves.length)]; | |
317 | mvArray.push(mv); | |
318 | this.play(mv); | |
319 | if (!mv.last) | |
320 | // A piece was just released from an union | |
321 | moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]); | |
322 | else break; | |
323 | } | |
324 | for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]); | |
325 | return (mvArray.length > 1 ? mvArray : mvArray[0]); | |
326 | } | |
327 | ||
328 | // NOTE: evalPosition() is wrong, but unused since bot plays at random | |
329 | ||
330 | getNotation(move) { | |
331 | // TODO: in case of enemy pawn promoted, add "=..." in the end | |
332 | return super.getNotation(move); | |
333 | } | |
334 | ||
335 | }; |