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