Early draft of Paco-Sako + some fixes
[vchess.git] / client / src / variants / Pacosako.js
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);
123
124 console.log(p);
125
126 if (ChessRules.PIECES.includes(p)) return p;
127 const c = this.board[i][j].charAt(0);
128 // NOTE: this.turn == HACK, but should work...
129 color = color || this.turn;
130 return V.UNIONS[p][c == color ? 0 : 1];
131 }
132
133 getUnionPieces(color, code) {
134 const pieces = V.UNIONS[code];
135 return {
136 w: pieces[color == 'w' ? 0 : 1],
137 b: pieces[color == 'b' ? 0 : 1]
138 };
139 }
140
141 getUnionCode(p1, p2) {
142 let uIdx = (
143 Object.values(V.UNIONS).findIndex(v => v[0] == p1 && v[1] == p2)
144 );
145 const c = (uIdx >= 0 ? 'w' : 'b');
146 if (uIdx == -1) {
147 uIdx = (
148 Object.values(V.UNIONS).findIndex(v => v[0] == p2 && v[1] == p1)
149 );
150 }
151 return { c: c, p: Object.keys(V.UNIONS)[uIdx] };
152 }
153
154 getBasicMove([sx, sy], [ex, ey], tr) {
155 const initColor = this.board[sx][sy].charAt(0);
156 const initPiece = this.board[sx][sy].charAt(1);
157 // 4 cases : moving
158 // - union to free square (other cases are illegal: return null)
159 // - normal piece to free square,
160 // to enemy normal piece, or
161 // to union (releasing our piece)
162 let mv = new Move({
163 vanish: [
164 new PiPo({
165 x: sx,
166 y: sy,
167 c: initColor,
168 p: initPiece
169 })
170 ],
171 end: { x: ex, y: ey }
172 });
173 // Treat free square cases first:
174 if (this.board[ex][ey] == V.EMPTY) {
175 mv.appear = [
176 new PiPo({
177 x: ex,
178 y: ey,
179 c: initColor,
180 p: !!tr ? tr.p : initPiece
181 })
182 ];
183 return mv;
184 }
185 // Now the two cases with union / release:
186 const destColor = this.board[ex][ey].charAt(0);
187 const destPiece = this.board[ex][ey].charAt(1);
188 mv.vanish.push(
189 new PiPo({
190 x: ex,
191 y: ey,
192 c: destColor,
193 p: destPiece
194 })
195 );
196 if (ChessRules.PIECES.includes(destPiece)) {
197 // Normal piece: just create union
198 const cp = this.getUnionCode(!!tr ? tr.p : initPiece, destPiece);
199 mv.appear = [
200 new PiPo({
201 x: ex,
202 y: ey,
203 c: cp.c,
204 p: cp.p
205 })
206 ];
207 return mv;
208 }
209 // Releasing a piece in an union: keep track of released piece
210 const up = this.getUnionPieces(destColor, destPiece);
211 const c = this.turn;
212 const oppCol = V.GetOppCol(c);
213 const cp = this.getUnionCode(!!tr ? tr.p : initPiece, up[oppCol])
214 mv.appear = [
215 new PiPo({
216 x: ex,
217 y: ey,
218 c: cp.c,
219 p: cp.p
220 })
221 ];
222 mv.released = up[c];
223 return mv;
224 }
225
226 getPotentialMoves([x, y]) {
227 const L = this.lastMoveEnd.length;
228 const lm = this.lastMoveEnd[L-1];
229 let piece = null;
230 if (!!lm) {
231 if (x != lm.x || y != lm.y) return [];
232 piece = lm.p;
233 }
234 if (!!piece) {
235 var unionOnBoard = this.board[x][y];
236 this.board[x][y] = this.turn + piece;
237 }
238 let baseMoves = [];
239 switch (piece || this.getPiece(x, y)) {
240 case V.PAWN:
241 baseMoves = this.getPotentialPawnMoves([x, y]);
242 break;
243 case V.ROOK:
244 baseMoves = this.getPotentialRookMoves([x, y]);
245 break;
246 case V.KNIGHT:
247 baseMoves = this.getPotentialKnightMoves([x, y]);
248 break;
249 case V.BISHOP:
250 baseMoves = this.getPotentialBishopMoves([x, y]);
251 break;
252 case V.QUEEN:
253 baseMoves = this.getPotentialQueenMoves([x, y]);
254 break;
255 case V.KING:
256 baseMoves = this.getPotentialKingMoves([x, y]);
257 break;
258 }
259 // When a pawn in an union reaches final rank with a non-standard
260 // promotion move: apply promotion anyway
261 let moves = [];
262 baseMoves.forEach(m => {
263 // (move to first rank, which is last rank for opponent [pawn]), should show promotion choices.
264 //if (m. //bring enemy pawn to his first rank ==> union types involved... color...
265 moves.push(m); //TODO
266 });
267 if (!!piece) this.board[x][y] = unionOnBoard;
268 return moves;
269 }
270
271 play(move) {
272 this.epSquares.push(this.getEpSquare(move));
273 // Check if the move is the last of the turn: all cases except releases
274 move.last = (
275 move.vanish.length == 1 ||
276 ChessRules.PIECES.includes(move.vanish[1].p)
277 );
278 if (move.last) {
279 // No more union releases available
280 this.turn = V.GetOppCol(this.turn);
281 this.movesCount++;
282 this.lastMoveEnd.push(null);
283 }
284 else {
285 const color = this.board[move.end.x][move.end.y].charAt(0);
286 const oldUnion = this.board[move.end.x][move.end.y].charAt(1);
287 const released = this.getUnionPieces(color, oldUnion)[this.turn];
288 this.lastMoveEnd.push(Object.assign({}, move.end, { p: released }));
289 }
290 V.PlayOnBoard(this.board, move);
291 }
292
293 undo(move) {
294 this.epSquares.pop();
295 V.UndoOnBoard(this.board, move);
296 this.lastMoveEnd.pop();
297 if (move.last) {
298 this.turn = V.GetOppCol(this.turn);
299 this.movesCount--;
300 }
301 }
302
303 getCurrentScore() {
304 // Check kings: if one is dancing, the side lost
305 const [kpW, kpB] = [this.kingPos['w'], this.kingPos['b']];
306 if (this.board[kpB[0]][kpB[1]].charAt(1) != 'k') return "1-0";
307 if (this.board[kpW[0]][kpW[1]].charAt(1) != 'k') return "0-1";
308 return "*";
309 }
310
311 getComputerMove() {
312 let moves = this.getAllValidMoves();
313 if (moves.length == 0) return null;
314 // Just play random moves (for now at least. TODO?)
315 let mvArray = [];
316 while (moves.length > 0) {
317 const mv = moves[randInt(moves.length)];
318 mvArray.push(mv);
319 this.play(mv);
320 if (!mv.last)
321 // A piece was just released from an union
322 moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]);
323 else break;
324 }
325 for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
326 return (mvArray.length > 1 ? mvArray : mvArray[0]);
327 }
328
329 // NOTE: evalPosition() is wrong, but unused since bot plays at random
330
331 getNotation(move) {
332 // TODO: in case of enemy pawn promoted, add "=..." in the end
333 return super.getNotation(move);
334 }
335
336 };