New variant idea
[xogo.git] / variants / Bario / class.js
1 import ChessRules from "/base_rules.js";
2 import PiPo from "/utils/PiPo.js";
3 import Move from "/utils/Move.js";
4
5 export default class BarioRules extends ChessRules {
6
7 static get Options() {
8 return {
9 select: C.Options.select,
10 input: C.Options.input,
11 styles: [
12 "atomic", "cannibal", "capture", "cylinder",
13 "dark", "madrasi", "rifle", "teleport"
14 ]
15 };
16 }
17
18 // Does not really seem necessary (although the author mention it)
19 // Instead, first move = pick a square for the king.
20 get hasFlags() {
21 return false;
22 }
23 get hasReserve() {
24 return true;
25 }
26
27 pieces(color, x, y) {
28 return Object.assign(
29 { 'u': {"class": "undefined"} },
30 super.pieces(color, x, y)
31 );
32 }
33
34 get onlyClick() {
35 return this.movesCount <= 1;
36 }
37
38 // Initiate the game by choosing a square for the king:
39 doClick(coords) {
40 const color = this.turn;
41 if (
42 this.movesCount <= 1 &&
43 (
44 (color == 'w' && coords.x == this.size.x - 1) ||
45 (color == 'b' && coords.x == 0)
46 )
47 ) {
48 return new Move({
49 appear: [ new PiPo({x: coords.x, y: coords.y, c: color, p: 'k' }) ],
50 vanish: [ new PiPo({x: coords.x, y: coords.y, c: color, p: 'u' }) ]
51 });
52 }
53 return null;
54 }
55
56 genRandInitBaseFen() {
57 return {
58 fen: "uuuuuuuu/pppppppp/8/8/8/8/PPPPPPPP/UUUUUUUU",
59 o: {}
60 }
61 }
62
63 getPartFen(o) {
64 return Object.assign(
65 {
66 captureUndef: (o.init || !this.captureUndef)
67 ? "-"
68 : C.CoordsToSquare(this.captureUndef)
69 },
70 super.getPartFen(o)
71 );
72 }
73
74 getReserveFen(o) {
75 if (o.init)
76 return "22212221";
77 return (
78 ["w","b"].map(c => Object.values(this.reserve[c]).join("")).join("")
79 );
80 }
81
82 initReserves(reserveStr) {
83 super.initReserves(reserveStr, ['r', 'n', 'b', 'q']);
84 }
85
86 setOtherVariables(fenParsed) {
87 super.setOtherVariables(fenParsed);
88 this.captureUndef = fenParsed.captureUndef == '-'
89 ? null :
90 C.SquareToCoords(fenParsed.captureUndef);
91 this.definition = null;
92 }
93
94 canDrop([c, p], [i, j]) {
95 switch (this.subTurn) {
96 case 0:
97 return i == this.captureUndef.x && j == this.captureUndef.y;
98 case 1:
99 return this.getPiece(i, j) == 'u' && c == this.getColor(i, j);
100 }
101 return false; //never reached
102 }
103
104 getPotentialMovesFrom([x, y]) {
105 if (this.movesCount <= 1)
106 return [];
107 let moves = [];
108 switch (this.subTurn) {
109 case 0:
110 if (typeof x == "string")
111 moves = this.getDropMovesFrom([x, y]);
112 // Empty move: just start + end
113 moves.forEach(m => {m.vanish.pop(); m.appear.pop();});
114 break;
115 case 1:
116 // Both normal move (from defined piece) and definition allowed
117 if (typeof x == "string")
118 moves = this.getDropMovesFrom([x, y]);
119 else if (this.getPiece(x, y) != 'u')
120 moves = super.getPotentialMovesFrom([x, y]);
121 break;
122 case 2:
123 // We can only move the just-defined piece
124 if (x == this.definition.x && y == this.definition.y)
125 moves = super.getPotentialMovesFrom([x, y]);
126 break;
127 }
128 return moves;
129 }
130
131 filterValid(moves) {
132 if (this.movesCount <= 1 || this.subTurn == 0)
133 return moves;
134 let filterLater = [];
135 if (this.subTurn == 1) {
136 // Remove defining moves with un-movable def piece,
137 // and separate compatible definitions.
138 moves = moves.filter(m => {
139 if (m.vanish.length >= 2 || m.vanish[0].p != 'u')
140 return true;
141 this.playOnBoard(m);
142 const canMove = super.filterValid(
143 super.getPotentialMovesFrom([m.end.x, m.end.y])).length >= 1;
144 this.undoOnBoard(m);
145 if (canMove)
146 filterLater.push(m);
147 return false;
148 });
149 }
150 return super.filterValid(moves).concat(filterLater);
151 }
152
153 atLeastOneMove(color) {
154 if (this.subTurn != 1)
155 return true;
156 return super.atLeastOneMove(color);
157 }
158
159 underCheck(square_s, oppCol) {
160 if (super.underCheck(square_s, oppCol))
161 return true;
162 // Check potential specializations of undefined using reserve:
163 const inReserve = Object.keys(this.reserve[oppCol])
164 .filter(k => this.reserve[oppCol][k] >= 1);
165 const allAttacks = Array.prototype.concat.apply(
166 inReserve.map(p => this.pieces()[p].both[0]));
167 const [x, y] = square_s[0];
168 for (let i=0; i<this.size.x; i++) {
169 for (let j=0; j<this.size.y; j++) {
170 if (
171 this.board[i][j] != "" &&
172 this.getColor(i, j) == oppCol &&
173 this.getPiece(i, j) == 'u'
174 ) {
175 for (let stepDef of allAttacks) {
176 for (let s of stepDef.steps) {
177 if (!super.compatibleStep([i, j], [x, y], s, stepDef.range))
178 continue;
179 if (
180 super.findDestSquares(
181 [i, j],
182 {
183 captureTarget: [x, y],
184 captureSteps: [{steps: [s], range: stepDef.range}],
185 segments: false,
186 attackOnly: true,
187 one: false
188 }
189 )
190 ) {
191 return true;
192 }
193 }
194 }
195 }
196 }
197 }
198 return false;
199 }
200
201 postPlay(move) {
202 const color = this.turn;
203 if (this.movesCount <= 1 || move.reset || move.next) {
204 if (!move.next)
205 this.tryChangeTurn();
206 return;
207 }
208 if (this.subTurn == 0)
209 this.captureUndef = null; //already used
210 const captureUndef = (
211 move.vanish.length == 2 && //exclude subTurn == 0
212 move.vanish[1].c != color &&
213 move.vanish[1].p == 'u'
214 );
215 if (typeof move.start.x == "number" && !captureUndef)
216 // Normal move (including Teleport)
217 super.postPlay(move);
218 else if (typeof move.start.x == "string") {
219 super.updateReserve(
220 color, move.start.y, this.reserve[color][move.start.y] - 1);
221 if (move.vanish.length == 1 && move.vanish[0].p == 'u')
222 this.definition = move.end;
223 this.subTurn++;
224 }
225 else {
226 this.subTurn = 0;
227 this.captureUndef = move.end;
228 this.tryChangeTurn(null, captureUndef);
229 }
230 }
231
232 // NOTE: not "trying", the turn always change here (TODO?)
233 tryChangeTurn(move, captureUndef) {
234 this.definition = null;
235 this.subTurn = captureUndef ? 0 : 1;
236 this.turn = C.GetOppTurn(this.turn);
237 this.movesCount++;
238 }
239
240 computeNextMove(move) {
241 if (
242 !this.definition || this.playerColor != this.turn ||
243 this.board.some(row => row.some(cell =>
244 cell.charAt(0) == this.turn && cell.charAt(1) == 'u'))
245 ) {
246 return;
247 }
248 const variety = (c) => {
249 return (
250 [...new Set(
251 Array.prototype.concat.apply([],
252 this.board.map(row =>
253 row.filter(cell =>
254 cell.charAt(0) == c && !['p', 'k'].includes(cell.charAt(1))
255 ).map(cell => cell.charAt(1))
256 )
257 )
258 )].length >= 2
259 );
260 };
261 let next = {start: move.end, end: move.end, vanish: [], appear: []};
262 this.playOnBoard(move);
263 const twoOrMorePieces = {w: variety('w'), b: variety('b')};
264 const resetCols =
265 Object.keys(twoOrMorePieces).filter(k => twoOrMorePieces[k]);
266 if (resetCols.length >= 1) {
267 for (let i=0; i<this.size.x; i++) {
268 for (let j=0; j<this.size.y; j++) {
269 const colIJ = this.getColor(i, j);
270 const pieceIJ = this.getPiece(i, j);
271 if (
272 resetCols.includes(colIJ) &&
273 this.board[i][j] != "" &&
274 !['p', 'k', 'u'].includes(pieceIJ)
275 ) {
276 // NOTE: could also use a "flip" strategy similar to Benedict
277 next.vanish.push(new PiPo({c: colIJ, p: pieceIJ, x: i, y: j}));
278 next.appear.push(new PiPo({c: colIJ, p: 'u', x: i, y: j}));
279 this.reserve[colIJ][pieceIJ]++;
280 }
281 }
282 }
283 super.re_drawReserve(resetCols);
284 }
285 this.undoOnBoard(move);
286 if (next.vanish.length >= 1) {
287 next.reset = true;
288 move.next = next;
289 }
290 }
291
292 isLastMove() {
293 return true; //called only on normal moves (not Teleport)
294 }
295
296 getCurrentScore(move_s) {
297 return (this.movesCount <= 2 ? "*" : super.getCurrentScore(move_s));
298 }
299
300 };