43cad10379747e78010c72cf7368c9b6a3aadcc7
[vchess.git] / client / src / variants / Grand.js
1 import { ChessRules } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt } from "@/utils/alea";
4
5 export class GrandRules extends ChessRules {
6
7 static get HasFlags() {
8 return false;
9 }
10
11 static IsGoodEnpassant(enpassant) {
12 if (enpassant != "-") return !!enpassant.match(/^([a-j][0-9]{1,2},?)+$/);
13 return true;
14 }
15
16 getPpath(b) {
17 return ([V.MARSHALL, V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
18 }
19
20 static get size() {
21 return { x: 10, y: 10 };
22 }
23
24 // Rook + knight:
25 static get MARSHALL() {
26 return "m";
27 }
28
29 // Bishop + knight
30 static get CARDINAL() {
31 return "c";
32 }
33
34 static get PIECES() {
35 return ChessRules.PIECES.concat([V.MARSHALL, V.CARDINAL]);
36 }
37
38 getPotentialMovesFrom([x, y]) {
39 switch (this.getPiece(x, y)) {
40 case V.MARSHALL:
41 return this.getPotentialMarshallMoves([x, y]);
42 case V.CARDINAL:
43 return this.getPotentialCardinalMoves([x, y]);
44 default:
45 return super.getPotentialMovesFrom([x, y]);
46 }
47 }
48
49 // Special pawn rules: promotions to captured friendly pieces,
50 // optional on ranks 8-9 and mandatory on rank 10.
51 getPotentialPawnMoves([x, y]) {
52 const color = this.turn;
53 let moves = [];
54 const [sizeX, sizeY] = [V.size.x, V.size.y];
55 const shiftX = color == "w" ? -1 : 1;
56 const startRank = (color == "w" ? sizeX - 3 : 2);
57 const lastRanks =
58 color == "w" ? [0, 1, 2] : [sizeX - 1, sizeX - 2, sizeX - 3];
59 // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
60 let finalPieces = [V.PAWN];
61 if (lastRanks.includes(x + shiftX)) {
62 // Determine which promotion pieces are available:
63 let promotionPieces = {
64 [V.ROOK]: 2,
65 [V.KNIGHT]: 2,
66 [V.BISHOP]: 2,
67 [V.QUEEN]: 1,
68 [V.MARSHALL]: 1,
69 [V.CARDINAL]: 1
70 };
71 for (let i=0; i<10; i++) {
72 for (let j=0; j<10; j++) {
73 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
74 const p = this.getPiece(i, j);
75 if (![V.PAWN, V.KING].includes(p)) promotionPieces[p]--;
76 }
77 }
78 }
79 const availablePieces =
80 Object.keys(promotionPieces).filter(k => promotionPieces[k] > 0);
81 if (x + shiftX == lastRanks[0]) finalPieces = availablePieces;
82 else Array.prototype.push.apply(finalPieces, availablePieces);
83 }
84 if (this.board[x + shiftX][y] == V.EMPTY) {
85 // One square forward
86 for (let piece of finalPieces)
87 moves.push(
88 this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece })
89 );
90 if (x == startRank && this.board[x + 2 * shiftX][y] == V.EMPTY)
91 // Two squares jump
92 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
93 }
94 // Captures
95 for (let shiftY of [-1, 1]) {
96 if (
97 y + shiftY >= 0 &&
98 y + shiftY < sizeY &&
99 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
100 this.canTake([x, y], [x + shiftX, y + shiftY])
101 ) {
102 for (let piece of finalPieces) {
103 moves.push(
104 this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
105 c: color,
106 p: piece
107 })
108 );
109 }
110 }
111 }
112
113 // En passant
114 Array.prototype.push.apply(
115 moves,
116 this.getEnpassantCaptures([x, y], shiftX)
117 );
118
119 return moves;
120 }
121
122 getPotentialMarshallMoves(sq) {
123 return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
124 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
125 );
126 }
127
128 getPotentialCardinalMoves(sq) {
129 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
130 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
131 );
132 }
133
134 isAttacked(sq, color) {
135 return (
136 super.isAttacked(sq, color) ||
137 this.isAttackedByMarshall(sq, color) ||
138 this.isAttackedByCardinal(sq, color)
139 );
140 }
141
142 isAttackedByMarshall(sq, color) {
143 return (
144 this.isAttackedBySlideNJump(sq, color, V.MARSHALL, V.steps[V.ROOK]) ||
145 this.isAttackedBySlideNJump(
146 sq,
147 color,
148 V.MARSHALL,
149 V.steps[V.KNIGHT],
150 "oneStep"
151 )
152 );
153 }
154
155 isAttackedByCardinal(sq, color) {
156 return (
157 this.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) ||
158 this.isAttackedBySlideNJump(
159 sq,
160 color,
161 V.CARDINAL,
162 V.steps[V.KNIGHT],
163 "oneStep"
164 )
165 );
166 }
167
168 static get VALUES() {
169 return Object.assign(
170 { c: 5, m: 7 }, //experimental
171 ChessRules.VALUES
172 );
173 }
174
175 static get SEARCH_DEPTH() {
176 return 2;
177 }
178
179 static GenRandInitFen(randomness) {
180 if (randomness == 0) {
181 return (
182 "r8r/1nbqkmcbn1/pppppppppp/91/91/91/91/PPPPPPPPPP/1NBQKMCBN1/R8R " +
183 "w 0 -"
184 );
185 }
186
187 let pieces = { w: new Array(8), b: new Array(8) };
188 // Shuffle pieces on second and before-last rank
189 for (let c of ["w", "b"]) {
190 if (c == 'b' && randomness == 1) {
191 pieces['b'] = pieces['w'];
192 break;
193 }
194
195 let positions = ArrayFun.range(8);
196
197 // Get random squares for bishops
198 let randIndex = 2 * randInt(4);
199 const bishop1Pos = positions[randIndex];
200 // The second bishop must be on a square of different color
201 let randIndex_tmp = 2 * randInt(4) + 1;
202 const bishop2Pos = positions[randIndex_tmp];
203 // Remove chosen squares
204 positions.splice(Math.max(randIndex, randIndex_tmp), 1);
205 positions.splice(Math.min(randIndex, randIndex_tmp), 1);
206
207 // Get random squares for knights
208 randIndex = randInt(6);
209 const knight1Pos = positions[randIndex];
210 positions.splice(randIndex, 1);
211 randIndex = randInt(5);
212 const knight2Pos = positions[randIndex];
213 positions.splice(randIndex, 1);
214
215 // Get random square for queen
216 randIndex = randInt(4);
217 const queenPos = positions[randIndex];
218 positions.splice(randIndex, 1);
219
220 // ...random square for marshall
221 randIndex = randInt(3);
222 const marshallPos = positions[randIndex];
223 positions.splice(randIndex, 1);
224
225 // ...random square for cardinal
226 randIndex = randInt(2);
227 const cardinalPos = positions[randIndex];
228 positions.splice(randIndex, 1);
229
230 // King position is now fixed,
231 const kingPos = positions[0];
232
233 // Finally put the shuffled pieces in the board array
234 pieces[c][knight1Pos] = "n";
235 pieces[c][bishop1Pos] = "b";
236 pieces[c][queenPos] = "q";
237 pieces[c][marshallPos] = "m";
238 pieces[c][cardinalPos] = "c";
239 pieces[c][kingPos] = "k";
240 pieces[c][bishop2Pos] = "b";
241 pieces[c][knight2Pos] = "n";
242 }
243 return (
244 "r8r/1" + pieces["b"].join("") + "1/" +
245 "pppppppppp/91/91/91/91/PPPPPPPPPP/" +
246 "1" + pieces["w"].join("").toUpperCase() + "1/R8R" +
247 " w 0 -"
248 );
249 }
250
251 };