374a620c932069fe6661e18f9d0fbe73f5b459a8
[vchess.git] / client / src / variants / Enpassant.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2
3 export const VariantRules = class EnpassantRules extends ChessRules {
4
5 static IsGoodEnpassant(enpassant) {
6 if (enpassant != "-") {
7 const squares = enpassant.split(",");
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 let res = [];
22 square.split(",").forEach(sq => {
23 res.push(V.SquareToCoords(sq));
24 });
25 return res;
26 }
27 // Argument is a move: all intermediate squares are en-passant candidates,
28 // except if the moving piece is a king.
29 const move = moveOrSquare;
30 const piece = move.appear[0].p;
31 if (piece == V.KING ||
32 (
33 Math.abs(move.end.x-move.start.x) <= 1 &&
34 Math.abs(move.end.y-move.start.y) <= 1
35 )
36 ) {
37 return undefined;
38 }
39 const delta = [move.end.x-move.start.x, move.end.y-move.start.y];
40 let step = undefined;
41 if (piece == V.KNIGHT) {
42 const divisor = Math.min(Math.abs(delta[0]), Math.abs(delta[1]));
43 step = [delta[0]/divisor || 0, delta[1]/divisor || 0];
44 } else {
45 step = [delta[0]/Math.abs(delta[0]) || 0, delta[1]/Math.abs(delta[1]) || 0];
46 }
47 let res = [];
48 for (
49 let [x,y] = [move.start.x+step[0],move.start.y+step[1]];
50 x != move.end.x || y != move.end.y;
51 x += step[0], y += step[1]
52 ) {
53 res.push({x:x, y:y});
54 }
55 // Add final square to know which piece is taken en passant:
56 res.push(move.end);
57 return res;
58 }
59
60 getEnpassantFen() {
61 const L = this.epSquares.length;
62 if (!this.epSquares[L - 1]) return "-"; //no en-passant
63 let res = "";
64 this.epSquares[L - 1].forEach(sq => {
65 res += V.CoordsToSquare(sq) + ",";
66 });
67 return res.slice(0, -1); //remove last comma
68 }
69
70 // TODO: this getPotentialPawnMovesFrom() is mostly duplicated:
71 // it could be split in "capture", "promotion", "enpassant"...
72 getPotentialPawnMoves([x, y]) {
73 const color = this.turn;
74 let moves = [];
75 const [sizeX, sizeY] = [V.size.x, V.size.y];
76 const shiftX = color == "w" ? -1 : 1;
77 const startRank = color == "w" ? sizeX - 2 : 1;
78 const lastRank = color == "w" ? 0 : sizeX - 1;
79
80 const finalPieces =
81 x + shiftX == lastRank
82 ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
83 : [V.PAWN];
84 // One square forward
85 if (this.board[x + shiftX][y] == V.EMPTY) {
86 for (let piece of finalPieces) {
87 moves.push(
88 this.getBasicMove([x, y], [x + shiftX, y], {
89 c: color,
90 p: piece
91 })
92 );
93 }
94 if (
95 x == startRank &&
96 this.board[x + 2 * shiftX][y] == V.EMPTY
97 ) {
98 // Two squares jump
99 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
100 }
101 }
102 // Captures
103 for (let shiftY of [-1, 1]) {
104 if (
105 y + shiftY >= 0 &&
106 y + shiftY < sizeY &&
107 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
108 this.canTake([x, y], [x + shiftX, y + shiftY])
109 ) {
110 for (let piece of finalPieces) {
111 moves.push(
112 this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
113 c: color,
114 p: piece
115 })
116 );
117 }
118 }
119 }
120
121 // En passant
122 const Lep = this.epSquares.length;
123 const squares = this.epSquares[Lep - 1];
124 if (!!squares) {
125 const S = squares.length;
126 const taken = squares[S-1];
127 const pipoV = new PiPo({
128 x: taken.x,
129 y: taken.y,
130 p: this.getPiece(taken.x, taken.y),
131 c: this.getColor(taken.x, taken.y)
132 });
133 [...Array(S-1).keys()].forEach(i => {
134 const sq = squares[i];
135 if (sq.x == x + shiftX && Math.abs(sq.y - y) == 1) {
136 let enpassantMove = this.getBasicMove([x, y], [sq.x, sq.y]);
137 enpassantMove.vanish.push(pipoV);
138 moves.push(enpassantMove);
139 }
140 });
141 }
142
143 return moves;
144 }
145
146 // Remove the "onestep" condition: knight promote to knightrider:
147
148 getPotentialKnightMoves(sq) {
149 return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]);
150 }
151
152 isAttackedByKnight(sq, colors) {
153 return this.isAttackedBySlideNJump(
154 sq,
155 colors,
156 V.KNIGHT,
157 V.steps[V.KNIGHT]
158 );
159 }
160
161 getPotentialMovesFrom([x, y]) {
162 let moves = super.getPotentialMovesFrom([x,y]);
163 // Add en-passant captures from this square:
164 const L = this.epSquares.length;
165 if (!this.epSquares[L - 1]) return moves;
166 const squares = this.epSquares[L - 1];
167 const S = squares.length;
168 // Object describing the removed opponent's piece:
169 const pipoV = new PiPo({
170 x: squares[S-1].x,
171 y: squares[S-1].y,
172 c: V.GetOppCol(this.turn),
173 p: this.getPiece(squares[S-1].x, squares[S-1].y)
174 });
175 // Check if existing non-capturing moves could also capture en passant
176 moves.forEach(m => {
177 if (
178 m.appear[0].p != V.PAWN && //special pawn case is handled elsewhere
179 m.vanish.length <= 1 &&
180 [...Array(S-1).keys()].some(i => {
181 return m.end.x == squares[i].x && m.end.y == squares[i].y;
182 })
183 ) {
184 m.vanish.push(pipoV);
185 }
186 });
187 // Special case of the king knight's movement:
188 if (this.getPiece(x, y) == V.KING) {
189 V.steps[V.KNIGHT].forEach(step => {
190 const endX = x + step[0];
191 const endY = y + step[1];
192 if (
193 V.OnBoard(endX, endY) &&
194 [...Array(S-1).keys()].some(i => {
195 return endX == squares[i].x && endY == squares[i].y;
196 })
197 ) {
198 let enpassantMove = this.getBasicMove([x, y], [endX, endY]);
199 enpassantMove.vanish.push(pipoV);
200 moves.push(enpassantMove);
201 }
202 });
203 }
204 return moves;
205 }
206
207 static get VALUES() {
208 return {
209 p: 1,
210 r: 5,
211 n: 4,
212 b: 3,
213 q: 9,
214 k: 1000
215 };
216 }
217 };