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