Slightly less buggish Paco-Sako
[vchess.git] / client / src / variants / Pacosako.js
CommitLineData
173f11dc
BA
1import { ChessRules, PiPo, Move } from "@/base_rules";
2import { randInt } from "@/utils/alea";
3
4export 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};