1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class ConvertRules
extends ChessRules
{
6 setOtherVariables(fen
) {
7 super.setOtherVariables(fen
);
8 // Stack of "last move" only for intermediate chaining
9 this.lastMoveEnd
= [null];
12 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
13 const L
= this.lastMoveEnd
.length
;
14 const lm
= this.lastMoveEnd
[L
-1];
15 const piece
= (!!lm
? lm
.p : null);
17 if (this.board
[ex
][ey
] == V
.EMPTY
) {
18 if (!!piece
&& !tr
) tr
= { c: c
, p: piece
}
19 let mv
= super.getBasicMove([sx
, sy
], [ex
, ey
], tr
);
20 if (!!piece
) mv
.vanish
.pop();
23 // Capture: initial, or inside a chain
24 const initPiece
= (piece
|| this.getPiece(sx
, sy
));
25 const oppCol
= V
.GetOppCol(c
);
26 const oppPiece
= this.getPiece(ex
, ey
);
28 start: { x: sx
, y: sy
},
29 end: { x: ex
, y: ey
},
35 p: (!!tr
? tr
.p : initPiece
)
58 // TODO: This "converted" indication isn't needed in fact,
59 // because it can be deduced from the move itself.
60 mv
.end
.converted
= oppPiece
;
64 getPotentialMovesFrom([x
, y
], asA
) {
65 const L
= this.lastMoveEnd
.length
;
66 if (!!this.lastMoveEnd
[L
-1]) {
67 if (x
!= this.lastMoveEnd
[L
-1].x
|| y
!= this.lastMoveEnd
[L
-1].y
)
68 // A capture was played: wrong square
70 asA
= this.lastMoveEnd
[L
-1].p
;
72 switch (asA
|| this.getPiece(x
, y
)) {
73 case V
.PAWN: return super.getPotentialPawnMoves([x
, y
]);
74 case V
.ROOK: return super.getPotentialRookMoves([x
, y
]);
75 case V
.KNIGHT: return super.getPotentialKnightMoves([x
, y
]);
76 case V
.BISHOP: return super.getPotentialBishopMoves([x
, y
]);
77 case V
.QUEEN: return super.getPotentialQueenMoves([x
, y
]);
78 case V
.KING: return super.getPotentialKingMoves([x
, y
]);
83 getPossibleMovesFrom(sq
) {
84 const L
= this.lastMoveEnd
.length
;
86 if (!!this.lastMoveEnd
[L
-1]) {
88 sq
[0] != this.lastMoveEnd
[L
-1].x
||
89 sq
[1] != this.lastMoveEnd
[L
-1].y
93 asA
= this.lastMoveEnd
[L
-1].p
;
95 return this.filterValid(this.getPotentialMovesFrom(sq
, asA
));
98 isAttacked_aux([x
, y
], color
, explored
) {
99 if (explored
.some(sq
=> sq
[0] == x
&& sq
[1] == y
))
100 // Start of an infinite loop: exit
102 explored
.push([x
, y
]);
103 if (super.isAttacked([x
, y
], color
)) return true;
104 // Maybe indirect "chaining" attack:
105 const myColor
= this.turn
107 let toCheck
= []; //check all but king (no need)
109 const shiftToPawn
= (myColor
== 'w' ? -1 : 1);
110 for (let yShift
of [-1, 1]) {
111 const [i
, j
] = [x
+ shiftToPawn
, y
+ yShift
];
114 this.board
[i
][j
] != V
.EMPTY
&&
115 // NOTE: no need to check color (no enemy pawn can take directly)
116 this.getPiece(i
, j
) == V
.PAWN
118 toCheck
.push([i
, j
]);
122 V
.steps
[V
.KNIGHT
].forEach(s
=> {
123 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
126 this.board
[i
][j
] != V
.EMPTY
&&
127 this.getPiece(i
, j
) == V
.KNIGHT
129 toCheck
.push([i
, j
]);
133 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(s
=> {
134 let [i
, j
] = [x
+ s
[0], y
+ s
[1]];
135 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
139 if (!V
.OnBoard(i
, j
)) return;
140 const piece
= this.getPiece(i
, j
);
143 (piece
== V
.ROOK
&& (s
[0] == 0 || s
[1] == 0)) ||
144 (piece
== V
.BISHOP
&& (s
[0] != 0 && s
[1] != 0))
146 toCheck
.push([i
, j
]);
149 for (let ij
of toCheck
) {
150 if (this.isAttacked_aux(ij
, color
, explored
)) return true;
155 isAttacked([x
, y
], color
) {
157 return this.isAttacked_aux([x
, y
], color
, explored
);
161 // No "checks" (except to forbid castle)
170 move.flags
= JSON
.stringify(this.aggregateFlags());
171 this.epSquares
.push(this.getEpSquare(move));
172 V
.PlayOnBoard(this.board
, move);
173 if (!move.end
.converted
) {
174 // Not a capture: change turn
175 this.turn
= V
.GetOppCol(this.turn
);
177 this.lastMoveEnd
.push(null);
180 this.lastMoveEnd
.push(
181 Object
.assign({}, move.end
, { p: move.end
.converted
})
188 const c
= (!move.end
.converted
? V
.GetOppCol(this.turn
) : this.turn
);
189 const piece
= move.appear
[0].p
;
190 if (piece
== V
.KING
) {
191 this.kingPos
[c
][0] = move.appear
[0].x
;
192 this.kingPos
[c
][1] = move.appear
[0].y
;
194 super.updateCastleFlags(move, piece
, c
);
198 this.disaggregateFlags(JSON
.parse(move.flags
));
199 this.epSquares
.pop();
200 this.lastMoveEnd
.pop();
201 V
.UndoOnBoard(this.board
, move);
202 if (!move.end
.converted
) {
203 this.turn
= V
.GetOppCol(this.turn
);
206 super.postUndo(move);
210 const color
= this.turn
;
211 const kp
= this.kingPos
[color
];
212 if (this.getColor(kp
[0], kp
[1]) != color
)
213 return (color
== "w" ? "0-1" : "1-0");
214 if (!super.atLeastOneMove()) return "1/2";
219 let initMoves
= this.getAllValidMoves();
220 if (initMoves
.length
== 0) return null;
221 // Loop until valid move is found (no blocked pawn conversion...)
223 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
226 // Just play random moves (for now at least. TODO?)
227 while (moves
.length
> 0) {
228 mv
= moves
[randInt(moves
.length
)];
231 if (!!mv
.end
.converted
)
232 // A piece was just converted
233 moves
= this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]);
236 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
237 if (!mv
.end
.released
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
239 return null; //never reached
243 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
244 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
246 const L
= this.lastMoveEnd
.length
;
247 const lm
= this.lastMoveEnd
[L
-1];
248 const piece
= (!lm
? move.appear
[0].p : lm
.p
);
249 // Basic move notation:
250 let notation
= piece
.toUpperCase();
252 this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
||
253 (piece
== V
.PAWN
&& move.start
.y
!= move.end
.y
)
257 const finalSquare
= V
.CoordsToSquare(move.end
);
258 notation
+= finalSquare
;
260 // Add potential promotion indications:
261 const firstLastRank
= (c
== 'w' ? [7, 0] : [0, 7]);
262 if (move.end
.x
== firstLastRank
[1] && piece
== V
.PAWN
)
263 notation
+= "=" + move.appear
[0].p
.toUpperCase();