Add Tencubed and Omega variants + some fixes (updateCastleFlags()) + cleaner FEN...
[vchess.git] / client / src / variants / Enpassant.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2
3 export class EnpassantRules extends ChessRules {
4 static IsGoodEnpassant(enpassant) {
5 if (enpassant != "-") {
6 const squares = enpassant.split(",");
7 if (squares.length > 2) return false;
8 for (let sq of squares) {
9 const ep = V.SquareToCoords(sq);
10 if (isNaN(ep.x) || !V.OnBoard(ep)) return false;
11 }
12 }
13 return true;
14 }
15
16 getEpSquare(moveOrSquare) {
17 if (!moveOrSquare) return undefined;
18 if (typeof moveOrSquare === "string") {
19 const square = moveOrSquare;
20 if (square == "-") return undefined;
21 // Expand init + dest squares into a full path:
22 const init = V.SquareToCoords(square.substr(0, 2));
23 let newPath = [init];
24 if (square.length == 2) return newPath;
25 const dest = V.SquareToCoords(square.substr(2));
26 const delta = ['x', 'y'].map(i => Math.abs(dest[i] - init[i]));
27 // Check if it's a knight(rider) movement:
28 let step = [0, 0];
29 if (delta[0] > 0 && delta[1] > 0 && delta[0] != delta[1]) {
30 // Knightrider
31 const minShift = Math.min(delta[0], delta[1]);
32 step[0] = (dest.x - init.x) / minShift;
33 step[1] = (dest.y - init.y) / minShift;
34 } else {
35 // "Sliders"
36 step = ['x', 'y'].map((i, idx) => {
37 return (dest[i] - init[i]) / delta[idx] || 0
38 });
39 }
40 let x = init.x + step[0],
41 y = init.y + step[1];
42 while (x != dest.x || y != dest.y) {
43 newPath.push({ x: x, y: y });
44 x += step[0];
45 y += step[1];
46 }
47 newPath.push(dest);
48 return newPath;
49 }
50 // Argument is a move: all intermediate squares are en-passant candidates,
51 // except if the moving piece is a king.
52 const move = moveOrSquare;
53 const piece = move.appear[0].p;
54 if (piece == V.KING ||
55 (
56 Math.abs(move.end.x-move.start.x) <= 1 &&
57 Math.abs(move.end.y-move.start.y) <= 1
58 )
59 ) {
60 return undefined;
61 }
62 const delta = [move.end.x-move.start.x, move.end.y-move.start.y];
63 let step = undefined;
64 if (piece == V.KNIGHT) {
65 const divisor = Math.min(Math.abs(delta[0]), Math.abs(delta[1]));
66 step = [delta[0]/divisor || 0, delta[1]/divisor || 0];
67 } else {
68 step = [
69 delta[0]/Math.abs(delta[0]) || 0,
70 delta[1]/Math.abs(delta[1]) || 0
71 ];
72 }
73 let res = [];
74 for (
75 let [x,y] = [move.start.x+step[0],move.start.y+step[1]];
76 x != move.end.x || y != move.end.y;
77 x += step[0], y += step[1]
78 ) {
79 res.push({ x: x, y: y });
80 }
81 // Add final square to know which piece is taken en passant:
82 res.push(move.end);
83 return res;
84 }
85
86 getEnpassantFen() {
87 const L = this.epSquares.length;
88 if (!this.epSquares[L - 1]) return "-"; //no en-passant
89 const epsq = this.epSquares[L - 1];
90 if (epsq.length <= 2) return epsq.map(V.CoordsToSquare).join("");
91 // Condensate path: just need initial and final squares:
92 return V.CoordsToSquare(epsq[0]) + V.CoordsToSquare(epsq[epsq.length - 1]);
93 }
94
95 getPotentialMovesFrom([x, y]) {
96 let moves = super.getPotentialMovesFrom([x,y]);
97 // Add en-passant captures from this square:
98 const L = this.epSquares.length;
99 if (!this.epSquares[L - 1]) return moves;
100 const squares = this.epSquares[L - 1];
101 const S = squares.length;
102 // Object describing the removed opponent's piece:
103 const pipoV = new PiPo({
104 x: squares[S-1].x,
105 y: squares[S-1].y,
106 c: V.GetOppCol(this.turn),
107 p: this.getPiece(squares[S-1].x, squares[S-1].y)
108 });
109 // Check if existing non-capturing moves could also capture en passant
110 moves.forEach(m => {
111 if (
112 m.appear[0].p != V.PAWN && //special pawn case is handled elsewhere
113 m.vanish.length <= 1 &&
114 [...Array(S-1).keys()].some(i => {
115 return m.end.x == squares[i].x && m.end.y == squares[i].y;
116 })
117 ) {
118 m.vanish.push(pipoV);
119 }
120 });
121 // Special case of the king knight's movement:
122 if (this.getPiece(x, y) == V.KING) {
123 V.steps[V.KNIGHT].forEach(step => {
124 const endX = x + step[0];
125 const endY = y + step[1];
126 if (
127 V.OnBoard(endX, endY) &&
128 [...Array(S-1).keys()].some(i => {
129 return endX == squares[i].x && endY == squares[i].y;
130 })
131 ) {
132 let enpassantMove = this.getBasicMove([x, y], [endX, endY]);
133 enpassantMove.vanish.push(pipoV);
134 moves.push(enpassantMove);
135 }
136 });
137 }
138 return moves;
139 }
140
141 getEnpassantCaptures([x, y], shiftX) {
142 const Lep = this.epSquares.length;
143 const squares = this.epSquares[Lep - 1];
144 let moves = [];
145 if (!!squares) {
146 const S = squares.length;
147 const taken = squares[S-1];
148 const pipoV = new PiPo({
149 x: taken.x,
150 y: taken.y,
151 p: this.getPiece(taken.x, taken.y),
152 c: this.getColor(taken.x, taken.y)
153 });
154 [...Array(S-1).keys()].forEach(i => {
155 const sq = squares[i];
156 if (sq.x == x + shiftX && Math.abs(sq.y - y) == 1) {
157 let enpassantMove = this.getBasicMove([x, y], [sq.x, sq.y]);
158 enpassantMove.vanish.push(pipoV);
159 moves.push(enpassantMove);
160 }
161 });
162 }
163 return moves;
164 }
165
166 // Remove the "onestep" condition: knight promote to knightrider:
167 getPotentialKnightMoves(sq) {
168 return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]);
169 }
170
171 filterValid(moves) {
172 const filteredMoves = super.filterValid(moves);
173 // If at least one full move made, everything is allowed:
174 if (this.movesCount >= 2)
175 return filteredMoves;
176 // Else, forbid captures:
177 return filteredMoves.filter(m => m.vanish.length == 1);
178 }
179
180 isAttackedByKnight(sq, color) {
181 return this.isAttackedBySlideNJump(
182 sq,
183 color,
184 V.KNIGHT,
185 V.steps[V.KNIGHT]
186 );
187 }
188
189 static get SEARCH_DEPTH() {
190 return 2;
191 }
192
193 static get VALUES() {
194 return {
195 p: 1,
196 r: 5,
197 n: 4,
198 b: 3,
199 q: 9,
200 k: 1000
201 };
202 }
203 };