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(randomness
) {
18 return "rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w 0 ahah";
19 const baseFen
= ChessRules
.GenRandInitFen(randomness
);
21 baseFen
.substr(0, 8) +
22 "/8/pppppppp/8/8/PPPPPPPP/8/" +
23 baseFen
.substr(35, 17)
27 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
28 const L
= this.lastMoveEnd
.length
;
29 const lm
= this.lastMoveEnd
[L
-1];
30 const piece
= (!!lm
? lm
.p : null);
32 if (this.board
[ex
][ey
] == V
.EMPTY
) {
33 if (!!piece
&& !tr
) tr
= { c: c
, p: piece
}
34 let mv
= super.getBasicMove([sx
, sy
], [ex
, ey
], tr
);
35 if (!!piece
) mv
.vanish
.pop();
38 // Capture: initial, or inside a chain
39 const initPiece
= (piece
|| this.getPiece(sx
, sy
));
40 const oppCol
= V
.GetOppCol(c
);
41 const oppPiece
= this.getPiece(ex
, ey
);
43 start: { x: sx
, y: sy
},
44 end: { x: ex
, y: ey
},
50 p: (!!tr
? tr
.p : initPiece
)
73 // TODO: This "converted" indication isn't needed in fact,
74 // because it can be deduced from the move itself.
75 mv
.end
.converted
= oppPiece
;
79 getPotentialMovesFrom([x
, y
], asA
) {
80 const L
= this.lastMoveEnd
.length
;
81 if (!!this.lastMoveEnd
[L
-1]) {
82 if (x
!= this.lastMoveEnd
[L
-1].x
|| y
!= this.lastMoveEnd
[L
-1].y
)
83 // A capture was played: wrong square
85 asA
= this.lastMoveEnd
[L
-1].p
;
87 switch (asA
|| this.getPiece(x
, y
)) {
88 case V
.PAWN: return super.getPotentialPawnMoves([x
, y
]);
89 case V
.ROOK: return super.getPotentialRookMoves([x
, y
]);
90 case V
.KNIGHT: return super.getPotentialKnightMoves([x
, y
]);
91 case V
.BISHOP: return super.getPotentialBishopMoves([x
, y
]);
92 case V
.QUEEN: return super.getPotentialQueenMoves([x
, y
]);
93 case V
.KING: return super.getPotentialKingMoves([x
, y
]);
98 getPossibleMovesFrom(sq
) {
99 const L
= this.lastMoveEnd
.length
;
101 if (!!this.lastMoveEnd
[L
-1]) {
103 sq
[0] != this.lastMoveEnd
[L
-1].x
||
104 sq
[1] != this.lastMoveEnd
[L
-1].y
108 asA
= this.lastMoveEnd
[L
-1].p
;
110 return this.filterValid(this.getPotentialMovesFrom(sq
, asA
));
113 isAttacked_aux([x
, y
], color
, explored
) {
114 if (explored
.some(sq
=> sq
[0] == x
&& sq
[1] == y
))
115 // Start of an infinite loop: exit
117 explored
.push([x
, y
]);
118 if (super.isAttacked([x
, y
], color
)) return true;
119 // Maybe indirect "chaining" attack:
120 const myColor
= this.turn
122 let toCheck
= []; //check all but king (no need)
124 const shiftToPawn
= (myColor
== 'w' ? -1 : 1);
125 for (let yShift
of [-1, 1]) {
126 const [i
, j
] = [x
+ shiftToPawn
, y
+ yShift
];
129 this.board
[i
][j
] != V
.EMPTY
&&
130 // NOTE: no need to check color (no enemy pawn can take directly)
131 this.getPiece(i
, j
) == V
.PAWN
133 toCheck
.push([i
, j
]);
137 V
.steps
[V
.KNIGHT
].forEach(s
=> {
138 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
141 this.board
[i
][j
] != V
.EMPTY
&&
142 this.getPiece(i
, j
) == V
.KNIGHT
144 toCheck
.push([i
, j
]);
148 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(s
=> {
149 let [i
, j
] = [x
+ s
[0], y
+ s
[1]];
150 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
154 if (!V
.OnBoard(i
, j
)) return;
155 const piece
= this.getPiece(i
, j
);
158 (piece
== V
.ROOK
&& (s
[0] == 0 || s
[1] == 0)) ||
159 (piece
== V
.BISHOP
&& (s
[0] != 0 && s
[1] != 0))
161 toCheck
.push([i
, j
]);
164 for (let ij
of toCheck
) {
165 if (this.isAttacked_aux(ij
, color
, explored
)) return true;
170 isAttacked([x
, y
], color
) {
172 return this.isAttacked_aux([x
, y
], color
, explored
);
176 // No "checks" (except to forbid castle)
186 // Extra conditions to avoid tracking converted kings:
188 move.appear
[0].p
== V
.KING
&&
189 move.vanish
.length
>= 1 &&
190 move.vanish
[0].p
== V
.KING
192 this.kingPos
[c
][0] = move.appear
[0].x
;
193 this.kingPos
[c
][1] = move.appear
[0].y
;
200 move.flags
= JSON
.stringify(this.aggregateFlags());
201 V
.PlayOnBoard(this.board
, move);
202 if (!move.end
.converted
) {
203 // Not a capture: change turn
204 this.turn
= V
.GetOppCol(this.turn
);
206 this.lastMoveEnd
.push(null);
209 this.lastMoveEnd
.push(
210 Object
.assign({}, move.end
, { p: move.end
.converted
})
213 super.updateCastleFlags(move, move.appear
[0].p
, c
);
217 this.disaggregateFlags(JSON
.parse(move.flags
));
218 this.lastMoveEnd
.pop();
219 V
.UndoOnBoard(this.board
, move);
220 if (!move.end
.converted
) {
221 this.turn
= V
.GetOppCol(this.turn
);
228 const c
= this.getColor(move.start
.x
, move.start
.y
);
230 move.appear
[0].p
== V
.KING
&&
231 move.vanish
.length
>= 1 &&
232 move.vanish
[0].p
== V
.KING
234 this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
239 const color
= this.turn
;
240 const kp
= this.kingPos
[color
];
241 if (this.getColor(kp
[0], kp
[1]) != color
)
242 return (color
== "w" ? "0-1" : "1-0");
243 if (!super.atLeastOneMove()) return "1/2";
248 let initMoves
= this.getAllValidMoves();
249 if (initMoves
.length
== 0) return null;
250 // Loop until valid move is found (no blocked pawn conversion...)
252 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
255 // Just play random moves (for now at least. TODO?)
256 while (moves
.length
> 0) {
257 mv
= moves
[randInt(moves
.length
)];
260 if (!!mv
.end
.converted
)
261 // A piece was just converted
262 moves
= this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]);
265 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
266 if (!mv
.end
.converted
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
268 return null; //never reached
272 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
273 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
275 const L
= this.lastMoveEnd
.length
;
276 const lm
= this.lastMoveEnd
[L
-1];
277 const piece
= (!lm
? move.appear
[0].p : lm
.p
);
278 // Basic move notation:
279 let notation
= piece
.toUpperCase();
281 this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
||
282 (piece
== V
.PAWN
&& move.start
.y
!= move.end
.y
)
286 const finalSquare
= V
.CoordsToSquare(move.end
);
287 notation
+= finalSquare
;
289 // Add potential promotion indications:
290 const firstLastRank
= (c
== 'w' ? [7, 0] : [0, 7]);
291 if (move.end
.x
== firstLastRank
[1] && piece
== V
.PAWN
)
292 notation
+= "=" + move.appear
[0].p
.toUpperCase();