More balanced Shinobi according to Couch Tomato + Fables tests
[vchess.git] / client / src / variants / Alapo.js
1 import { ChessRules } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt } from "@/utils/alea";
4
5 export class AlapoRules extends ChessRules {
6
7 static get HasFlags() {
8 return false;
9 }
10
11 static get HasEnpassant() {
12 return false;
13 }
14
15 static get Lines() {
16 return [
17 [[1, 0], [1, 6]],
18 [[5, 0], [5, 6]]
19 ];
20 }
21
22 static get PIECES() {
23 return [V.ROOK, V.BISHOP, V.QUEEN, V.ROOK_S, V.BISHOP_S, V.QUEEN_S];
24 }
25
26 static get ROOK_S() {
27 return "t";
28 }
29 static get BISHOP_S() {
30 return "c";
31 }
32 static get QUEEN_S() {
33 return "s";
34 }
35
36 getPotentialMinirookMoves(sq) {
37 return super.getSlideNJumpMoves(sq, V.steps[V.ROOK], "oneStep");
38 }
39 getPotentialMinibishopMoves(sq) {
40 return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep");
41 }
42 getPotentialMiniqueenMoves(sq) {
43 return (
44 super.getSlideNJumpMoves(
45 sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep")
46 );
47 }
48
49 getPotentialMovesFrom(sq) {
50 switch (this.getPiece(sq[0], sq[1])) {
51 case V.ROOK: return super.getPotentialRookMoves(sq);
52 case V.BISHOP: return super.getPotentialBishopMoves(sq);
53 case V.QUEEN: return super.getPotentialQueenMoves(sq);
54 case V.ROOK_S: return this.getPotentialMinirookMoves(sq);
55 case V.BISHOP_S: return this.getPotentialMinibishopMoves(sq);
56 case V.QUEEN_S: return this.getPotentialMiniqueenMoves(sq);
57 }
58 return [];
59 }
60
61 static get size() {
62 return { x: 6, y: 6 };
63 }
64
65 getPpath(b, color, score, orientation) {
66 // 'i' for "inversed":
67 const suffix = (b[0] == orientation ? "" : "i");
68 return "Alapo/" + b + suffix;
69 }
70
71 static GenRandInitFen(randomness) {
72 if (randomness == 0)
73 return "rbqqbr/tcssct/6/6/TCSSCT/RBQQBR w 0";
74
75 const piece2pawn = {
76 r: 't',
77 q: 's',
78 b: 'c'
79 };
80
81 let pieces = { w: new Array(6), b: new Array(6) };
82 // Shuffle pieces on first (and last rank if randomness == 2)
83 for (let c of ["w", "b"]) {
84 if (c == 'b' && randomness == 1) {
85 pieces['b'] = pieces['w'];
86 break;
87 }
88
89 let positions = ArrayFun.range(6);
90
91 // Get random squares for bishops
92 let randIndex = 2 * randInt(3);
93 const bishop1Pos = positions[randIndex];
94 let randIndex_tmp = 2 * randInt(3) + 1;
95 const bishop2Pos = positions[randIndex_tmp];
96 positions.splice(Math.max(randIndex, randIndex_tmp), 1);
97 positions.splice(Math.min(randIndex, randIndex_tmp), 1);
98
99 // Get random square for queens
100 randIndex = randInt(4);
101 const queen1Pos = positions[randIndex];
102 positions.splice(randIndex, 1);
103 randIndex = randInt(3);
104 const queen2Pos = positions[randIndex];
105 positions.splice(randIndex, 1);
106
107 // Rooks positions are now fixed,
108 const rook1Pos = positions[0];
109 const rook2Pos = positions[1];
110
111 pieces[c][rook1Pos] = "r";
112 pieces[c][bishop1Pos] = "b";
113 pieces[c][queen1Pos] = "q";
114 pieces[c][queen2Pos] = "q";
115 pieces[c][bishop2Pos] = "b";
116 pieces[c][rook2Pos] = "r";
117 }
118
119 return (
120 pieces["b"].join("") + "/" +
121 pieces["b"].map(p => piece2pawn[p]).join("") +
122 "/6/6/" +
123 pieces["w"].map(p => piece2pawn[p].toUpperCase()).join("") + "/" +
124 pieces["w"].join("").toUpperCase() +
125 " w 0"
126 );
127 }
128
129 static IsGoodPosition(position) {
130 if (position.length == 0) return false;
131 const rows = position.split("/");
132 if (rows.length != V.size.x) return false;
133 // Just check that at least one piece of each color is there:
134 let pieces = { "w": 0, "b": 0 };
135 for (let row of rows) {
136 let sumElts = 0;
137 for (let i = 0; i < row.length; i++) {
138 const lowerRi = row[i].toLowerCase();
139 if (V.PIECES.includes(lowerRi)) {
140 pieces[row[i] == lowerRi ? "b" : "w"]++;
141 sumElts++;
142 }
143 else {
144 const num = parseInt(row[i], 10);
145 if (isNaN(num)) return false;
146 sumElts += num;
147 }
148 }
149 if (sumElts != V.size.y) return false;
150 }
151 if (Object.values(pieces).some(v => v == 0)) return false;
152 return true;
153 }
154
155 // Find possible captures by opponent on [x, y]
156 findCaptures([x, y]) {
157 const color = this.getColor(x, y);
158 let moves = [];
159 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
160 const oppCol = V.GetOppCol(color);
161 for (let loop = 0; loop < steps.length; loop++) {
162 const step = steps[loop];
163 let i = x + step[0];
164 let j = y + step[1];
165 let stepsAfter = 1;
166 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
167 i += step[0];
168 j += step[1];
169 stepsAfter++;
170 }
171 if (
172 V.OnBoard(i, j) &&
173 this.board[i][j] != V.EMPTY &&
174 this.getColor(i, j) == oppCol
175 ) {
176 const oppPiece = this.getPiece(i, j);
177 if (
178 (
179 stepsAfter >= 2 &&
180 [V.ROOK_S, V.BISHOP_S, V.QUEEN_S].includes(oppPiece)
181 )
182 ||
183 (
184 [V.BISHOP, V.BISHOP_S].includes(oppPiece) &&
185 step.some(e => e == 0)
186 )
187 ||
188 (
189 [V.ROOK, V.ROOK_S].includes(oppPiece) &&
190 step.every(e => e != 0)
191 )
192 ) {
193 continue;
194 }
195 return true;
196 }
197 }
198 return false;
199 }
200
201 postPlay() {}
202 postUndo() {}
203
204 getCheckSquares() {
205 return [];
206 }
207 filterValid(moves) {
208 return moves;
209 }
210
211 getCurrentScore() {
212 // Try both colors (to detect potential suicides)
213 for (let c of ['w', 'b']) {
214 const oppCol = V.GetOppCol(c);
215 const goal = (c == 'w' ? 0 : 5);
216 if (
217 this.board[goal].some(
218 (b,j) => b[0] == c && !this.findCaptures([goal, j])
219 )
220 ) {
221 return c == 'w' ? "1-0" : "0-1";
222 }
223 }
224 return super.getCurrentScore();
225 }
226
227 static get VALUES() {
228 return {
229 r: 5,
230 b: 3,
231 q: 9,
232 t: 3,
233 c: 2,
234 s: 5
235 };
236 }
237
238 };