1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class ConvertRules
extends ChessRules
{
6 static get HasEnpassant() {
10 setOtherVariables(fen
) {
11 super.setOtherVariables(fen
);
12 // Stack of "last move" only for intermediate chaining
13 this.lastMoveEnd
= [null];
16 static GenRandInitFen(options
) {
17 const baseFen
= ChessRules
.GenRandInitFen(options
);
19 baseFen
.substr(0, 8) +
20 "/8/pppppppp/8/8/PPPPPPPP/8/" +
21 baseFen
.substr(35, 17)
25 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
26 const L
= this.lastMoveEnd
.length
;
27 const lm
= this.lastMoveEnd
[L
-1];
28 const piece
= (!!lm
? lm
.p : null);
30 if (this.board
[ex
][ey
] == V
.EMPTY
) {
31 if (!!piece
&& !tr
) tr
= { c: c
, p: piece
}
32 let mv
= super.getBasicMove([sx
, sy
], [ex
, ey
], tr
);
33 if (!!piece
) mv
.vanish
.pop();
36 // Capture: initial, or inside a chain
37 const initPiece
= (piece
|| this.getPiece(sx
, sy
));
38 const oppCol
= V
.GetOppCol(c
);
39 const oppPiece
= this.getPiece(ex
, ey
);
41 start: { x: sx
, y: sy
},
42 end: { x: ex
, y: ey
},
48 p: (!!tr
? tr
.p : initPiece
)
71 // TODO: This "converted" indication isn't needed in fact,
72 // because it can be deduced from the move itself.
73 mv
.end
.converted
= oppPiece
;
77 getPotentialMovesFrom([x
, y
], asA
) {
78 const L
= this.lastMoveEnd
.length
;
79 if (!!this.lastMoveEnd
[L
-1]) {
80 if (x
!= this.lastMoveEnd
[L
-1].x
|| y
!= this.lastMoveEnd
[L
-1].y
)
81 // A capture was played: wrong square
83 asA
= this.lastMoveEnd
[L
-1].p
;
85 switch (asA
|| this.getPiece(x
, y
)) {
86 case V
.PAWN: return super.getPotentialPawnMoves([x
, y
]);
87 case V
.ROOK: return super.getPotentialRookMoves([x
, y
]);
88 case V
.KNIGHT: return super.getPotentialKnightMoves([x
, y
]);
89 case V
.BISHOP: return super.getPotentialBishopMoves([x
, y
]);
90 case V
.QUEEN: return super.getPotentialQueenMoves([x
, y
]);
91 case V
.KING: return super.getPotentialKingMoves([x
, y
]);
96 getPossibleMovesFrom(sq
) {
97 const L
= this.lastMoveEnd
.length
;
99 if (!!this.lastMoveEnd
[L
-1]) {
101 sq
[0] != this.lastMoveEnd
[L
-1].x
||
102 sq
[1] != this.lastMoveEnd
[L
-1].y
106 asA
= this.lastMoveEnd
[L
-1].p
;
108 return this.filterValid(this.getPotentialMovesFrom(sq
, asA
));
111 isAttacked_aux([x
, y
], color
, explored
) {
112 if (explored
.some(sq
=> sq
[0] == x
&& sq
[1] == y
))
113 // Start of an infinite loop: exit
115 explored
.push([x
, y
]);
116 if (super.isAttacked([x
, y
], color
)) return true;
117 // Maybe indirect "chaining" attack:
118 const myColor
= this.turn
120 let toCheck
= []; //check all but king (no need)
122 const shiftToPawn
= (myColor
== 'w' ? -1 : 1);
123 for (let yShift
of [-1, 1]) {
124 const [i
, j
] = [x
+ shiftToPawn
, y
+ yShift
];
127 this.board
[i
][j
] != V
.EMPTY
&&
128 // NOTE: no need to check color (no enemy pawn can take directly)
129 this.getPiece(i
, j
) == V
.PAWN
131 toCheck
.push([i
, j
]);
135 V
.steps
[V
.KNIGHT
].forEach(s
=> {
136 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
139 this.board
[i
][j
] != V
.EMPTY
&&
140 this.getPiece(i
, j
) == V
.KNIGHT
142 toCheck
.push([i
, j
]);
146 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(s
=> {
147 let [i
, j
] = [x
+ s
[0], y
+ s
[1]];
148 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
152 if (!V
.OnBoard(i
, j
)) return;
153 const piece
= this.getPiece(i
, j
);
156 (piece
== V
.ROOK
&& (s
[0] == 0 || s
[1] == 0)) ||
157 (piece
== V
.BISHOP
&& (s
[0] != 0 && s
[1] != 0))
159 toCheck
.push([i
, j
]);
162 for (let ij
of toCheck
) {
163 if (this.isAttacked_aux(ij
, color
, explored
)) return true;
168 isAttacked([x
, y
], color
) {
170 return this.isAttacked_aux([x
, y
], color
, explored
);
174 // No "checks" (except to forbid castle)
184 // Extra conditions to avoid tracking converted kings:
186 move.appear
[0].p
== V
.KING
&&
187 move.vanish
.length
>= 1 &&
188 move.vanish
[0].p
== V
.KING
190 this.kingPos
[c
][0] = move.appear
[0].x
;
191 this.kingPos
[c
][1] = move.appear
[0].y
;
198 move.flags
= JSON
.stringify(this.aggregateFlags());
199 V
.PlayOnBoard(this.board
, move);
200 if (!move.end
.converted
) {
201 // Not a capture: change turn
202 this.turn
= V
.GetOppCol(this.turn
);
204 this.lastMoveEnd
.push(null);
207 this.lastMoveEnd
.push(
208 Object
.assign({}, move.end
, { p: move.end
.converted
})
211 super.updateCastleFlags(move, move.appear
[0].p
, c
);
215 this.disaggregateFlags(JSON
.parse(move.flags
));
216 this.lastMoveEnd
.pop();
217 V
.UndoOnBoard(this.board
, move);
218 if (!move.end
.converted
) {
219 this.turn
= V
.GetOppCol(this.turn
);
226 const c
= this.getColor(move.start
.x
, move.start
.y
);
228 move.appear
[0].p
== V
.KING
&&
229 move.vanish
.length
>= 1 &&
230 move.vanish
[0].p
== V
.KING
232 this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
237 const color
= this.turn
;
238 const kp
= this.kingPos
[color
];
239 if (this.getColor(kp
[0], kp
[1]) != color
)
240 return (color
== "w" ? "0-1" : "1-0");
241 if (!super.atLeastOneMove()) return "1/2";
246 let initMoves
= this.getAllValidMoves();
247 if (initMoves
.length
== 0) return null;
248 // Loop until valid move is found (no blocked pawn conversion...)
250 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
253 // Just play random moves (for now at least. TODO?)
254 while (moves
.length
> 0) {
255 mv
= moves
[randInt(moves
.length
)];
258 if (!!mv
.end
.converted
)
259 // A piece was just converted
260 moves
= this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]);
263 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
264 if (!mv
.end
.converted
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
266 return null; //never reached
270 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
271 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
273 const L
= this.lastMoveEnd
.length
;
274 const lm
= this.lastMoveEnd
[L
-1];
275 const piece
= (!lm
? move.appear
[0].p : lm
.p
);
276 // Basic move notation:
277 let notation
= piece
.toUpperCase();
279 this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
||
280 (piece
== V
.PAWN
&& move.start
.y
!= move.end
.y
)
284 const finalSquare
= V
.CoordsToSquare(move.end
);
285 notation
+= finalSquare
;
287 // Add potential promotion indications:
288 const firstLastRank
= (c
== 'w' ? [7, 0] : [0, 7]);
289 if (move.end
.x
== firstLastRank
[1] && piece
== V
.PAWN
)
290 notation
+= "=" + move.appear
[0].p
.toUpperCase();