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