A few small fixes + add Monster variant
[vchess.git] / client / src / variants / Monster.js
1 import { ChessRules } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class MonsterRules extends ChessRules {
5 static IsGoodFlags(flags) {
6 // Only black can castle
7 return !!flags.match(/^[a-z]{2,2}$/);
8 }
9
10 static GenRandInitFen(randomness) {
11 if (randomness == 2) randomness--;
12 const fen = ChessRules.GenRandInitFen(randomness);
13 return (
14 // 26 first chars are 6 rows + 6 slashes
15 fen.substr(0, 26)
16 // En passant available, and "half-castle"
17 .concat("2PPPP2/4K3 w 0 ")
18 .concat(fen.substr(-6, 2))
19 .concat(" -")
20 );
21 }
22
23 getFlagsFen() {
24 return this.castleFlags['b'].map(V.CoordToColumn).join("");
25 }
26
27 setFlags(fenflags) {
28 this.castleFlags = { 'b': [-1, -1] };
29 for (let i = 0; i < 2; i++)
30 this.castleFlags['b'][i] = V.ColumnToCoord(fenflags.charAt(i));
31 }
32
33 setOtherVariables(fen) {
34 super.setOtherVariables(fen);
35 this.subTurn = 1;
36 }
37
38 getPotentialKingMoves([x, y]) {
39 if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]);
40 // White doesn't castle:
41 return this.getSlideNJumpMoves(
42 [x, y],
43 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
44 "oneStep"
45 );
46 }
47
48 isAttacked(sq, color, castling) {
49 const singleMoveAttack = super.isAttacked(sq, color);
50 if (singleMoveAttack) return true;
51 if (color == 'b' || !!castling) return singleMoveAttack;
52 // Attacks by white: double-move allowed
53 const curTurn = this.turn;
54 this.turn = 'w';
55 const w1Moves = super.getAllPotentialMoves();
56 this.turn = curTurn;
57 for (let move of w1Moves) {
58 this.play(move);
59 const res = super.isAttacked(sq, 'w');
60 this.undo(move);
61 if (res) return res;
62 }
63 return false;
64 }
65
66 play(move) {
67 move.flags = JSON.stringify(this.aggregateFlags());
68 if (this.turn == 'b' || this.subTurn == 2)
69 this.epSquares.push(this.getEpSquare(move));
70 else this.epSquares.push(null);
71 V.PlayOnBoard(this.board, move);
72 if (this.turn == 'w') {
73 if (this.subTurn == 1) this.movesCount++;
74 else this.turn = 'b';
75 this.subTurn = 3 - this.subTurn;
76 } else {
77 this.turn = 'w';
78 this.movesCount++;
79 }
80 this.postPlay(move);
81 }
82
83 updateCastleFlags(move, piece) {
84 // Only black can castle:
85 const firstRank = 0;
86 if (piece == V.KING && move.appear[0].c == 'b')
87 this.castleFlags['b'] = [8, 8];
88 else if (
89 move.start.x == firstRank &&
90 this.castleFlags['b'].includes(move.start.y)
91 ) {
92 const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
93 this.castleFlags['b'][flagIdx] = 8;
94 }
95 else if (
96 move.end.x == firstRank &&
97 this.castleFlags['b'].includes(move.end.y)
98 ) {
99 const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
100 this.castleFlags['b'][flagIdx] = 8;
101 }
102 }
103
104 postPlay(move) {
105 // Definition of 'c' in base class doesn't work:
106 const c = move.vanish[0].c;
107 const piece = move.vanish[0].p;
108 if (piece == V.KING && move.appear.length > 0) {
109 this.kingPos[c][0] = move.appear[0].x;
110 this.kingPos[c][1] = move.appear[0].y;
111 return;
112 }
113 this.updateCastleFlags(move, piece);
114 }
115
116 undo(move) {
117 this.epSquares.pop();
118 this.disaggregateFlags(JSON.parse(move.flags));
119 V.UndoOnBoard(this.board, move);
120 if (this.turn == 'w') {
121 if (this.subTurn == 2) this.subTurn = 1;
122 else this.turn = 'b';
123 this.movesCount--;
124 } else {
125 this.turn = 'w';
126 this.subTurn = 2;
127 }
128 this.postUndo(move);
129 }
130
131 filterValid(moves) {
132 if (this.turn == 'w' && this.subTurn == 1) {
133 return moves.filter(m1 => {
134 this.play(m1);
135 // NOTE: no recursion because next call will see subTurn == 2
136 const res = super.atLeastOneMove();
137 this.undo(m1);
138 return res;
139 });
140 }
141 return super.filterValid(moves);
142 }
143
144 static get SEARCH_DEPTH() {
145 return 1;
146 }
147
148 getComputerMove() {
149 const color = this.turn;
150 if (color == 'w') {
151 // Generate all sequences of 2-moves
152 const moves1 = this.getAllValidMoves();
153 moves1.forEach(m1 => {
154 m1.eval = -V.INFINITY;
155 m1.move2 = null;
156 this.play(m1);
157 const moves2 = this.getAllValidMoves();
158 moves2.forEach(m2 => {
159 this.play(m2);
160 const eval2 = this.evalPosition();
161 this.undo(m2);
162 if (eval2 > m1.eval) {
163 m1.eval = eval2;
164 m1.move2 = m2;
165 }
166 });
167 this.undo(m1);
168 });
169 moves1.sort((a, b) => b.eval - a.eval);
170 let candidates = [0];
171 for (
172 let i = 1;
173 i < moves1.length && moves1[i].eval == moves1[0].eval;
174 i++
175 ) {
176 candidates.push(i);
177 }
178 const idx = candidates[randInt(candidates.length)];
179 const move2 = moves1[idx].move2;
180 delete moves1[idx]["move2"];
181 return [moves1[idx], move2];
182 }
183 // For black at depth 1, super method is fine:
184 return super.getComputerMove();
185 }
186 };