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