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