Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Mesmer.js
1 import { ChessRules } from "@/base_rules";
2 import { Antiking2Rules } from "@/variants/Antiking2";
3
4 export class MesmerRules extends ChessRules {
5
6 static IsGoodFen(fen) {
7 if (!ChessRules.IsGoodFen(fen)) return false;
8 const fenParsed = V.ParseFen(fen);
9 // 5) Check arrival of last hypnotizing move (if any)
10 if (
11 !fenParsed.hSquare ||
12 (fenParsed.hSquare != "-" && !fenParsed.hSquare.match(/^[a-h][1-8]$/))
13 ) {
14 return false;
15 }
16 return true;
17 }
18
19 static get MESMERIST() {
20 return 'm';
21 }
22
23 static get PIECES() {
24 return ChessRules.PIECES.concat([V.MESMERIST]);
25 }
26
27 getPpath(b) {
28 return (b.charAt(1) == 'm' ? "Mesmer/" : "") + b;
29 }
30
31 static ParseFen(fen) {
32 const fenParts = fen.split(" ");
33 return Object.assign(
34 { hSquare: fenParts[5] },
35 ChessRules.ParseFen(fen)
36 );
37 }
38
39 static GenRandInitFen(options) {
40 const antikingFen = Antiking2Rules.GenRandInitFen(options);
41 return antikingFen.replace('a', 'M').replace('A', 'm') + " -";
42 }
43
44 setOtherVariables(fen) {
45 super.setOtherVariables(fen);
46 const parsedFen = V.ParseFen(fen);
47 this.hSquares = [
48 parsedFen.hSquare != "-"
49 ? V.SquareToCoords(parsedFen.hSquare)
50 : null
51 ];
52 }
53
54 scanKings(fen) {
55 super.scanKings(fen);
56 // Squares of white and black mesmerist:
57 this.mesmerPos = { w: [-1, -1], b: [-1, -1] };
58 const fenRows = V.ParseFen(fen).position.split("/");
59 for (let i = 0; i < fenRows.length; i++) {
60 let k = 0;
61 for (let j = 0; j < fenRows[i].length; j++) {
62 switch (fenRows[i].charAt(j)) {
63 case "m":
64 this.mesmerPos["b"] = [i, k];
65 break;
66 case "M":
67 this.mesmerPos["w"] = [i, k];
68 break;
69 default: {
70 const num = parseInt(fenRows[i].charAt(j), 10);
71 if (!isNaN(num)) k += num - 1;
72 }
73 }
74 k++;
75 }
76 }
77 }
78
79 getFen() {
80 const L = this.hSquares.length;
81 return (
82 super.getFen() + " " +
83 (!this.hSquares[L-1] ? "-" : V.CoordsToSquare(this.hSquares[L-1]))
84 );
85 }
86
87 canIplay(side) {
88 // Wrong, but sufficient approximation let's say
89 return this.turn == side;
90 }
91
92 canTake([x1, y1], [x2, y2]) {
93 const c = this.turn;
94 const c1 = this.getColor(x1, y1);
95 const c2 = this.getColor(x2, y2);
96 return (c == c1 && c1 != c2) || (c != c1 && c1 == c2);
97 }
98
99 getPotentialMovesFrom([x, y]) {
100 const L = this.hSquares.length;
101 const lh = this.hSquares[L-1];
102 if (!!lh && lh.x == x && lh.y == y) return [];
103 const c = this.getColor(x, y);
104 const piece = this.getPiece(x, y);
105 if (c == this.turn) {
106 if (piece == V.MESMERIST) return this.getPotentialMesmeristMoves([x, y]);
107 return super.getPotentialMovesFrom([x, y]);
108 }
109 // Playing opponent's pieces: hypnotizing moves. Allowed?
110 if (piece == V.MESMERIST || !this.isAttackedByMesmerist([x, y], this.turn))
111 return [];
112 const moves =
113 piece == V.KING
114 // No castling with enemy king (...yes, should eat it but...)
115 ? super.getSlideNJumpMoves(
116 [x, y], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1)
117 : super.getPotentialMovesFrom([x, y]);
118 return moves;
119 }
120
121 // Moves like a queen without capturing
122 getPotentialMesmeristMoves([x, y]) {
123 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
124 let moves = [];
125 for (let step of steps) {
126 let i = x + step[0];
127 let j = y + step[1];
128 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
129 moves.push(this.getBasicMove([x, y], [i, j]));
130 i += step[0];
131 j += step[1];
132 }
133 }
134 return moves;
135 }
136
137 isAttackedByMesmerist(sq, color) {
138 return (
139 super.isAttackedBySlideNJump(
140 sq, color, V.MESMERIST, V.steps[V.ROOK].concat(V.steps[V.BISHOP]))
141 );
142 }
143
144 getEnpassantCaptures([x, y], shiftX) {
145 const Lep = this.epSquares.length;
146 const epSquare = this.epSquares[Lep - 1]; //always at least one element
147 let enpassantMove = null;
148 const c = this.getColor(x, y);
149 if (
150 !!epSquare &&
151 epSquare.x == x + shiftX &&
152 Math.abs(epSquare.y - y) == 1 &&
153 // Next conditions to avoid capturing self hypnotized pawns:
154 this.board[x][epSquare.y] != V.EMPTY &&
155 this.getColor(x, epSquare.y) != c //TODO: probably redundant
156 ) {
157 enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
158 enpassantMove.vanish.push({
159 x: x,
160 y: epSquare.y,
161 p: this.board[x][epSquare.y].charAt(1),
162 c: this.getColor(x, epSquare.y)
163 });
164 }
165 return !!enpassantMove ? [enpassantMove] : [];
166 }
167
168 // TODO: avoid following code duplication, by using getColor()
169 // instead of this.turn at the beginning of 2 next methods
170 addPawnMoves([x1, y1], [x2, y2], moves, promotions) {
171 let finalPieces = [V.PAWN];
172 const color = this.getColor(x1, y1);
173 const lastRank = (color == "w" ? 0 : V.size.x - 1);
174 if (x2 == lastRank) finalPieces = V.PawnSpecs.promotions;
175 let tr = null;
176 for (let piece of finalPieces) {
177 tr = (piece != V.PAWN ? { c: color, p: piece } : null);
178 moves.push(this.getBasicMove([x1, y1], [x2, y2], tr));
179 }
180 }
181
182 getPotentialPawnMoves([x, y], promotions) {
183 const color = this.getColor(x, y);
184 const [sizeX, sizeY] = [V.size.x, V.size.y];
185 const forward = (color == 'w' ? -1 : 1);
186
187 let moves = [];
188 if (x + forward >= 0 && x + forward < sizeX) {
189 if (this.board[x + forward][y] == V.EMPTY) {
190 this.addPawnMoves([x, y], [x + forward, y], moves, promotions);
191 if (
192 ((color == 'w' && x == 6) || (color == 'b' && x == 1)) &&
193 this.board[x + 2 * forward][y] == V.EMPTY
194 ) {
195 moves.push(this.getBasicMove([x, y], [x + 2 * forward, y]));
196 }
197 }
198 for (let shiftY of [-1, 1]) {
199 if (
200 y + shiftY >= 0 && y + shiftY < sizeY &&
201 this.board[x + forward][y + shiftY] != V.EMPTY &&
202 this.canTake([x, y], [x + forward, y + shiftY])
203 ) {
204 this.addPawnMoves(
205 [x, y], [x + forward, y + shiftY],
206 moves, promotions
207 );
208 }
209 }
210 }
211 Array.prototype.push.apply(moves,
212 this.getEnpassantCaptures([x, y], forward));
213 return moves;
214 }
215
216 postPlay(move) {
217 super.postPlay(move);
218 if (move.vanish[0].p == V.MESMERIST)
219 this.mesmerPos[move.vanish[0].c] = [move.appear[0].x, move.appear[0].y];
220 if (move.vanish[0].c == this.turn)
221 this.hSquares.push({ x: move.appear[0].x, y: move.appear[0].y });
222 else this.hSquares.push(null);
223 if (move.vanish.length == 2) {
224 if (move.vanish[1].p == V.KING)
225 this.kingPos[move.vanish[1].c] = [-1, -1];
226 else if (move.vanish[1].p == V.MESMERIST)
227 this.mesmerPos[move.vanish[1].c] = [-1, -1]
228 }
229 }
230 postUndo(move) {
231 super.postUndo(move);
232 if (move.vanish[0].p == V.MESMERIST)
233 this.mesmerPos[move.vanish[0].c] = [move.vanish[0].x, move.vanish[0].y];
234 this.hSquares.pop();
235 if (move.vanish.length == 2) {
236 const v = move.vanish[1];
237 if (v.p == V.KING)
238 this.kingPos[v.c] = [v.x, v.y];
239 else if (v.p == V.MESMERIST)
240 this.mesmerPos[v.c] = [v.x, v.y];
241 }
242 }
243
244 getCheckSquares() {
245 return [];
246 }
247 filterValid(moves) {
248 return moves;
249 }
250
251 getCurrentScore() {
252 const c = this.turn;
253 if (this.kingPos[c][0] < 0) return (c == 'w' ? "0-1" : "1-0");
254 if (this.mesmerPos[c][0] < 0) return (c == 'w' ? "0-1" : "1-0");
255 return "*";
256 }
257
258 static get SEARCH_DEPTH() {
259 return 2;
260 }
261
262 };