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