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