1fd5c1d31fe4952db1a808d3d1791f290151a408
[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 const baseFen = ChessRules.GenRandInitFen(randomness);
41 return (
42 baseFen.substr(0, 8).split('').map(p => piecesMap[p]).join('') +
43 // Skip 3 first rows + black castle flags
44 "/8/pppppppp" + baseFen.substr(19, 31) + " -"
45 );
46 }
47
48 getFlagsFen() {
49 return this.castleFlags['w'].map(V.CoordToColumn).join("");
50 }
51
52 setFlags(fenflags) {
53 this.castleFlags = { 'w': [-1, -1] };
54 for (let i = 0; i < 2; i++)
55 this.castleFlags['w'][i] = V.ColumnToCoord(fenflags.charAt(i));
56 }
57
58 static get LANCER() {
59 return 'l';
60 }
61 static get ARCHER() {
62 return 'a';
63 }
64 static get KHESHIG() {
65 return 'h';
66 }
67 static get YURT() {
68 return 'y';
69 }
70 // Khan is technically a King, so let's keep things simple.
71
72 static get PIECES() {
73 return ChessRules.PIECES.concat([V.LANCER, V.ARCHER, V.KHESHIG, V.YURT]);
74 }
75
76 getPotentialMovesFrom([x, y]) {
77 switch (this.getPiece(x, y)) {
78 case V.LANCER:
79 return this.getPotentialLancerMoves([x, y]);
80 case V.ARCHER:
81 return this.getPotentialArcherMoves([x, y]);
82 case V.KHESHIG:
83 return this.getPotentialKheshigMoves([x, y]);
84 case V.YURT:
85 return this.getPotentialYurtMoves([x, y]);
86 default:
87 return super.getPotentialMovesFrom([x, y]);
88 }
89 return [];
90 }
91
92 getSlideNJumpMoves([x, y], steps, oneStep, options) {
93 options = options || {};
94 let moves = [];
95 outerLoop: for (let step of steps) {
96 let i = x + step[0];
97 let j = y + step[1];
98 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
99 if (!options.onlyTake) moves.push(this.getBasicMove([x, y], [i, j]));
100 if (!!oneStep) continue outerLoop;
101 i += step[0];
102 j += step[1];
103 }
104 if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]) && !options.onlyMove)
105 moves.push(this.getBasicMove([x, y], [i, j]));
106 }
107 return moves;
108 }
109
110 getPotentialLancerMoves(sq) {
111 const onlyMoves = this.getSlideNJumpMoves(
112 sq,
113 V.steps[V.KNIGHT],
114 "oneStep",
115 { onlyMove: true }
116 );
117 const onlyTakes = this.getSlideNJumpMoves(
118 sq,
119 V.steps[V.ROOK],
120 null,
121 { onlyTake: true }
122 );
123 return onlyMoves.concat(onlyTakes);
124 }
125
126 getPotentialArcherMoves(sq) {
127 const onlyMoves = this.getSlideNJumpMoves(
128 sq,
129 V.steps[V.KNIGHT],
130 "oneStep",
131 { onlyMove: true }
132 );
133 const onlyTakes = this.getSlideNJumpMoves(
134 sq,
135 V.steps[V.BISHOP],
136 null,
137 { onlyTake: true }
138 );
139 return onlyMoves.concat(onlyTakes);
140 }
141
142 getPotentialKheshigMoves(sq) {
143 return super.getSlideNJumpMoves(
144 sq,
145 V.steps[V.KNIGHT].concat(V.steps[V.ROOK]).concat(V.steps[V.BISHOP]),
146 "oneStep"
147 );
148 }
149
150 getPotentialYurtMoves(sq) {
151 return super.getSlideNJumpMoves(
152 sq,
153 V.steps[V.BISHOP].concat([ [1, 0] ]),
154 "oneStep"
155 );
156 }
157
158 getPotentialKingMoves([x, y]) {
159 if (this.getColor(x, y) == 'w') return super.getPotentialKingMoves([x, y]);
160 // Horde doesn't castle:
161 return super.getSlideNJumpMoves(
162 [x, y],
163 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
164 "oneStep"
165 );
166 }
167
168 isAttacked(sq, color) {
169 if (color == 'w') {
170 return (
171 super.isAttacked(sq, color) ||
172 this.isAttackedByKheshig(sq, color)
173 );
174 }
175 // Horde: only pawn, king and queen (if promotions) in common:
176 return (
177 super.isAttackedByPawn(sq, color) ||
178 this.isAttackedByLancer(sq, color) ||
179 this.isAttackedByKheshig(sq, color) ||
180 this.isAttackedByArcher(sq, color) ||
181 this.isAttackedByYurt(sq, color) ||
182 super.isAttackedByKing(sq, color) ||
183 super.isAttackedByQueen(sq, color)
184 );
185 }
186
187 isAttackedByLancer(sq, color) {
188 return this.isAttackedBySlideNJump(sq, color, V.LANCER, V.steps[V.ROOK]);
189 }
190
191 isAttackedByArcher(sq, color) {
192 return this.isAttackedBySlideNJump(sq, color, V.ARCHER, V.steps[V.BISHOP]);
193 }
194
195 isAttackedByKheshig(sq, color) {
196 return super.isAttackedBySlideNJump(
197 sq,
198 color,
199 V.KHESHIG,
200 V.steps[V.KNIGHT].concat(V.steps[V.ROOK]).concat(V.steps[V.BISHOP]),
201 "oneStep"
202 );
203 }
204
205 isAttackedByYurt(sq, color) {
206 return super.isAttackedBySlideNJump(
207 sq,
208 color,
209 V.YURT,
210 V.steps[V.BISHOP].concat([ [1, 0] ]),
211 "oneStep"
212 );
213 }
214
215 updateCastleFlags(move, piece) {
216 // Only white can castle:
217 const firstRank = 7;
218 if (piece == V.KING && move.appear[0].c == 'w')
219 this.castleFlags['w'] = [8, 8];
220 else if (
221 move.start.x == firstRank &&
222 this.castleFlags['w'].includes(move.start.y)
223 ) {
224 const flagIdx = (move.start.y == this.castleFlags['w'][0] ? 0 : 1);
225 this.castleFlags['w'][flagIdx] = 8;
226 }
227 else if (
228 move.end.x == firstRank &&
229 this.castleFlags['w'].includes(move.end.y)
230 ) {
231 const flagIdx = (move.end.y == this.castleFlags['w'][0] ? 0 : 1);
232 this.castleFlags['w'][flagIdx] = 8;
233 }
234 }
235
236 getCurrentScore() {
237 // Turn has changed:
238 const color = V.GetOppCol(this.turn);
239 const lastRank = (color == 'w' ? 0 : 7);
240 if (this.kingPos[color][0] == lastRank)
241 // The opposing edge is reached!
242 return color == "w" ? "1-0" : "0-1";
243 if (this.atLeastOneMove()) return "*";
244 // Game over
245 const oppCol = this.turn;
246 if (!this.underCheck(oppCol)) return "1/2";
247 return (oppCol == "w" ? "0-1" : "1-0");
248 }
249
250 static get VALUES() {
251 return Object.assign(
252 {},
253 ChessRules.VALUES,
254 {
255 y: 2,
256 a: 4,
257 h: 7,
258 l: 4
259 }
260 );
261 }
262
263 };