e4ec04d8f9684b99592035ba5f6b8d787a2f545f
[vchess.git] / client / src / variants / Refusal.js
1 import { ChessRules } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 // TODO: Two moves, both promoting the same pawn, but to a different type of piece, count as two different moves.
5 // ==> need to accept temporarily pawn promotions even if on forbidden square, and check afterward if promoted type changed (info in lastMove)
6
7 export class RefusalRules extends ChessRules {
8
9 static IsGoodFen(fen) {
10 if (!ChessRules.IsGoodFen(fen)) return false;
11 if (!V.ParseFen(fen).lastMove) return false;
12 return true;
13 }
14
15 static ParseFen(fen) {
16 return Object.assign(
17 { lastMove: fen.split(" ")[5] },
18 ChessRules.ParseFen(fen)
19 );
20 }
21
22 getFen() {
23 const L = this.lastMove.length;
24 const lm = this.lastMove[L-1];
25 return super.getFen() + " " + JSON.stringify(lm);
26 }
27
28 // NOTE: with this variant's special rule,
29 // some extra repetitions could be detected... TODO (...)
30
31 static GenRandInitFen(randomness) {
32 return ChessRules.GenRandInitFen(randomness) + " null";
33 }
34
35 setOtherVariables(fen) {
36 super.setOtherVariables(fen);
37 this.lastMove = [JSON.parse(V.ParseFen(fen).lastMove)]; //may be null
38 }
39
40 canIplay(side, [x, y]) {
41 if (super.canIplay(side, [x, y])) return true;
42 if (this.turn != side) return false;
43 // Check if playing last move, reversed:
44 const L = this.lastMove.length;
45 const lm = this.lastMove[L-1];
46 return (!!lm && !lm.noRef && x == lm.end.x && y == lm.end.y);
47 }
48
49 getPotentialMovesFrom([x, y]) {
50 if (this.getColor(x, y) != this.turn) {
51 const L = this.lastMove.length;
52 const lm = this.lastMove[L-1];
53 if (!!lm && !lm.noRef && x == lm.end.x && y == lm.end.y) {
54 let revLm = JSON.parse(JSON.stringify(lm));
55 let tmp = revLm.appear;
56 revLm.appear = revLm.vanish;
57 revLm.vanish = tmp;
58 tmp = revLm.start;
59 revLm.start = revLm.end;
60 revLm.end = tmp;
61 return [revLm];
62 }
63 return [];
64 }
65 return super.getPotentialMovesFrom([x, y]);
66 }
67
68 // NOTE: do not take refusal move into account here (two own moves)
69 atLeastTwoMoves() {
70 let movesCounter = 0;
71 const color = this.turn;
72 for (let i = 0; i < V.size.x; i++) {
73 for (let j = 0; j < V.size.y; j++) {
74 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
75 const moves = this.getPotentialMovesFrom([i, j]);
76 for (let m of moves) {
77 if (m.vanish[0].c == color && this.filterValid([m]).length > 0) {
78 movesCounter++;
79 if (movesCounter >= 2) return true;
80 }
81 }
82 }
83 }
84 }
85 return false;
86 }
87
88 filterValid(moves) {
89 if (moves.length == 0) return [];
90 const color = this.turn;
91 const L = this.lastMove.length;
92 const lm = this.lastMove[L-1];
93 return moves.filter(m => {
94 if (
95 !!lm && !!lm.refusal &&
96 m.start.x == lm.end.x && m.start.y == lm.end.y &&
97 m.end.x == lm.start.x && m.end.y == lm.start.y
98 ) {
99 return false;
100 }
101 // NOTE: not using this.play()/undo() ==> infinite loop
102 V.PlayOnBoard(this.board, m);
103 if (m.appear[0].p == V.KING)
104 this.kingPos[m.appear[0].c] = [m.appear[0].x, m.appear[0].y];
105 const res = !this.underCheck(color);
106 V.UndoOnBoard(this.board, m);
107 if (m.vanish[0].p == V.KING)
108 this.kingPos[m.vanish[0].c] = [m.vanish[0].x, m.vanish[0].y];
109 return res;
110 });
111 }
112
113 prePlay(move) {
114 const L = this.lastMove.length;
115 const lm = this.lastMove[L-1];
116 if (
117 // My previous move was already refused?
118 (!!lm && this.getColor(lm.end.x, lm.end.y) == this.turn) ||
119 // I've only one move available?
120 !this.atLeastTwoMoves()
121 ) {
122 move.noRef = true;
123 }
124 // NOTE: refusal could be recomputed, but, it's easier like this
125 if (move.vanish[0].c != this.turn) move.refusal = true;
126 }
127
128 getEpSquare(move) {
129 if (!move.refusal) return super.getEpSquare(move);
130 return null; //move refusal
131 }
132
133 postPlay(move) {
134 if (!move.refusal) super.postPlay(move);
135 else {
136 const L = this.lastMove.length;
137 const lm = this.lastMove[L-1];
138 this.disaggregateFlags(JSON.parse(lm.flags));
139 }
140 // NOTE: explicitely give fields, because some are assigned in BaseGame
141 let mvInLm = {
142 start: move.start,
143 end: move.end,
144 appear: move.appear,
145 vanish: move.vanish,
146 flags: move.flags
147 };
148 if (!!move.noRef) mvInLm.noRef = true;
149 if (!!move.refusal) mvInLm.refusal = true;
150 this.lastMove.push(mvInLm);
151 }
152
153 postUndo(move) {
154 if (!move.refusal) super.postUndo(move);
155 this.lastMove.pop();
156 }
157
158 getAllPotentialMoves() {
159 const color = this.turn;
160 const L = this.lastMove.length;
161 const lm = this.lastMove[L-1];
162 let potentialMoves = [];
163 for (let i = 0; i < V.size.x; i++) {
164 for (let j = 0; j < V.size.y; j++) {
165 if (
166 this.board[i][j] != V.EMPTY &&
167 (
168 this.getColor(i, j) == color ||
169 // Add move refusal:
170 (!!lm && lm.end.x == i && lm.end.y == j)
171 )
172 ) {
173 Array.prototype.push.apply(
174 potentialMoves,
175 this.getPotentialMovesFrom([i, j])
176 );
177 }
178 }
179 }
180 return potentialMoves;
181 }
182
183 getComputerMove() {
184 // Just play at random for now... (TODO?)
185 // Refuse last move with odds 1/3.
186 const moves = this.getAllValidMoves();
187 const refusal = moves.find(m => m.vanish[0].c != this.turn);
188 if (!!refusal) {
189 if (Math.random() <= 0.33) return refusal;
190 const others = moves.filter(m => m.vanish[0].c == this.turn);
191 return others[randInt(others.length)];
192 }
193 else return moves[randInt(moves.length)];
194 }
195
196 getNotation(move) {
197 if (move.vanish[0].c != this.turn) return "Refuse";
198 return super.getNotation(move);
199 }
200
201 };