Add temporary patch for preset challenges
[vchess.git] / client / src / variants / Royalrace.js
1 import { ChessRules } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt, shuffle } from "@/utils/alea";
4
5 export class RoyalraceRules extends ChessRules {
6
7 static get HasFlags() {
8 return false;
9 }
10
11 static get HasEnpassant() {
12 return false;
13 }
14
15 static get CanFlip() {
16 return false;
17 }
18
19 static get size() {
20 return { x: 11, y: 11 };
21 }
22
23 getPpath(b) {
24 return (b[1] == V.KNIGHT ? "Enpassant/" : "") + b;
25 }
26
27 static GenRandInitFen(randomness) {
28 if (randomness == 0)
29 return "92/92/92/92/92/92/92/92/92/qrbnp1PNBRQ/krbnp1PNBRK w 0";
30
31 let pieces = { w: new Array(10), b: new Array(10) };
32 // Shuffle pieces on first and second rank
33 for (let c of ["w", "b"]) {
34 if (c == 'b' && randomness == 1) {
35 pieces['b'] = JSON.parse(JSON.stringify(pieces['w'])).reverse();
36 pieces['b'] =
37 pieces['b'].splice(5,10).reverse().concat(
38 pieces['b'].splice(0,5).reverse());
39 break;
40 }
41
42 // Reserve 4 and 5 which are pawns positions
43 let positions = ArrayFun.range(10).filter(i => i != 4 && i != 5);
44
45 // Get random squares for bishops
46 let randIndex = 2 * randInt(4);
47 const bishop1Pos = positions[randIndex];
48 // The second bishop must be on a square of different color
49 let randIndex_tmp = 2 * randInt(4) + 1;
50 const bishop2Pos = positions[randIndex_tmp];
51 // Remove chosen squares
52 positions.splice(Math.max(randIndex, randIndex_tmp), 1);
53 positions.splice(Math.min(randIndex, randIndex_tmp), 1);
54
55 // Place the king at random on (remaining squares of) first row
56 let maxIndex = 4;
57 if (positions[maxIndex-1] >= 4)
58 maxIndex--;
59 if (positions[maxIndex-1] >= 4)
60 maxIndex--;
61 randIndex = randInt(maxIndex);
62 const kingPos = positions[randIndex];
63 positions.splice(randIndex, 1);
64
65 // Get random squares for knights
66 randIndex = randInt(5);
67 const knight1Pos = positions[randIndex];
68 positions.splice(randIndex, 1);
69 randIndex = randInt(4);
70 const knight2Pos = positions[randIndex];
71 positions.splice(randIndex, 1);
72
73 // Get random squares for rooks
74 randIndex = randInt(3);
75 const rook1Pos = positions[randIndex];
76 positions.splice(randIndex, 1);
77 randIndex = randInt(2);
78 const rook2Pos = positions[randIndex];
79 positions.splice(randIndex, 1);
80
81 // Queen position is now determined,
82 // because pawns are not placed at random
83 const queenPos = positions[0];
84
85 // Finally put the shuffled pieces in the board array
86 pieces[c][rook1Pos] = "r";
87 pieces[c][knight1Pos] = "n";
88 pieces[c][bishop1Pos] = "b";
89 pieces[c][queenPos] = "q";
90 pieces[c][kingPos] = "k";
91 pieces[c][bishop2Pos] = "b";
92 pieces[c][knight2Pos] = "n";
93 pieces[c][rook2Pos] = "r";
94 pieces[c][4] = "p";
95 pieces[c][5] = "p";
96 }
97 const whiteFen = pieces["w"].join("").toUpperCase();
98 const blackFen = pieces["b"].join("");
99 return (
100 "92/92/92/92/92/92/92/92/92/" +
101 blackFen.substr(5).split("").reverse().join("") +
102 "1" +
103 whiteFen.substr(5).split("").join("") +
104 "/" +
105 blackFen.substr(0,5) +
106 "1" +
107 whiteFen.substr(0,5).split("").reverse().join("") +
108 " w 0"
109 );
110 }
111
112 getPotentialPawnMoves([x, y]) {
113 // Normal moves (as a rook)
114 let moves =
115 this.getSlideNJumpMoves([x, y], V.steps[V.ROOK]).filter(m => {
116 // Remove captures. Alt: redefine canTake
117 return m.vanish.length == 1;
118 });
119
120 // Captures (in both directions)
121 for (let shiftX of [-1, 1]) {
122 for (let shiftY of [-1, 1]) {
123 if (
124 V.OnBoard(x + shiftX, y + shiftY) &&
125 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
126 this.canTake([x, y], [x + shiftX, y + shiftY])
127 ) {
128 moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
129 }
130 }
131 }
132
133 return moves;
134 }
135
136 getPotentialKnightMoves(sq) {
137 // Knight becomes knightrider:
138 return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]);
139 }
140
141 filterValid(moves) {
142 if (moves.length == 0) return [];
143 const color = this.turn;
144 const oppCol = V.GetOppCol(color);
145 return moves.filter(m => {
146 this.play(m);
147 // Giving check is forbidden as well:
148 const res = !this.underCheck(color) && !this.underCheck(oppCol);
149 this.undo(m);
150 return res;
151 });
152 }
153
154 isAttackedByPawn([x, y], color) {
155 // Pawns can capture forward and backward:
156 for (let pawnShift of [-1, 1]) {
157 if (0 < x + pawnShift && x + pawnShift < V.size.x) {
158 for (let i of [-1, 1]) {
159 if (
160 y + i >= 0 &&
161 y + i < V.size.y &&
162 this.getPiece(x + pawnShift, y + i) == V.PAWN &&
163 this.getColor(x + pawnShift, y + i) == color
164 ) {
165 return true;
166 }
167 }
168 }
169 }
170 return false;
171 }
172
173 isAttackedByKnight(sq, color) {
174 return this.isAttackedBySlideNJump(
175 sq,
176 color,
177 V.KNIGHT,
178 V.steps[V.KNIGHT]
179 );
180 }
181
182 getCurrentScore() {
183 // Turn has changed:
184 const color = V.GetOppCol(this.turn);
185 if (this.kingPos[color][0] == 0)
186 // The opposing edge is reached!
187 return color == "w" ? "1-0" : "0-1";
188 if (this.atLeastOneMove()) return "*";
189 // Stalemate (will probably never happen)
190 return "1/2";
191 }
192
193 static get SEARCH_DEPTH() {
194 return 2;
195 }
196
197 static get VALUES() {
198 return {
199 p: 2,
200 r: 5,
201 n: 3,
202 b: 3,
203 q: 9,
204 k: 1000
205 };
206 }
207
208 evalPosition() {
209 // Count material:
210 let evaluation = super.evalPosition();
211 // Ponder with king position:
212 return evaluation/5 + this.kingPos["b"][0] - this.kingPos["w"][0];
213 }
214
215 getNotation(move) {
216 // Since pawns are much more mobile, treat them as other pieces:
217 return (
218 move.vanish[0].p.toUpperCase() +
219 (move.vanish.length > move.appear.length ? "x" : "") +
220 V.CoordsToSquare(move.end)
221 );
222 }
223
224 };