Add Forward + Doubleorda variants
[vchess.git] / client / src / variants / Orda.js
1 import { ChessRules } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt } from "@/utils/alea";
4
5 export class OrdaRules extends ChessRules {
6 static get PawnSpecs() {
7 return Object.assign(
8 {},
9 ChessRules.PawnSpecs,
10 { promotions: [V.QUEEN, V.KHESHIG] }
11 );
12 }
13
14 static IsGoodFlags(flags) {
15 // Only white can castle
16 return !!flags.match(/^[a-z]{2,2}$/);
17 }
18
19 getPpath(b) {
20 if (b[0] == 'b' || b[1] == 'h')
21 // Horde piece or white promoted pawn in kheshig
22 return "Orda/" + b;
23 return b;
24 }
25
26 static GenRandInitFen(randomness) {
27 if (randomness == 0)
28 return "lhaykahl/8/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w 0 ah -";
29
30 // Mapping kingdom --> horde:
31 const piecesMap = {
32 'r': 'l',
33 'n': 'h',
34 'b': 'a',
35 'q': 'y',
36 'k': 'k'
37 };
38
39 let pieces = { w: new Array(8), b: new Array(8) };
40 let flags = "";
41 // Shuffle pieces on first (and last rank if randomness == 2)
42 for (let c of ["w", "b"]) {
43 if (c == 'b' && randomness == 1) {
44 pieces['b'] = pieces['w'].map(p => piecesMap[p]);
45 break;
46 }
47
48 // TODO: same code as in base_rules. Should extract and factorize?
49
50 let positions = ArrayFun.range(8);
51
52 let randIndex = 2 * randInt(4);
53 const bishop1Pos = positions[randIndex];
54 let randIndex_tmp = 2 * randInt(4) + 1;
55 const bishop2Pos = positions[randIndex_tmp];
56 positions.splice(Math.max(randIndex, randIndex_tmp), 1);
57 positions.splice(Math.min(randIndex, randIndex_tmp), 1);
58
59 randIndex = randInt(6);
60 const knight1Pos = positions[randIndex];
61 positions.splice(randIndex, 1);
62 randIndex = randInt(5);
63 const knight2Pos = positions[randIndex];
64 positions.splice(randIndex, 1);
65
66 randIndex = randInt(4);
67 const queenPos = positions[randIndex];
68 positions.splice(randIndex, 1);
69
70 const rook1Pos = positions[0];
71 const kingPos = positions[1];
72 const rook2Pos = positions[2];
73
74 pieces[c][rook1Pos] = "r";
75 pieces[c][knight1Pos] = "n";
76 pieces[c][bishop1Pos] = "b";
77 pieces[c][queenPos] = "q";
78 pieces[c][kingPos] = "k";
79 pieces[c][bishop2Pos] = "b";
80 pieces[c][knight2Pos] = "n";
81 pieces[c][rook2Pos] = "r";
82 if (c == 'b') pieces[c] = pieces[c].map(p => piecesMap[p]);
83 else flags = V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
84 }
85 // Add turn + flags + enpassant
86 return (
87 pieces["b"].join("") +
88 "/8/pppppppp/8/8/8/PPPPPPPP/" +
89 pieces["w"].join("").toUpperCase() +
90 " w 0 " + flags + " -"
91 );
92 }
93
94 getFlagsFen() {
95 return this.castleFlags['w'].map(V.CoordToColumn).join("");
96 }
97
98 setFlags(fenflags) {
99 this.castleFlags = { 'w': [-1, -1] };
100 for (let i = 0; i < 2; i++)
101 this.castleFlags['w'][i] = V.ColumnToCoord(fenflags.charAt(i));
102 }
103
104 static get LANCER() {
105 return 'l';
106 }
107 static get ARCHER() {
108 return 'a';
109 }
110 static get KHESHIG() {
111 return 'h';
112 }
113 static get YURT() {
114 return 'y';
115 }
116 // Khan is technically a King, so let's keep things simple.
117
118 static get PIECES() {
119 return ChessRules.PIECES.concat([V.LANCER, V.ARCHER, V.KHESHIG, V.YURT]);
120 }
121
122 getPotentialMovesFrom([x, y]) {
123 switch (this.getPiece(x, y)) {
124 case V.LANCER:
125 return this.getPotentialLancerMoves([x, y]);
126 case V.ARCHER:
127 return this.getPotentialArcherMoves([x, y]);
128 case V.KHESHIG:
129 return this.getPotentialKheshigMoves([x, y]);
130 case V.YURT:
131 return this.getPotentialYurtMoves([x, y]);
132 default:
133 return super.getPotentialMovesFrom([x, y]);
134 }
135 return [];
136 }
137
138 getSlideNJumpMoves([x, y], steps, oneStep, options) {
139 options = options || {};
140 let moves = [];
141 outerLoop: for (let step of steps) {
142 let i = x + step[0];
143 let j = y + step[1];
144 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
145 if (!options.onlyTake) moves.push(this.getBasicMove([x, y], [i, j]));
146 if (!!oneStep) continue outerLoop;
147 i += step[0];
148 j += step[1];
149 }
150 if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]) && !options.onlyMove)
151 moves.push(this.getBasicMove([x, y], [i, j]));
152 }
153 return moves;
154 }
155
156 getPotentialLancerMoves(sq) {
157 const onlyMoves = this.getSlideNJumpMoves(
158 sq,
159 V.steps[V.KNIGHT],
160 "oneStep",
161 { onlyMove: true }
162 );
163 const onlyTakes = this.getSlideNJumpMoves(
164 sq,
165 V.steps[V.ROOK],
166 null,
167 { onlyTake: true }
168 );
169 return onlyMoves.concat(onlyTakes);
170 }
171
172 getPotentialArcherMoves(sq) {
173 const onlyMoves = this.getSlideNJumpMoves(
174 sq,
175 V.steps[V.KNIGHT],
176 "oneStep",
177 { onlyMove: true }
178 );
179 const onlyTakes = this.getSlideNJumpMoves(
180 sq,
181 V.steps[V.BISHOP],
182 null,
183 { onlyTake: true }
184 );
185 return onlyMoves.concat(onlyTakes);
186 }
187
188 getPotentialLancerMoves(sq) {
189 const onlyMoves = this.getSlideNJumpMoves(
190 sq,
191 V.steps[V.KNIGHT],
192 "oneStep",
193 { onlyMove: true }
194 );
195 const onlyTakes = this.getSlideNJumpMoves(
196 sq,
197 V.steps[V.ROOK],
198 null,
199 { onlyTake: true }
200 );
201 return onlyMoves.concat(onlyTakes);
202 }
203
204 getPotentialKheshigMoves(sq) {
205 return this.getSlideNJumpMoves(
206 sq,
207 V.steps[V.KNIGHT].concat(V.steps[V.ROOK]).concat(V.steps[V.BISHOP]),
208 "oneStep"
209 );
210 }
211
212 getPotentialYurtMoves(sq) {
213 return this.getSlideNJumpMoves(
214 sq,
215 V.steps[V.BISHOP].concat([ [1, 0] ]),
216 "oneStep"
217 );
218 }
219
220 getPotentialKingMoves([x, y]) {
221 if (this.getColor(x, y) == 'w') return super.getPotentialKingMoves([x, y]);
222 // Horde doesn't castle:
223 return this.getSlideNJumpMoves(
224 [x, y],
225 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
226 "oneStep"
227 );
228 }
229
230 isAttacked(sq, color) {
231 if (color == 'w') {
232 return (
233 super.isAttacked(sq, color) ||
234 this.isAttackedByKheshig(sq, color)
235 );
236 }
237 // Horde: only pawn, king and queen (if promotions) in common:
238 return (
239 super.isAttackedByPawn(sq, color) ||
240 this.isAttackedByLancer(sq, color) ||
241 this.isAttackedByKheshig(sq, color) ||
242 this.isAttackedByArcher(sq, color) ||
243 this.isAttackedByYurt(sq, color) ||
244 super.isAttackedByKing(sq, color) ||
245 super.isAttackedByQueen(sq, color)
246 );
247 }
248
249 isAttackedByLancer(sq, color) {
250 return this.isAttackedBySlideNJump(sq, color, V.LANCER, V.steps[V.ROOK]);
251 }
252
253 isAttackedByArcher(sq, color) {
254 return this.isAttackedBySlideNJump(sq, color, V.ARCHER, V.steps[V.BISHOP]);
255 }
256
257 isAttackedByKheshig(sq, color) {
258 return super.isAttackedBySlideNJump(
259 sq,
260 color,
261 V.KHESHIG,
262 V.steps[V.KNIGHT].concat(V.steps[V.ROOK]).concat(V.steps[V.BISHOP]),
263 "oneStep"
264 );
265 }
266
267 isAttackedByYurt(sq, color) {
268 return super.isAttackedBySlideNJump(
269 sq,
270 color,
271 V.YURT,
272 V.steps[V.BISHOP].concat([ [1, 0] ]),
273 "oneStep"
274 );
275 }
276
277 updateCastleFlags(move, piece) {
278 // Only white can castle:
279 const firstRank = 7;
280 if (piece == V.KING && move.appear[0].c == 'w')
281 this.castleFlags['w'] = [8, 8];
282 else if (
283 move.start.x == firstRank &&
284 this.castleFlags['w'].includes(move.start.y)
285 ) {
286 const flagIdx = (move.start.y == this.castleFlags['w'][0] ? 0 : 1);
287 this.castleFlags['w'][flagIdx] = 8;
288 }
289 else if (
290 move.end.x == firstRank &&
291 this.castleFlags['w'].includes(move.end.y)
292 ) {
293 const flagIdx = (move.end.y == this.castleFlags['w'][0] ? 0 : 1);
294 this.castleFlags['w'][flagIdx] = 8;
295 }
296 }
297
298 getCurrentScore() {
299 // Turn has changed:
300 const color = V.GetOppCol(this.turn);
301 const lastRank = (color == 'w' ? 0 : 7);
302 if (this.kingPos[color][0] == lastRank)
303 // The opposing edge is reached!
304 return color == "w" ? "1-0" : "0-1";
305 if (this.atLeastOneMove()) return "*";
306 // Game over
307 const oppCol = this.turn;
308 if (!this.underCheck(oppCol)) return "1/2";
309 return (oppCol == "w" ? "0-1" : "1-0");
310 }
311
312 static get VALUES() {
313 return Object.assign(
314 {},
315 ChessRules.VALUES,
316 {
317 y: 2,
318 a: 4,
319 h: 7,
320 l: 4
321 }
322 );
323 }
324 };