Draft 2 new variants. Still 4 to add in this series
[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("").join("") +
88 "/" +
89 whiteFen.substr(0,5) +
90 "1" +
91 blackFen.substr(0,5).split("").reverse().join("") +
92 " w 0"
93 );
94 }
95
96 getPotentialPawnMoves([x, y]) {
97 // Normal moves (as a rook)
98 let moves =
99 this.getSlideNJumpMoves([x, y], V.steps[V.ROOK]).filter(m => {
100 // Remove captures. Alt: redefine canTake
101 return m.vanish.length == 1;
102 });
103
104 // Captures (in both directions)
105 for (let shiftX of [-1, 1]) {
106 for (let shiftY of [-1, 1]) {
107 if (
108 V.OnBoard(x + shiftX, y + shiftY) &&
109 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
110 this.canTake([x, y], [x + shiftX, y + shiftY])
111 ) {
112 moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
113 }
114 }
115 }
116
117 return moves;
118 }
119
120 getPotentialKnightMoves(sq) {
121 // Knight becomes knightrider:
122 return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]);
123 }
124
125 // What are the king moves from square x,y ?
126 getPotentialKingMoves(sq) {
127 return this.getSlideNJumpMoves(
128 sq,
129 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
130 "oneStep"
131 );
132 }
133
134 filterValid(moves) {
135 if (moves.length == 0) return [];
136 const color = this.turn;
137 const oppCol = V.GetOppCol(color);
138 return moves.filter(m => {
139 this.play(m);
140 // Giving check is forbidden as well:
141 const res = !this.underCheck(color) && !this.underCheck(oppCol);
142 this.undo(m);
143 return res;
144 });
145 }
146
147 isAttackedByPawn([x, y], colors) {
148 const pawnShift = 1;
149 if (x + pawnShift < V.size.x) {
150 for (let c of colors) {
151 for (let i of [-1, 1]) {
152 if (
153 y + i >= 0 &&
154 y + i < V.size.y &&
155 this.getPiece(x + pawnShift, y + i) == V.PAWN &&
156 this.getColor(x + pawnShift, y + i) == c
157 ) {
158 return true;
159 }
160 }
161 }
162 }
163 return false;
164 }
165
166 isAttackedByKnight(sq, colors) {
167 return this.isAttackedBySlideNJump(
168 sq,
169 colors,
170 V.KNIGHT,
171 V.steps[V.KNIGHT]
172 );
173 }
174
175 getCurrentScore() {
176 // Turn has changed:
177 const color = V.GetOppCol(this.turn);
178 if (this.kingPos[color][0] == 0)
179 // The opposing edge is reached!
180 return color == "w" ? "1-0" : "0-1";
181 if (this.atLeastOneMove())
182 return "*";
183 // Stalemate (will probably never happen)
184 return "1/2";
185 }
186
187 static get SEARCH_DEPTH() {
188 return 2;
189 }
190
191 static get VALUES() {
192 return {
193 p: 2,
194 r: 5,
195 n: 3,
196 b: 3,
197 q: 9,
198 k: 1000
199 };
200 }
201
202 evalPosition() {
203 // Count material:
204 let evaluation = super.evalPosition();
205 // Ponder with king position:
206 return evaluation/5 + this.kingPos["b"][0] - this.kingPos["w"][0];
207 }
208
209 getNotation(move) {
210 // Since pawns are much more mobile, treat them as other pieces:
211 return (
212 move.vanish[0].p.toUpperCase() +
213 (move.vanish.length > move.appear.length ? "x" : "") +
214 V.CoordsToSquare(move.end)
215 );
216 }
217 };