Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Monster.js
CommitLineData
5e1bc651
BA
1import { ChessRules } from "@/base_rules";
2import { randInt } from "@/utils/alea";
3
4export class MonsterRules extends ChessRules {
7e8a7ea1 5
4313762d
BA
6 static get Options() {
7 return {
8 check: [
9 {
10 label: "Random",
11 defaut: false,
12 variable: "random"
13 }
14 ],
15 select: [
16 {
17 label: "Number of pawns",
18 variable: "pawnsCount",
19 defaut: 4,
20 options: [
21 { label: "Two", value: 2 },
22 { label: "Four", value: 4 },
23 { label: "Six", value: 6 }
24 ]
25 }
26 ]
27 };
28 }
29
30 static AbbreviateOptions(opts) {
31 return opts["pawnsCount"];
32 }
33
5e1bc651
BA
34 static IsGoodFlags(flags) {
35 // Only black can castle
36 return !!flags.match(/^[a-z]{2,2}$/);
37 }
38
4313762d
BA
39 static GenRandInitFen(options) {
40 const baseFen = ChessRules.GenRandInitFen(
41 { randomness: (options.random ? 1 : 0) });
42 let pawnsLine = "";
43 switch (options.pawnsCount) {
44 case 2: pawnsLine = "3PP3"; break;
45 case 4: pawnsLine = "2PPPP2"; break;
46 case 6: pawnsLine = "1PPPPPP1"; break;
47 }
5e1bc651
BA
48 return (
49 // 26 first chars are 6 rows + 6 slashes
4313762d 50 baseFen.substr(0, 26) + pawnsLine + "/4K3 w 0 " +
5e1bc651 51 // En passant available, and "half-castle"
4313762d 52 baseFen.substr(-6, 2) + " -"
5e1bc651
BA
53 );
54 }
55
56 getFlagsFen() {
57 return this.castleFlags['b'].map(V.CoordToColumn).join("");
58 }
59
60 setFlags(fenflags) {
61 this.castleFlags = { 'b': [-1, -1] };
62 for (let i = 0; i < 2; i++)
63 this.castleFlags['b'][i] = V.ColumnToCoord(fenflags.charAt(i));
64 }
65
66 setOtherVariables(fen) {
67 super.setOtherVariables(fen);
68 this.subTurn = 1;
69 }
70
71 getPotentialKingMoves([x, y]) {
72 if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]);
73 // White doesn't castle:
74 return this.getSlideNJumpMoves(
4313762d 75 [x, y], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1);
5e1bc651
BA
76 }
77
34bfe151 78 isAttacked() {
bc0b9205 79 // Goal is king capture => no checks
5e1bc651
BA
80 return false;
81 }
82
bc0b9205
BA
83 filterValid(moves) {
84 return moves;
85 }
86
87 getCheckSquares() {
88 return [];
89 }
90
91 getCurrentScore() {
92 const color = this.turn;
93 if (this.kingPos[color][0] < 0) return (color == 'w' ? "0-1" : "1-0");
94 return "*";
95 }
96
5e1bc651
BA
97 play(move) {
98 move.flags = JSON.stringify(this.aggregateFlags());
99 if (this.turn == 'b' || this.subTurn == 2)
100 this.epSquares.push(this.getEpSquare(move));
101 else this.epSquares.push(null);
102 V.PlayOnBoard(this.board, move);
103 if (this.turn == 'w') {
104 if (this.subTurn == 1) this.movesCount++;
964eda04
BA
105 if (
106 this.subTurn == 2 ||
107 // King captured
108 (move.vanish.length == 2 && move.vanish[1].p == V.KING)
109 ) {
110 this.turn = 'b';
111 this.subTurn = 1;
112 }
113 else this.subTurn = 2;
114 }
115 else {
5e1bc651
BA
116 this.turn = 'w';
117 this.movesCount++;
118 }
119 this.postPlay(move);
120 }
121
122 updateCastleFlags(move, piece) {
123 // Only black can castle:
124 const firstRank = 0;
125 if (piece == V.KING && move.appear[0].c == 'b')
126 this.castleFlags['b'] = [8, 8];
127 else if (
128 move.start.x == firstRank &&
129 this.castleFlags['b'].includes(move.start.y)
130 ) {
131 const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
132 this.castleFlags['b'][flagIdx] = 8;
133 }
134 else if (
135 move.end.x == firstRank &&
136 this.castleFlags['b'].includes(move.end.y)
137 ) {
138 const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
139 this.castleFlags['b'][flagIdx] = 8;
140 }
141 }
142
143 postPlay(move) {
144 // Definition of 'c' in base class doesn't work:
145 const c = move.vanish[0].c;
146 const piece = move.vanish[0].p;
bc0b9205
BA
147 if (piece == V.KING)
148 this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
964eda04 149 if (move.vanish.length == 2 && move.vanish[1].p == V.KING) {
bc0b9205
BA
150 // Opponent's king is captured, game over
151 this.kingPos[move.vanish[1].c] = [-1, -1];
964eda04
BA
152 move.captureKing = true; //for undo
153 }
5e1bc651
BA
154 this.updateCastleFlags(move, piece);
155 }
156
157 undo(move) {
158 this.epSquares.pop();
159 this.disaggregateFlags(JSON.parse(move.flags));
160 V.UndoOnBoard(this.board, move);
161 if (this.turn == 'w') {
162 if (this.subTurn == 2) this.subTurn = 1;
163 else this.turn = 'b';
164 this.movesCount--;
bc0b9205
BA
165 }
166 else {
5e1bc651 167 this.turn = 'w';
964eda04 168 this.subTurn = (!move.captureKing ? 2 : 1);
5e1bc651
BA
169 }
170 this.postUndo(move);
171 }
172
bc0b9205
BA
173 postUndo(move) {
174 if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
175 // Opponent's king was captured
176 this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y];
177 super.postUndo(move);
5e1bc651
BA
178 }
179
bc0b9205 180 // Custom search at depth 1(+1)
5e1bc651 181 getComputerMove() {
bc0b9205 182 const getBestWhiteMove = (terminal) => {
5e1bc651 183 // Generate all sequences of 2-moves
bc0b9205 184 let moves1 = this.getAllValidMoves();
5e1bc651
BA
185 moves1.forEach(m1 => {
186 m1.eval = -V.INFINITY;
187 m1.move2 = null;
188 this.play(m1);
bc0b9205
BA
189 if (!!terminal) m1.eval = this.evalPosition();
190 else {
191 const moves2 = this.getAllValidMoves();
192 moves2.forEach(m2 => {
193 this.play(m2);
194 const eval2 = this.evalPosition() + 0.05 - Math.random() / 10;
195 this.undo(m2);
196 if (eval2 > m1.eval) {
197 m1.eval = eval2;
198 m1.move2 = m2;
199 }
200 });
201 }
5e1bc651
BA
202 this.undo(m1);
203 });
204 moves1.sort((a, b) => b.eval - a.eval);
bc0b9205
BA
205 if (!!terminal)
206 // The move itself doesn't matter, only its eval:
207 return moves1[0];
5e1bc651
BA
208 let candidates = [0];
209 for (
210 let i = 1;
211 i < moves1.length && moves1[i].eval == moves1[0].eval;
212 i++
213 ) {
214 candidates.push(i);
215 }
216 const idx = candidates[randInt(candidates.length)];
217 const move2 = moves1[idx].move2;
218 delete moves1[idx]["move2"];
219 return [moves1[idx], move2];
bc0b9205
BA
220 };
221
222 const getBestBlackMove = () => {
223 let moves = this.getAllValidMoves();
224 moves.forEach(m => {
225 m.eval = V.INFINITY;
226 this.play(m);
227 const evalM = getBestWhiteMove("terminal").eval
228 this.undo(m);
229 if (evalM < m.eval) m.eval = evalM;
230 });
231 moves.sort((a, b) => a.eval - b.eval);
232 let candidates = [0];
233 for (
234 let i = 1;
235 i < moves.length && moves[i].eval == moves[0].eval;
236 i++
237 ) {
238 candidates.push(i);
239 }
240 const idx = candidates[randInt(candidates.length)];
241 return moves[idx];
242 };
243
244 const color = this.turn;
245 return (color == 'w' ? getBestWhiteMove() : getBestBlackMove());
5e1bc651 246 }
7e8a7ea1 247
5e1bc651 248};