c501c023a87b0f2ce7bf5059bafc52422b0e00df
[vchess.git] / client / src / variants / Convert.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class ConvertRules extends ChessRules {
5
6 static get HasEnpassant() {
7 return false;
8 }
9
10 setOtherVariables(fen) {
11 super.setOtherVariables(fen);
12 // Stack of "last move" only for intermediate chaining
13 this.lastMoveEnd = [null];
14 }
15
16 static GenRandInitFen(options) {
17 const baseFen = ChessRules.GenRandInitFen(options);
18 return (
19 baseFen.substr(0, 8) +
20 "/8/pppppppp/8/8/PPPPPPPP/8/" +
21 baseFen.substr(35, 17)
22 );
23 }
24
25 getBasicMove([sx, sy], [ex, ey], tr) {
26 const L = this.lastMoveEnd.length;
27 const lm = this.lastMoveEnd[L-1];
28 const piece = (!!lm ? lm.p : null);
29 const c = this.turn;
30 if (this.board[ex][ey] == V.EMPTY) {
31 if (!!piece && !tr) tr = { c: c, p: piece }
32 let mv = super.getBasicMove([sx, sy], [ex, ey], tr);
33 if (!!piece) mv.vanish.pop();
34 return mv;
35 }
36 // Capture: initial, or inside a chain
37 const initPiece = (piece || this.getPiece(sx, sy));
38 const oppCol = V.GetOppCol(c);
39 const oppPiece = this.getPiece(ex, ey);
40 let mv = new Move({
41 start: { x: sx, y: sy },
42 end: { x: ex, y: ey },
43 appear: [
44 new PiPo({
45 x: ex,
46 y: ey,
47 c: c,
48 p: (!!tr ? tr.p : initPiece)
49 })
50 ],
51 vanish: [
52 new PiPo({
53 x: ex,
54 y: ey,
55 c: oppCol,
56 p: oppPiece
57 })
58 ]
59 });
60 if (!piece) {
61 // Initial capture
62 mv.vanish.unshift(
63 new PiPo({
64 x: sx,
65 y: sy,
66 c: c,
67 p: initPiece
68 })
69 );
70 }
71 // TODO: This "converted" indication isn't needed in fact,
72 // because it can be deduced from the move itself.
73 mv.end.converted = oppPiece;
74 return mv;
75 }
76
77 getPotentialMovesFrom([x, y], asA) {
78 const L = this.lastMoveEnd.length;
79 if (!!this.lastMoveEnd[L-1]) {
80 if (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y)
81 // A capture was played: wrong square
82 return [];
83 asA = this.lastMoveEnd[L-1].p;
84 }
85 switch (asA || this.getPiece(x, y)) {
86 case V.PAWN: return super.getPotentialPawnMoves([x, y]);
87 case V.ROOK: return super.getPotentialRookMoves([x, y]);
88 case V.KNIGHT: return super.getPotentialKnightMoves([x, y]);
89 case V.BISHOP: return super.getPotentialBishopMoves([x, y]);
90 case V.QUEEN: return super.getPotentialQueenMoves([x, y]);
91 case V.KING: return super.getPotentialKingMoves([x, y]);
92 }
93 return [];
94 }
95
96 getPossibleMovesFrom(sq) {
97 const L = this.lastMoveEnd.length;
98 let asA = undefined;
99 if (!!this.lastMoveEnd[L-1]) {
100 if (
101 sq[0] != this.lastMoveEnd[L-1].x ||
102 sq[1] != this.lastMoveEnd[L-1].y
103 ) {
104 return [];
105 }
106 asA = this.lastMoveEnd[L-1].p;
107 }
108 return this.filterValid(this.getPotentialMovesFrom(sq, asA));
109 }
110
111 isAttacked_aux([x, y], color, explored) {
112 if (explored.some(sq => sq[0] == x && sq[1] == y))
113 // Start of an infinite loop: exit
114 return false;
115 explored.push([x, y]);
116 if (super.isAttacked([x, y], color)) return true;
117 // Maybe indirect "chaining" attack:
118 const myColor = this.turn
119 let res = false;
120 let toCheck = []; //check all but king (no need)
121 // Pawns:
122 const shiftToPawn = (myColor == 'w' ? -1 : 1);
123 for (let yShift of [-1, 1]) {
124 const [i, j] = [x + shiftToPawn, y + yShift];
125 if (
126 V.OnBoard(i, j) &&
127 this.board[i][j] != V.EMPTY &&
128 // NOTE: no need to check color (no enemy pawn can take directly)
129 this.getPiece(i, j) == V.PAWN
130 ) {
131 toCheck.push([i, j]);
132 }
133 }
134 // Knights:
135 V.steps[V.KNIGHT].forEach(s => {
136 const [i, j] = [x + s[0], y + s[1]];
137 if (
138 V.OnBoard(i, j) &&
139 this.board[i][j] != V.EMPTY &&
140 this.getPiece(i, j) == V.KNIGHT
141 ) {
142 toCheck.push([i, j]);
143 }
144 });
145 // Sliders:
146 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
147 let [i, j] = [x + s[0], y + s[1]];
148 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
149 i += s[0];
150 j += s[1];
151 }
152 if (!V.OnBoard(i, j)) return;
153 const piece = this.getPiece(i, j);
154 if (
155 piece == V.QUEEN ||
156 (piece == V.ROOK && (s[0] == 0 || s[1] == 0)) ||
157 (piece == V.BISHOP && (s[0] != 0 && s[1] != 0))
158 ) {
159 toCheck.push([i, j]);
160 }
161 });
162 for (let ij of toCheck) {
163 if (this.isAttacked_aux(ij, color, explored)) return true;
164 }
165 return false;
166 }
167
168 isAttacked([x, y], color) {
169 let explored = [];
170 return this.isAttacked_aux([x, y], color, explored);
171 }
172
173 filterValid(moves) {
174 // No "checks" (except to forbid castle)
175 return moves;
176 }
177
178 getCheckSquares() {
179 return [];
180 }
181
182 prePlay(move) {
183 const c = this.turn;
184 // Extra conditions to avoid tracking converted kings:
185 if (
186 move.appear[0].p == V.KING &&
187 move.vanish.length >= 1 &&
188 move.vanish[0].p == V.KING
189 ) {
190 this.kingPos[c][0] = move.appear[0].x;
191 this.kingPos[c][1] = move.appear[0].y;
192 }
193 }
194
195 play(move) {
196 this.prePlay(move);
197 const c = this.turn;
198 move.flags = JSON.stringify(this.aggregateFlags());
199 V.PlayOnBoard(this.board, move);
200 if (!move.end.converted) {
201 // Not a capture: change turn
202 this.turn = V.GetOppCol(this.turn);
203 this.movesCount++;
204 this.lastMoveEnd.push(null);
205 }
206 else {
207 this.lastMoveEnd.push(
208 Object.assign({}, move.end, { p: move.end.converted })
209 );
210 }
211 super.updateCastleFlags(move, move.appear[0].p, c);
212 }
213
214 undo(move) {
215 this.disaggregateFlags(JSON.parse(move.flags));
216 this.lastMoveEnd.pop();
217 V.UndoOnBoard(this.board, move);
218 if (!move.end.converted) {
219 this.turn = V.GetOppCol(this.turn);
220 this.movesCount--;
221 }
222 this.postUndo(move);
223 }
224
225 postUndo(move) {
226 const c = this.getColor(move.start.x, move.start.y);
227 if (
228 move.appear[0].p == V.KING &&
229 move.vanish.length >= 1 &&
230 move.vanish[0].p == V.KING
231 ) {
232 this.kingPos[c] = [move.start.x, move.start.y];
233 }
234 }
235
236 getCurrentScore() {
237 const color = this.turn;
238 const kp = this.kingPos[color];
239 if (this.getColor(kp[0], kp[1]) != color)
240 return (color == "w" ? "0-1" : "1-0");
241 if (!super.atLeastOneMove()) return "1/2";
242 return "*";
243 }
244
245 getComputerMove() {
246 let initMoves = this.getAllValidMoves();
247 if (initMoves.length == 0) return null;
248 // Loop until valid move is found (no blocked pawn conversion...)
249 while (true) {
250 let moves = JSON.parse(JSON.stringify(initMoves));
251 let mvArray = [];
252 let mv = null;
253 // Just play random moves (for now at least. TODO?)
254 while (moves.length > 0) {
255 mv = moves[randInt(moves.length)];
256 mvArray.push(mv);
257 this.play(mv);
258 if (!!mv.end.converted)
259 // A piece was just converted
260 moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]);
261 else break;
262 }
263 for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
264 if (!mv.end.converted) return (mvArray.length > 1 ? mvArray : mvArray[0]);
265 }
266 return null; //never reached
267 }
268
269 getNotation(move) {
270 if (move.appear.length == 2 && move.appear[0].p == V.KING)
271 return (move.end.y < move.start.y ? "0-0-0" : "0-0");
272 const c = this.turn;
273 const L = this.lastMoveEnd.length;
274 const lm = this.lastMoveEnd[L-1];
275 const piece = (!lm ? move.appear[0].p : lm.p);
276 // Basic move notation:
277 let notation = piece.toUpperCase();
278 if (
279 this.board[move.end.x][move.end.y] != V.EMPTY ||
280 (piece == V.PAWN && move.start.y != move.end.y)
281 ) {
282 notation += "x";
283 }
284 const finalSquare = V.CoordsToSquare(move.end);
285 notation += finalSquare;
286
287 // Add potential promotion indications:
288 const firstLastRank = (c == 'w' ? [7, 0] : [0, 7]);
289 if (move.end.x == firstLastRank[1] && piece == V.PAWN)
290 notation += "=" + move.appear[0].p.toUpperCase();
291 return notation;
292 }
293
294 };