Several small improvements + integrate options + first working draft of Cwda
[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], 1)
125 );
126 }
127
128 getPotentialCardinalMoves(sq) {
129 return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
130 this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1)
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(sq, color, V.MARSHALL, V.steps[V.KNIGHT], 1)
146 );
147 }
148
149 isAttackedByCardinal(sq, color) {
150 return (
151 this.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) ||
152 this.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.KNIGHT], 1)
153 );
154 }
155
156 static get VALUES() {
157 return Object.assign(
158 { c: 5, m: 7 }, //experimental
159 ChessRules.VALUES
160 );
161 }
162
163 static get SEARCH_DEPTH() {
164 return 2;
165 }
166
167 static GenRandInitFen(options) {
168 if (options.randomness == 0) {
169 return (
170 "r8r/1nbqkmcbn1/pppppppppp/91/91/91/91/PPPPPPPPPP/1NBQKMCBN1/R8R " +
171 "w 0 -"
172 );
173 }
174
175 let pieces = { w: new Array(8), b: new Array(8) };
176 // Shuffle pieces on second and before-last rank
177 for (let c of ["w", "b"]) {
178 if (c == 'b' && options.randomness == 1) {
179 pieces['b'] = pieces['w'];
180 break;
181 }
182
183 let positions = ArrayFun.range(8);
184
185 // Get random squares for bishops
186 let randIndex = 2 * randInt(4);
187 const bishop1Pos = positions[randIndex];
188 // The second bishop must be on a square of different color
189 let randIndex_tmp = 2 * randInt(4) + 1;
190 const bishop2Pos = positions[randIndex_tmp];
191 // Remove chosen squares
192 positions.splice(Math.max(randIndex, randIndex_tmp), 1);
193 positions.splice(Math.min(randIndex, randIndex_tmp), 1);
194
195 // Get random squares for knights
196 randIndex = randInt(6);
197 const knight1Pos = positions[randIndex];
198 positions.splice(randIndex, 1);
199 randIndex = randInt(5);
200 const knight2Pos = positions[randIndex];
201 positions.splice(randIndex, 1);
202
203 // Get random square for queen
204 randIndex = randInt(4);
205 const queenPos = positions[randIndex];
206 positions.splice(randIndex, 1);
207
208 // ...random square for marshall
209 randIndex = randInt(3);
210 const marshallPos = positions[randIndex];
211 positions.splice(randIndex, 1);
212
213 // ...random square for cardinal
214 randIndex = randInt(2);
215 const cardinalPos = positions[randIndex];
216 positions.splice(randIndex, 1);
217
218 // King position is now fixed,
219 const kingPos = positions[0];
220
221 // Finally put the shuffled pieces in the board array
222 pieces[c][knight1Pos] = "n";
223 pieces[c][bishop1Pos] = "b";
224 pieces[c][queenPos] = "q";
225 pieces[c][marshallPos] = "m";
226 pieces[c][cardinalPos] = "c";
227 pieces[c][kingPos] = "k";
228 pieces[c][bishop2Pos] = "b";
229 pieces[c][knight2Pos] = "n";
230 }
231 return (
232 "r8r/1" + pieces["b"].join("") + "1/" +
233 "pppppppppp/91/91/91/91/PPPPPPPPPP/" +
234 "1" + pieces["w"].join("").toUpperCase() + "1/R8R" +
235 " w 0 -"
236 );
237 }
238
239 };