Start working on Convert variant
[xogo.git] / variants / Convert / 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 ConvertRules extends ChessRules {
6
7 // TODO
8 static get Options() {
9 return {
10 select: C.Options.select,
11 input: C.Options.input,
12 styles: [
13 "atomic", "cannibal", "capture", "cylinder",
14 "dark", "madrasi", "rifle", "teleport"
15 ]
16 };
17 }
18
19 get hasEnpassant() {
20 return false;
21 }
22
23 setOtherVariables(fenParsed, pieceArray) {
24 super.setOtherVariables(fenParsed, pieceArray);
25 // Stack of "last move" only for intermediate chaining
26 this.lastMoveEnd = [null];
27 }
28
29 genRandInitBaseFen() {
30 const baseFen = super.genRandInitBaseFen();
31 return {
32 fen: baseFen.fen.replace("pppppppp/8", "8/pppppppp")
33 .replace("8/PPPPPPPP", "PPPPPPPP/8"),
34 o: baseFen.o
35 };
36 }
37
38 getBasicMove([sx, sy], [ex, ey], tr) {
39 const L = this.lastMoveEnd.length;
40 const lm = this.lastMoveEnd[L-1];
41 const piece = (!!lm ? lm.p : null);
42 const c = this.turn;
43 if (this.board[ex][ey] == "") {
44 if (piece && !tr)
45 tr = {c: c, p: piece};
46 let mv = super.getBasicMove([sx, sy], [ex, ey], tr);
47 if (piece)
48 mv.vanish.pop();
49 return mv;
50 }
51 // Capture: initial, or inside a chain
52 const initPiece = (piece || this.getPiece(sx, sy));
53 const oppCol = C.GetOppTurn(c);
54 const oppPiece = this.getPiece(ex, ey);
55 let mv = new Move({
56 start: {x: sx, y: sy},
57 end: {x: ex, y: ey},
58 appear: [
59 new PiPo({
60 x: ex,
61 y: ey,
62 c: c,
63 p: (!!tr ? tr.p : initPiece)
64 })
65 ],
66 vanish: [
67 new PiPo({
68 x: ex,
69 y: ey,
70 c: oppCol,
71 p: oppPiece
72 })
73 ]
74 });
75 if (!piece) {
76 // Initial capture
77 mv.vanish.unshift(
78 new PiPo({
79 x: sx,
80 y: sy,
81 c: c,
82 p: initPiece
83 })
84 );
85 }
86 return mv;
87 }
88
89 // TODO from here
90
91 getPotentialMovesFrom([x, y], asA) {
92 const L = this.lastMoveEnd.length;
93 if (!!this.lastMoveEnd[L-1]) {
94 if (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y)
95 // A capture was played: wrong square
96 return [];
97 asA = this.lastMoveEnd[L-1].p;
98 }
99 switch (asA || this.getPiece(x, y)) {
100 case V.PAWN: return super.getPotentialPawnMoves([x, y]);
101 case V.ROOK: return super.getPotentialRookMoves([x, y]);
102 case V.KNIGHT: return super.getPotentialKnightMoves([x, y]);
103 case V.BISHOP: return super.getPotentialBishopMoves([x, y]);
104 case V.QUEEN: return super.getPotentialQueenMoves([x, y]);
105 case V.KING: return super.getPotentialKingMoves([x, y]);
106 }
107 return [];
108 }
109
110 getPossibleMovesFrom(sq) {
111 const L = this.lastMoveEnd.length;
112 let asA = undefined;
113 if (!!this.lastMoveEnd[L-1]) {
114 if (
115 sq[0] != this.lastMoveEnd[L-1].x ||
116 sq[1] != this.lastMoveEnd[L-1].y
117 ) {
118 return [];
119 }
120 asA = this.lastMoveEnd[L-1].p;
121 }
122 return this.filterValid(this.getPotentialMovesFrom(sq, asA));
123 }
124
125 isAttacked_aux([x, y], color, explored) {
126 if (explored.some(sq => sq[0] == x && sq[1] == y))
127 // Start of an infinite loop: exit
128 return false;
129 explored.push([x, y]);
130 if (super.isAttacked([x, y], color)) return true;
131 // Maybe indirect "chaining" attack:
132 const myColor = this.turn
133 let res = false;
134 let toCheck = []; //check all but king (no need)
135 // Pawns:
136 const shiftToPawn = (myColor == 'w' ? -1 : 1);
137 for (let yShift of [-1, 1]) {
138 const [i, j] = [x + shiftToPawn, y + yShift];
139 if (
140 V.OnBoard(i, j) &&
141 this.board[i][j] != V.EMPTY &&
142 // NOTE: no need to check color (no enemy pawn can take directly)
143 this.getPiece(i, j) == V.PAWN
144 ) {
145 toCheck.push([i, j]);
146 }
147 }
148 // Knights:
149 V.steps[V.KNIGHT].forEach(s => {
150 const [i, j] = [x + s[0], y + s[1]];
151 if (
152 V.OnBoard(i, j) &&
153 this.board[i][j] != V.EMPTY &&
154 this.getPiece(i, j) == V.KNIGHT
155 ) {
156 toCheck.push([i, j]);
157 }
158 });
159 // Sliders:
160 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
161 let [i, j] = [x + s[0], y + s[1]];
162 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
163 i += s[0];
164 j += s[1];
165 }
166 if (!V.OnBoard(i, j)) return;
167 const piece = this.getPiece(i, j);
168 if (
169 piece == V.QUEEN ||
170 (piece == V.ROOK && (s[0] == 0 || s[1] == 0)) ||
171 (piece == V.BISHOP && (s[0] != 0 && s[1] != 0))
172 ) {
173 toCheck.push([i, j]);
174 }
175 });
176 for (let ij of toCheck) {
177 if (this.isAttacked_aux(ij, color, explored)) return true;
178 }
179 return false;
180 }
181
182 isAttacked([x, y], color) {
183 let explored = [];
184 return this.isAttacked_aux([x, y], color, explored);
185 }
186
187 filterValid(moves) {
188 // No "checks" (except to forbid castle)
189 return moves;
190 }
191
192 prePlay(move) {
193 const c = this.turn;
194 // Extra conditions to avoid tracking converted kings:
195 if (
196 move.appear[0].p == V.KING &&
197 move.vanish.length >= 1 &&
198 move.vanish[0].p == V.KING
199 ) {
200 this.kingPos[c][0] = move.appear[0].x;
201 this.kingPos[c][1] = move.appear[0].y;
202 }
203 }
204
205 play(move) {
206 this.prePlay(move);
207 const c = this.turn;
208 move.flags = JSON.stringify(this.aggregateFlags());
209 V.PlayOnBoard(this.board, move);
210 if (!move.end.converted) {
211 // Not a capture: change turn
212 this.turn = V.GetOppCol(this.turn);
213 this.movesCount++;
214 this.lastMoveEnd.push(null);
215 }
216 else {
217 this.lastMoveEnd.push(
218 Object.assign({}, move.end, { p: move.end.converted })
219 );
220 }
221 super.updateCastleFlags(move, move.appear[0].p, c);
222 }
223
224 };