Fix parseInt() usage, rename Doubleorda --> Ordamirror, implement Clorange variant
[vchess.git] / client / src / variants / Pacifist1.js
1 import { ChessRules } from "@/base_rules";
2
3 export class Pacifist1Rules extends ChessRules {
4 static get PawnSpecs() {
5 return Object.assign(
6 {},
7 ChessRules.PawnSpecs,
8 { canCapture: false }
9 );
10 }
11
12 static get HasEnpassant() {
13 return false;
14 }
15
16 static IsGoodPosition(position) {
17 if (position.length == 0) return false;
18 const rows = position.split("/");
19 if (rows.length != V.size.x) return false;
20 let kingsCount = 0;
21 for (let row of rows) {
22 let sumElts = 0;
23 for (let i = 0; i < row.length; i++) {
24 if (['K','k'].includes(row[i])) kingsCount++;
25 if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
26 else {
27 const num = parseInt(row[i], 10);
28 if (isNaN(num)) return false;
29 sumElts += num;
30 }
31 }
32 if (sumElts != V.size.y) return false;
33 }
34 // Both kings should be on board. May be of the same color.
35 if (kingsCount != 2) return false;
36 return true;
37 }
38
39 scanKings(fen) {
40 // Kings may be swapped, so they are not tracked (no kingPos)
41 this.INIT_COL_KING = { w: -1, b: -1 };
42 const fenRows = V.ParseFen(fen).position.split("/");
43 const startRow = { 'w': V.size.x - 1, 'b': 0 };
44 for (let i = 0; i < fenRows.length; i++) {
45 let k = 0; //column index on board
46 for (let j = 0; j < fenRows[i].length; j++) {
47 switch (fenRows[i].charAt(j)) {
48 case "k":
49 this.INIT_COL_KING["b"] = k;
50 break;
51 case "K":
52 this.INIT_COL_KING["w"] = k;
53 break;
54 default: {
55 const num = parseInt(fenRows[i].charAt(j), 10);
56 if (!isNaN(num)) k += num - 1;
57 }
58 }
59 k++;
60 }
61 }
62 }
63
64 // Sum white pieces attacking a square, and remove black pieces count.
65 sumAttacks([x, y]) {
66 const getSign = (color) => {
67 return (color == 'w' ? 1 : -1);
68 };
69 let res = 0;
70 // Knights:
71 V.steps[V.KNIGHT].forEach(s => {
72 const [i, j] = [x + s[0], y + s[1]];
73 if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KNIGHT)
74 res += getSign(this.getColor(i, j));
75 });
76 // Kings:
77 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
78 const [i, j] = [x + s[0], y + s[1]];
79 if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KING)
80 res += getSign(this.getColor(i, j));
81 });
82 // Pawns:
83 for (let c of ['w', 'b']) {
84 for (let shift of [-1, 1]) {
85 const sign = getSign(c);
86 const [i, j] = [x + sign, y + shift];
87 if (
88 V.OnBoard(i, j) &&
89 this.getPiece(i, j) == V.PAWN &&
90 this.getColor(i, j) == c
91 ) {
92 res += sign;
93 }
94 }
95 }
96 // Other pieces (sliders):
97 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
98 let [i, j] = [x + s[0], y + s[1]];
99 let compatible = [V.QUEEN];
100 compatible.push(s[0] == 0 || s[1] == 0 ? V.ROOK : V.BISHOP);
101 let firstCol = undefined;
102 while (V.OnBoard(i, j)) {
103 if (this.board[i][j] != V.EMPTY) {
104 if (!(compatible.includes(this.getPiece(i, j)))) break;
105 const colIJ = this.getColor(i, j);
106 if (!firstCol) firstCol = colIJ;
107 if (colIJ == firstCol) res += getSign(colIJ);
108 else break;
109 }
110 i += s[0];
111 j += s[1];
112 }
113 });
114 return res;
115 }
116
117 getPotentialMovesFrom([x, y]) {
118 let moves = super.getPotentialMovesFrom([x, y]);
119 const color = this.turn;
120 const oppCol = V.GetOppCol(color);
121 if (this.getPiece(x, y) == V.PAWN) {
122 // Pawns cannot move 2 squares if the intermediate is overly persuaded
123 moves = moves.filter(m => {
124 if (Math.abs(m.end.x - m.start.x) == 2) {
125 const [i, j] = [(m.start.x + m.end.x) / 2, y];
126 const persuasion = this.sumAttacks([i, j]);
127 return (
128 color == 'w' && persuasion >= 0 ||
129 color == 'b' && persuasion <= 0
130 );
131 }
132 return true;
133 });
134 }
135 // Potentially flipped (opp) pieces
136 let targets = [];
137 for (let i=0; i<8; i++) {
138 for (let j=0; j<8; j++) {
139 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == oppCol)
140 targets.push([i, j]);
141 }
142 }
143 moves.forEach(m => {
144 // Start persuading other pieces: loop until nothing changes
145 V.PlayOnBoard(this.board, m);
146 while (true) {
147 let persuaded = [];
148 targets.forEach(t => {
149 if (this.getColor(t[0], t[1]) == oppCol) {
150 const sqAttacks = this.sumAttacks([t[0], t[1]]);
151 if (
152 (oppCol == 'w' && sqAttacks < 0) ||
153 (oppCol == 'b' && sqAttacks > 0)
154 ) {
155 persuaded.push(t);
156 }
157 }
158 });
159 if (persuaded.length == 0) break;
160 persuaded.forEach(p => {
161 this.board[p[0]][p[1]] = color + this.getPiece(p[0], p[1]);
162 });
163 }
164 V.UndoOnBoard(this.board, m);
165 // Reset pieces colors + adjust move (flipped pieces)
166 targets.forEach(t => {
167 if (this.getColor(t[0], t[1]) == color) {
168 const piece = this.getPiece(t[0], t[1]);
169 m.appear.push({ x: t[0], y: t[1], c: color, p: piece });
170 m.vanish.push({ x: t[0], y: t[1], c: oppCol, p: piece });
171 this.board[t[0]][t[1]] = oppCol + piece;
172 }
173 });
174 });
175 return moves;
176 }
177
178 getSlideNJumpMoves([x, y], steps, oneStep) {
179 let moves = [];
180 outerLoop: for (let loop = 0; loop < steps.length; loop++) {
181 const step = steps[loop];
182 let i = x + step[0];
183 let j = y + step[1];
184 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
185 moves.push(this.getBasicMove([x, y], [i, j]));
186 if (oneStep) continue outerLoop;
187 i += step[0];
188 j += step[1];
189 }
190 // No captures
191 }
192 return moves;
193 }
194
195 underCheck(color) {
196 // Find the king(s) and determine if it (both) is persuaded.
197 // If yes, "under check"
198 let kingPos = [];
199 for (let i=0; i<8; i++) {
200 for (let j=0; j<8; j++) {
201 if (this.getPiece(i, j) == V.KING && this.getColor(i, j) == color)
202 kingPos.push([i, j]);
203 }
204 }
205 return kingPos.every(kp => {
206 const persuasion = this.sumAttacks(kp);
207 return (
208 (color == 'w' && persuasion < 0) ||
209 (color == 'b' && persuasion > 0)
210 );
211 });
212 }
213
214 filterValid(moves) {
215 const fmoves = super.filterValid(moves);
216 const color = this.turn;
217 // If the king isn't here, only moves persuading a king are valid
218 const kingHere = this.board.some(b =>
219 b.some(cell => cell[0] == color && cell[1] == V.KING)
220 );
221 if (!kingHere) {
222 return (
223 fmoves.filter(m => m.appear.some(a => a.c == color && a.p == V.KING))
224 );
225 }
226 return fmoves;
227 }
228
229 getCheckSquares() {
230 // There are not really "checks": just color change
231 return [];
232 }
233
234 getCurrentScore() {
235 const color = this.turn;
236 // TODO: if no king of turn color, and no move to get one, then it's lost
237 // otherwise 1/2 if no moves, or "*"
238 const kingHere = this.board.some(b =>
239 b.some(cell => cell[0] == color && cell[1] == V.KING)
240 );
241 if (kingHere) {
242 if (this.atLeastOneMove()) return "*";
243 return "1/2";
244 }
245 // No king was found: try to convert one
246 const moves = this.getAllValidMoves();
247 return (
248 moves.some(m => m.appear.some(a => a.c == color && a.p == V.KING))
249 ? "*"
250 : (color == 'w' ? "0-1" : "1-0")
251 );
252 }
253
254 postPlay(move) {
255 this.updateCastleFlags(move, move.vanish[0].p);
256 }
257
258 postUndo() {}
259
260 static get SEARCH_DEPTH() {
261 return 1;
262 }
263 };