566feb5ed1235d0231b0688141f1a077f9da2773
1 import ChessRules
from "/base_rules.js";
2 import PiPo
from "/utils/PiPo.js";
3 import Move
from "/utils/Move.js";
5 export default class ConvertRules
extends ChessRules
{
7 // TODO: options ? (balance progressive ok it seems?)
10 select: C
.Options
.select
,
11 input: C
.Options
.input
,
13 "atomic", "cannibal", "capture", "cylinder",
14 "dark", "madrasi", "rifle", "teleport"
23 setOtherVariables(fenParsed
, pieceArray
) {
24 super.setOtherVariables(fenParsed
, pieceArray
);
25 // Stack of "last move" only for intermediate chaining
26 this.lastMoveEnd
= [];
29 genRandInitBaseFen() {
30 const baseFen
= super.genRandInitBaseFen();
32 fen: baseFen
.fen
.replace("pppppppp/8", "8/pppppppp")
33 .replace("8/PPPPPPPP", "PPPPPPPP/8"),
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);
43 if (this.board
[ex
][ey
] == "") {
45 tr
= {c: c
, p: piece
};
46 let mv
= super.getBasicMove([sx
, sy
], [ex
, ey
], tr
);
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
);
56 start: {x: sx
, y: sy
},
63 p: (!!tr
? tr
.p : initPiece
)
90 const L
= this.lastMoveEnd
.length
;
91 if (L
>= 1 && this.lastMoveEnd
[L
-1].x
== x
&& this.lastMoveEnd
[L
-1].y
== y
)
92 return this.lastMoveEnd
[L
-1].p
;
93 return super.getPiece(x
, y
);
96 getPotentialMovesFrom([x
, y
], color
) {
97 const L
= this.lastMoveEnd
.length
;
100 (x
!= this.lastMoveEnd
[L
-1].x
|| y
!= this.lastMoveEnd
[L
-1].y
)
102 // A capture was played: wrong square
105 return super.getPotentialMovesFrom([x
, y
], color
);
108 underAttack_aux([x
, y
], color
, explored
) {
109 if (explored
.some(sq
=> sq
[0] == x
&& sq
[1] == y
))
110 // Start of an infinite loop: exit
112 explored
.push([x
, y
]);
113 if (super.underAttack([x
, y
], [color
]))
115 // Maybe indirect "chaining" attack:
116 const myColor
= this.turn
;
118 let toCheck
= []; //check all but king (no need)
120 const shiftToPawn
= (myColor
== 'w' ? -1 : 1);
121 for (let yShift
of [-1, 1]) {
122 const [i
, j
] = [x
+ shiftToPawn
, y
+ yShift
];
124 this.onBoard(i
, j
) &&
125 this.board
[i
][j
] != "" &&
126 // NOTE: no need to check color (no enemy pawn can take directly)
127 this.getPiece(i
, j
) == 'p'
129 toCheck
.push([i
, j
]);
133 this.pieces()['n'].both
[0].steps
.forEach(s
=> {
134 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
136 this.onBoard(i
, j
) &&
137 this.board
[i
][j
] != "" &&
138 this.getPiece(i
, j
) == 'n'
140 toCheck
.push([i
, j
]);
144 this.pieces()['q'].both
[0].steps
.forEach(s
=> {
145 let [i
, j
] = [x
+ s
[0], y
+ s
[1]];
146 while (this.onBoard(i
, j
) && this.board
[i
][j
] == "") {
150 if (!this.onBoard(i
, j
))
152 const piece
= this.getPiece(i
, j
);
155 (piece
== 'r' && (s
[0] == 0 || s
[1] == 0)) ||
156 (piece
== 'b' && (s
[0] != 0 && s
[1] != 0))
158 toCheck
.push([i
, j
]);
161 for (let ij
of toCheck
) {
162 if (this.underAttack_aux(ij
, color
, explored
))
168 underAttack([x
, y
], color
) {
170 return this.underAttack_aux([x
, y
], color
, explored
);
174 // No "checks" (except to forbid castle)
180 super.isLastMove(move) ||
181 move.vanish
.length
<= 1 ||
182 move.vanish
[1].c
!= move.vanish
[0].c
||
183 move.appear
.length
== 2 //castle!
188 super.postPlay(move);
189 if (!this.isLastMove(move)) {
190 this.lastMoveEnd
.push({
193 p: move.vanish
[1].p
//TODO: check this
200 // TODO: wrong rules! mismatch Convert (taking opponent pieces) and chaining (tend to be this) taking own units (with normal initial position).
203 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
204 import { randInt
} from "@/utils/alea";
206 export class ConvertRules
extends ChessRules
{
208 static get HasEnpassant() {
212 setOtherVariables(fen
) {
213 super.setOtherVariables(fen
);
214 // Stack of "last move" only for intermediate chaining
215 this.lastMoveEnd
= [null];
218 static GenRandInitFen(options
) {
219 const baseFen
= ChessRules
.GenRandInitFen(options
);
221 baseFen
.substr(0, 8) +
222 "/8/pppppppp/8/8/PPPPPPPP/8/" +
223 baseFen
.substr(35, 17)
227 getBasicMove([sx
, sy
], [ex
, ey
], tr
) {
228 const L
= this.lastMoveEnd
.length
;
229 const lm
= this.lastMoveEnd
[L
-1];
230 const piece
= (!!lm
? lm
.p : null);
232 if (this.board
[ex
][ey
] == V
.EMPTY
) {
233 if (!!piece
&& !tr
) tr
= { c: c
, p: piece
}
234 let mv
= super.getBasicMove([sx
, sy
], [ex
, ey
], tr
);
235 if (!!piece
) mv
.vanish
.pop();
238 // Capture: initial, or inside a chain
239 const initPiece
= (piece
|| this.getPiece(sx
, sy
));
240 const oppCol
= V
.GetOppCol(c
);
241 const oppPiece
= this.getPiece(ex
, ey
);
243 start: { x: sx
, y: sy
},
244 end: { x: ex
, y: ey
},
250 p: (!!tr
? tr
.p : initPiece
)
273 // TODO: This "converted" indication isn't needed in fact,
274 // because it can be deduced from the move itself.
275 mv
.end
.converted
= oppPiece
;
279 getPotentialMovesFrom([x
, y
], asA
) {
280 const L
= this.lastMoveEnd
.length
;
281 if (!!this.lastMoveEnd
[L
-1]) {
282 if (x
!= this.lastMoveEnd
[L
-1].x
|| y
!= this.lastMoveEnd
[L
-1].y
)
283 // A capture was played: wrong square
285 asA
= this.lastMoveEnd
[L
-1].p
;
287 switch (asA
|| this.getPiece(x
, y
)) {
288 case V
.PAWN: return super.getPotentialPawnMoves([x
, y
]);
289 case V
.ROOK: return super.getPotentialRookMoves([x
, y
]);
290 case V
.KNIGHT: return super.getPotentialKnightMoves([x
, y
]);
291 case V
.BISHOP: return super.getPotentialBishopMoves([x
, y
]);
292 case V
.QUEEN: return super.getPotentialQueenMoves([x
, y
]);
293 case V
.KING: return super.getPotentialKingMoves([x
, y
]);
298 getPossibleMovesFrom(sq
) {
299 const L
= this.lastMoveEnd
.length
;
301 if (!!this.lastMoveEnd
[L
-1]) {
303 sq
[0] != this.lastMoveEnd
[L
-1].x
||
304 sq
[1] != this.lastMoveEnd
[L
-1].y
308 asA
= this.lastMoveEnd
[L
-1].p
;
310 return this.filterValid(this.getPotentialMovesFrom(sq
, asA
));
313 isAttacked_aux([x
, y
], color
, explored
) {
314 if (explored
.some(sq
=> sq
[0] == x
&& sq
[1] == y
))
315 // Start of an infinite loop: exit
317 explored
.push([x
, y
]);
318 if (super.isAttacked([x
, y
], color
)) return true;
319 // Maybe indirect "chaining" attack:
320 const myColor
= this.turn
322 let toCheck
= []; //check all but king (no need)
324 const shiftToPawn
= (myColor
== 'w' ? -1 : 1);
325 for (let yShift
of [-1, 1]) {
326 const [i
, j
] = [x
+ shiftToPawn
, y
+ yShift
];
329 this.board
[i
][j
] != V
.EMPTY
&&
330 // NOTE: no need to check color (no enemy pawn can take directly)
331 this.getPiece(i
, j
) == V
.PAWN
333 toCheck
.push([i
, j
]);
337 V
.steps
[V
.KNIGHT
].forEach(s
=> {
338 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
341 this.board
[i
][j
] != V
.EMPTY
&&
342 this.getPiece(i
, j
) == V
.KNIGHT
344 toCheck
.push([i
, j
]);
348 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(s
=> {
349 let [i
, j
] = [x
+ s
[0], y
+ s
[1]];
350 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
354 if (!V
.OnBoard(i
, j
)) return;
355 const piece
= this.getPiece(i
, j
);
358 (piece
== V
.ROOK
&& (s
[0] == 0 || s
[1] == 0)) ||
359 (piece
== V
.BISHOP
&& (s
[0] != 0 && s
[1] != 0))
361 toCheck
.push([i
, j
]);
364 for (let ij
of toCheck
) {
365 if (this.isAttacked_aux(ij
, color
, explored
)) return true;
370 isAttacked([x
, y
], color
) {
372 return this.isAttacked_aux([x
, y
], color
, explored
);
376 // No "checks" (except to forbid castle)
386 // Extra conditions to avoid tracking converted kings:
388 move.appear
[0].p
== V
.KING
&&
389 move.vanish
.length
>= 1 &&
390 move.vanish
[0].p
== V
.KING
392 this.kingPos
[c
][0] = move.appear
[0].x
;
393 this.kingPos
[c
][1] = move.appear
[0].y
;
400 move.flags
= JSON
.stringify(this.aggregateFlags());
401 V
.PlayOnBoard(this.board
, move);
402 if (!move.end
.converted
) {
403 // Not a capture: change turn
404 this.turn
= V
.GetOppCol(this.turn
);
406 this.lastMoveEnd
.push(null);
409 this.lastMoveEnd
.push(
410 Object
.assign({}, move.end
, { p: move.end
.converted
})
413 super.updateCastleFlags(move, move.appear
[0].p
, c
);
417 this.disaggregateFlags(JSON
.parse(move.flags
));
418 this.lastMoveEnd
.pop();
419 V
.UndoOnBoard(this.board
, move);
420 if (!move.end
.converted
) {
421 this.turn
= V
.GetOppCol(this.turn
);
428 const c
= this.getColor(move.start
.x
, move.start
.y
);
430 move.appear
[0].p
== V
.KING
&&
431 move.vanish
.length
>= 1 &&
432 move.vanish
[0].p
== V
.KING
434 this.kingPos
[c
] = [move.start
.x
, move.start
.y
];
439 const color
= this.turn
;
440 const kp
= this.kingPos
[color
];
441 if (this.getColor(kp
[0], kp
[1]) != color
)
442 return (color
== "w" ? "0-1" : "1-0");
443 if (!super.atLeastOneMove()) return "1/2";
448 let initMoves
= this.getAllValidMoves();
449 if (initMoves
.length
== 0) return null;
450 // Loop until valid move is found (no blocked pawn conversion...)
452 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
455 // Just play random moves (for now at least. TODO?)
456 while (moves
.length
> 0) {
457 mv
= moves
[randInt(moves
.length
)];
460 if (!!mv
.end
.converted
)
461 // A piece was just converted
462 moves
= this.getPotentialMovesFrom([mv
.end
.x
, mv
.end
.y
]);
465 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
466 if (!mv
.end
.converted
) return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
468 return null; //never reached
472 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
473 return (move.end
.y
< move.start
.y
? "0-0-0" : "0-0");
475 const L
= this.lastMoveEnd
.length
;
476 const lm
= this.lastMoveEnd
[L
-1];
477 const piece
= (!lm
? move.appear
[0].p : lm
.p
);
478 // Basic move notation:
479 let notation
= piece
.toUpperCase();
481 this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
||
482 (piece
== V
.PAWN
&& move.start
.y
!= move.end
.y
)
486 const finalSquare
= V
.CoordsToSquare(move.end
);
487 notation
+= finalSquare
;
489 // Add potential promotion indications:
490 const firstLastRank
= (c
== 'w' ? [7, 0] : [0, 7]);
491 if (move.end
.x
== firstLastRank
[1] && piece
== V
.PAWN
)
492 notation
+= "=" + move.appear
[0].p
.toUpperCase();