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