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