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