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