Draft Hiddenqueen, Grasshopper and Knightmate chess (rules unwritten)
[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 getPotentialMovesFrom([x, y]) {
71 let moves = super.getPotentialMovesFrom([x,y]);
72 // Add en-passant captures from this square:
73 const L = this.epSquares.length;
74 if (!this.epSquares[L - 1]) return moves;
75 const squares = this.epSquares[L - 1];
76 const S = squares.length;
77 // Object describing the removed opponent's piece:
78 const pipoV = new PiPo({
79 x: squares[S-1].x,
80 y: squares[S-1].y,
81 c: V.GetOppCol(this.turn),
82 p: this.getPiece(squares[S-1].x, squares[S-1].y)
83 });
84 // Check if existing non-capturing moves could also capture en passant
85 moves.forEach(m => {
86 if (
87 m.appear[0].p != V.PAWN && //special pawn case is handled elsewhere
88 m.vanish.length <= 1 &&
89 [...Array(S-1).keys()].some(i => {
90 return m.end.x == squares[i].x && m.end.y == squares[i].y;
91 })
92 ) {
93 m.vanish.push(pipoV);
94 }
95 });
96 // Special case of the king knight's movement:
97 if (this.getPiece(x, y) == V.KING) {
98 V.steps[V.KNIGHT].forEach(step => {
99 const endX = x + step[0];
100 const endY = y + step[1];
101 if (
102 V.OnBoard(endX, endY) &&
103 [...Array(S-1).keys()].some(i => {
104 return endX == squares[i].x && endY == squares[i].y;
105 })
106 ) {
107 let enpassantMove = this.getBasicMove([x, y], [endX, endY]);
108 enpassantMove.vanish.push(pipoV);
109 moves.push(enpassantMove);
110 }
111 });
112 }
113 return moves;
114 }
115
116 // TODO: this getPotentialPawnMovesFrom() is mostly duplicated:
117 // it could be split in "capture", "promotion", "enpassant"...
118 getPotentialPawnMoves([x, y]) {
119 const color = this.turn;
120 let moves = [];
121 const [sizeX, sizeY] = [V.size.x, V.size.y];
122 const shiftX = color == "w" ? -1 : 1;
123 const startRank = color == "w" ? sizeX - 2 : 1;
124 const lastRank = color == "w" ? 0 : sizeX - 1;
125
126 const finalPieces =
127 x + shiftX == lastRank
128 ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
129 : [V.PAWN];
130 // One square forward
131 if (this.board[x + shiftX][y] == V.EMPTY) {
132 for (let piece of finalPieces) {
133 moves.push(
134 this.getBasicMove([x, y], [x + shiftX, y], {
135 c: color,
136 p: piece
137 })
138 );
139 }
140 if (
141 x == startRank &&
142 this.board[x + 2 * shiftX][y] == V.EMPTY
143 ) {
144 // Two squares jump
145 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
146 }
147 }
148 // Captures
149 for (let shiftY of [-1, 1]) {
150 if (
151 y + shiftY >= 0 &&
152 y + shiftY < sizeY &&
153 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
154 this.canTake([x, y], [x + shiftX, y + shiftY])
155 ) {
156 for (let piece of finalPieces) {
157 moves.push(
158 this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
159 c: color,
160 p: piece
161 })
162 );
163 }
164 }
165 }
166
167 // En passant
168 const Lep = this.epSquares.length;
169 const squares = this.epSquares[Lep - 1];
170 if (!!squares) {
171 const S = squares.length;
172 const taken = squares[S-1];
173 const pipoV = new PiPo({
174 x: taken.x,
175 y: taken.y,
176 p: this.getPiece(taken.x, taken.y),
177 c: this.getColor(taken.x, taken.y)
178 });
179 [...Array(S-1).keys()].forEach(i => {
180 const sq = squares[i];
181 if (sq.x == x + shiftX && Math.abs(sq.y - y) == 1) {
182 let enpassantMove = this.getBasicMove([x, y], [sq.x, sq.y]);
183 enpassantMove.vanish.push(pipoV);
184 moves.push(enpassantMove);
185 }
186 });
187 }
188
189 return moves;
190 }
191
192 // Remove the "onestep" condition: knight promote to knightrider:
193 getPotentialKnightMoves(sq) {
194 return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]);
195 }
196
197 filterValid(moves) {
198 const filteredMoves = super.filterValid(moves);
199 // If at least one full move made, everything is allowed:
200 if (this.movesCount >= 2)
201 return filteredMoves;
202 // Else, forbid captures:
203 return filteredMoves.filter(m => m.vanish.length == 1);
204 }
205
206 isAttackedByKnight(sq, colors) {
207 return this.isAttackedBySlideNJump(
208 sq,
209 colors,
210 V.KNIGHT,
211 V.steps[V.KNIGHT]
212 );
213 }
214
215 static get VALUES() {
216 return {
217 p: 1,
218 r: 5,
219 n: 4,
220 b: 3,
221 q: 9,
222 k: 1000
223 };
224 }
225 };