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