Some fixes, and add 2 variants: Checkless and Parachute
[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 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 "92/92/92/92/92/92/92/92/92/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 "92/92/92/92/92/92/92/92/92/" +
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 filterValid(moves) {
137 if (moves.length == 0) return [];
138 const color = this.turn;
139 const oppCol = V.GetOppCol(color);
140 return moves.filter(m => {
141 this.play(m);
142 // Giving check is forbidden as well:
143 const res = !this.underCheck(color) && !this.underCheck(oppCol);
144 this.undo(m);
145 return res;
146 });
147 }
148
149 isAttackedByPawn([x, y], color) {
150 // Pawns can capture forward and backward:
151 for (let pawnShift of [-1, 1]) {
152 if (0 < x + pawnShift && x + pawnShift < V.size.x) {
153 for (let i of [-1, 1]) {
154 if (
155 y + i >= 0 &&
156 y + i < V.size.y &&
157 this.getPiece(x + pawnShift, y + i) == V.PAWN &&
158 this.getColor(x + pawnShift, y + i) == color
159 ) {
160 return true;
161 }
162 }
163 }
164 }
165 return false;
166 }
167
168 isAttackedByKnight(sq, color) {
169 return this.isAttackedBySlideNJump(
170 sq,
171 color,
172 V.KNIGHT,
173 V.steps[V.KNIGHT]
174 );
175 }
176
177 getCurrentScore() {
178 // Turn has changed:
179 const color = V.GetOppCol(this.turn);
180 if (this.kingPos[color][0] == 0)
181 // The opposing edge is reached!
182 return color == "w" ? "1-0" : "0-1";
183 if (this.atLeastOneMove()) return "*";
184 // Stalemate (will probably never happen)
185 return "1/2";
186 }
187
188 static get SEARCH_DEPTH() {
189 return 2;
190 }
191
192 static get VALUES() {
193 return {
194 p: 2,
195 r: 5,
196 n: 3,
197 b: 3,
198 q: 9,
199 k: 1000
200 };
201 }
202
203 evalPosition() {
204 // Count material:
205 let evaluation = super.evalPosition();
206 // Ponder with king position:
207 return evaluation/5 + this.kingPos["b"][0] - this.kingPos["w"][0];
208 }
209
210 getNotation(move) {
211 // Since pawns are much more mobile, treat them as other pieces:
212 return (
213 move.vanish[0].p.toUpperCase() +
214 (move.vanish.length > move.appear.length ? "x" : "") +
215 V.CoordsToSquare(move.end)
216 );
217 }
218 };