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