Next: Clorange - TODO
[xogo.git] / variants / Clorange / class.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3
4 export default class ClorangeRules extends ChessRules {
5
6 get hasReserve() {
7 return true;
8 }
9
10 // TODO
11
12 static GenRandInitFen(options) {
13 // Capturing and non-capturing reserves:
14 return ChessRules.GenRandInitFen(options) + " 00000000000000000000";
15 }
16
17 getFen() {
18 return super.getFen() + " " + this.getReserveFen();
19 }
20
21 getFenForRepeat() {
22 return super.getFenForRepeat() + "_" + this.getReserveFen();
23 }
24
25 getReserveFen() {
26 return (
27 Object.keys(this.reserve).map(
28 c => Object.values(this.reserve[c]).join("")).join("")
29 );
30 }
31
32 getEpSquare(moveOrSquare) {
33 if (!moveOrSquare) return undefined;
34 if (typeof moveOrSquare === "string") {
35 const square = moveOrSquare;
36 if (square == "-") return undefined;
37 return V.SquareToCoords(square);
38 }
39 const move = moveOrSquare;
40 const s = move.start,
41 e = move.end;
42 if (
43 s.y == e.y &&
44 Math.abs(s.x - e.x) == 2 &&
45 move.vanish.length > 0 && ['p', 's'].includes(move.vanish[0].p)
46 ) {
47 return {
48 x: (s.x + e.x) / 2,
49 y: s.y
50 };
51 }
52 return undefined;
53 }
54
55 setOtherVariables(fen) {
56 super.setOtherVariables(fen);
57 // Also init reserves (used by the interface to show landable pieces)
58 const reserve =
59 V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10));
60 this.reserve = {
61 w: {
62 'p': reserve[0],
63 'r': reserve[1],
64 'n': reserve[2],
65 'b': reserve[3],
66 'q': reserve[4],
67 's': reserve[5],
68 'u': reserve[6],
69 'o': reserve[7],
70 'c': reserve[8],
71 't': reserve[9]
72 },
73 b: {
74 'p': reserve[10],
75 'r': reserve[11],
76 'n': reserve[12],
77 'b': reserve[13],
78 'q': reserve[14],
79 's': reserve[15],
80 'u': reserve[16],
81 'o': reserve[17],
82 'c': reserve[18],
83 't': reserve[19]
84 }
85 };
86 }
87
88 getColor(i, j) {
89 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
90 return this.board[i][j].charAt(0);
91 }
92
93 getPiece(i, j) {
94 if (i >= V.size.x) return V.RESERVE_PIECES[j];
95 return this.board[i][j].charAt(1);
96 }
97
98 getPpath(b) {
99 return (V.NON_VIOLENT.includes(b[1]) ? "Clorange/" : "") + b;
100 }
101
102 getReservePpath(index, color) {
103 const prefix =
104 (V.NON_VIOLENT.includes(V.RESERVE_PIECES[index]) ? "Clorange/" : "");
105 return prefix + color + V.RESERVE_PIECES[index];
106 }
107
108 static get NON_VIOLENT() {
109 return ['s', 'u', 'o', 'c', 't'];
110 }
111
112 static get PIECES() {
113 return ChessRules.PIECES.concat(V.NON_VIOLENT);
114 }
115
116 // Ordering on reserve pieces
117 static get RESERVE_PIECES() {
118 return V.PIECES.filter(p => p != 'k');
119 }
120
121 getReserveMoves([x, y]) {
122 const color = this.turn;
123 const p = V.RESERVE_PIECES[y];
124 if (this.reserve[color][p] == 0) return [];
125 let moves = [];
126 let rank1 = 0;
127 let rank2 = V.size.x - 1;
128 if (['p', 's'].includes(p)) {
129 if (color == 'w') rank1++;
130 else rank2--;
131 }
132 for (let i = rank1; i <= rank2; i++) {
133 for (let j = 0; j < V.size.y; j++) {
134 if (this.board[i][j] == V.EMPTY) {
135 let mv = new Move({
136 appear: [
137 new PiPo({
138 x: i,
139 y: j,
140 c: color,
141 p: p
142 })
143 ],
144 vanish: [],
145 start: { x: x, y: y }, //a bit artificial...
146 end: { x: i, y: j }
147 });
148 moves.push(mv);
149 }
150 }
151 }
152 return moves;
153 }
154
155 getPotentialMovesFrom([x, y]) {
156 if (x >= V.size.x)
157 // Reserves, outside of board: x == sizeX(+1)
158 return this.getReserveMoves([x, y]);
159 // Standard moves
160 switch (this.getPiece(x, y)) {
161 case 's': return this.getPotentialPawnMoves([x, y]);
162 case 'u': return super.getPotentialRookMoves([x, y]);
163 case 'o': return super.getPotentialKnightMoves([x, y]);
164 case 'c': return super.getPotentialBishopMoves([x, y]);
165 case 't': return super.getPotentialQueenMoves([x, y]);
166 default: return super.getPotentialMovesFrom([x, y]);
167 }
168 return []; //never reached
169 }
170
171 getPotentialPawnMoves(sq) {
172 let moves = super.getPotentialPawnMoves(sq);
173 if (moves.length > 0 && moves[0].vanish[0].p == 's') {
174 // Remove captures for non-violent pawns:
175 moves = moves.filter(m => m.vanish.length == 1);
176 moves.forEach(m => {
177 if (m.appear[0].p != 's') {
178 // Promotion pieces should be non-violent as well:
179 const pIdx = ChessRules.PIECES.findIndex(p => p == m.appear[0].p)
180 m.appear[0].p = V.NON_VIOLENT[pIdx];
181 }
182 });
183 }
184 return moves;
185 }
186
187 canTake([x1, y1], [x2, y2]) {
188 return (
189 this.getColor(x1, y1) !== this.getColor(x2, y2) &&
190 ChessRules.PIECES.includes(this.getPiece(x1, y1))
191 );
192 }
193
194 getAllValidMoves() {
195 let moves = super.getAllPotentialMoves();
196 const color = this.turn;
197 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
198 moves = moves.concat(
199 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
200 );
201 }
202 return this.filterValid(moves);
203 }
204
205 atLeastOneMove() {
206 if (!super.atLeastOneMove()) {
207 // Search one reserve move
208 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
209 let moves = this.filterValid(
210 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
211 );
212 if (moves.length > 0) return true;
213 }
214 return false;
215 }
216 return true;
217 }
218
219 prePlay(move) {
220 super.prePlay(move);
221 // Skip castle:
222 if (move.vanish.length == 2 && move.appear.length == 2) return;
223 const color = this.turn;
224 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--;
225 else if (move.vanish.length == 2) {
226 // Capture
227 const normal = ChessRules.PIECES.includes(move.vanish[1].p);
228 const pIdx =
229 normal
230 ? ChessRules.PIECES.findIndex(p => p == move.vanish[1].p)
231 : V.NON_VIOLENT.findIndex(p => p == move.vanish[1].p);
232 const rPiece = (normal ? V.NON_VIOLENT : ChessRules.PIECES)[pIdx];
233 this.reserve[move.vanish[1].c][rPiece]++;
234 }
235 }
236
237 };