1 import { ChessRules
, PiPo
} from "@/base_rules";
3 export class SchessRules
extends ChessRules
{
5 static get PawnSpecs() {
11 ChessRules
.PawnSpecs
.promotions
.concat([V
.HAWK
, V
.ELEPHANT
])
20 static get ELEPHANT() {
24 static get NOTHING() {
29 return ChessRules
.PIECES
.concat([V
.HAWK
, V
.ELEPHANT
]);
33 if ([V
.HAWK
, V
.ELEPHANT
, V
.NOTHING
].includes(b
[1])) return "Schess/" + b
;
37 static IsGoodFen(fen
) {
38 if (!ChessRules
.IsGoodFen(fen
)) return false;
39 const fenParsed
= V
.ParseFen(fen
);
41 if (!fenParsed
.pocket
|| !fenParsed
.pocket
.match(/^[0-1]{4,4}$/))
46 static IsGoodFlags(flags
) {
47 // 4 for castle + 16 for generators
48 return !!flags
.match(/^[a-z]{4,4}[01]{16,16}$/);
52 super.setFlags(fenflags
); //castleFlags
54 w: [...Array(8)], //pieces can generate Hawk or Elephant?
57 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
58 for (let c
of ["w", "b"]) {
59 for (let i
= 0; i
< 8; i
++)
60 this.pieceFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
65 return [this.castleFlags
, this.pieceFlags
];
68 disaggregateFlags(flags
) {
69 this.castleFlags
= flags
[0];
70 this.pieceFlags
= flags
[1];
73 static ParseFen(fen
) {
74 const fenParts
= fen
.split(" ");
76 ChessRules
.ParseFen(fen
),
77 { pocket: fenParts
[5] }
81 static GenRandInitFen(options
) {
83 ChessRules
.GenRandInitFen(options
).slice(0, -2) +
84 // Add pieceFlags + pocket
85 "1111111111111111 - 1111"
91 super.getFen() + " " +
98 super.getFenForRepeat() + "_" +
104 let fen
= super.getFlagsFen();
106 for (let c
of ["w", "b"])
107 for (let i
= 0; i
< 8; i
++) fen
+= (this.pieceFlags
[c
][i
] ? "1" : "0");
113 for (let c
of ["w", "b"])
114 res
+= this.pocket
[c
][V
.HAWK
] + this.pocket
[c
][V
.ELEPHANT
];
118 setOtherVariables(fen
) {
119 super.setOtherVariables(fen
);
120 const fenParsed
= V
.ParseFen(fen
);
123 h: parseInt(fenParsed
.pocket
[0], 10),
124 e: parseInt(fenParsed
.pocket
[1], 10)
127 h: parseInt(fenParsed
.pocket
[2], 10),
128 e: parseInt(fenParsed
.pocket
[3], 10)
133 getPotentialMovesFrom([x
, y
]) {
134 let moves
= undefined;
135 switch (this.getPiece(x
, y
)) {
137 moves
= this.getPotentialHawkMoves([x
, y
]);
140 moves
= this.getPotentialElephantMoves([x
, y
]);
143 moves
= super.getPotentialMovesFrom([x
, y
]);
145 // For moves presentation when choices:
146 const unshiftNothing
= (m
) => {
147 const a
= m
.appear
[0];
148 m
.appear
.unshift(new PiPo({
155 // Post-processing: add choices for hawk and elephant,
156 // except for moves letting the king under check.
157 const color
= this.turn
;
158 if (Object
.values(this.pocket
[color
]).some(v
=> v
> 0)) {
159 const firstRank
= (color
== "w" ? 7 : 0);
162 let inCheckAfter
= false;
164 if (this.underCheck(color
)) inCheckAfter
= true;
167 for (let pp
of ['h', 'e']) {
168 if (this.pocket
[color
][pp
] > 0) {
169 let shift
= (m
.appear
[0].p
== V
.NOTHING
? 1 : 0);
171 m
.start
.x
== firstRank
&&
172 this.pieceFlags
[color
][m
.start
.y
] &&
174 m
.appear
.length
== shift
+1 ||
175 // Special castle case: is initial king square free?
176 ![m
.appear
[shift
].y
, m
.appear
[shift
+1].y
]
177 .includes(m
.vanish
[0].y
)
180 let pMove
= JSON
.parse(JSON
.stringify(m
));
181 if (shift
== 1) pMove
.appear
.shift();
182 // NOTE: unshift instead of push, for choices presentation
183 pMove
.appear
.unshift(new PiPo({
189 validMoves
.push(pMove
);
190 if (shift
== 0) unshiftNothing(m
);
192 shift
= (m
.appear
[0].p
== V
.NOTHING
? 1 : 0);
194 m
.appear
.length
>= 2 + shift
&&
195 m
.vanish
.length
== 2 &&
196 ![m
.appear
[shift
].y
, m
.appear
[shift
+1].y
]
197 .includes(m
.vanish
[1].y
)
199 // Special castle case: rook flag was necessarily on
200 let pMove
= JSON
.parse(JSON
.stringify(m
));
201 if (shift
== 1) pMove
.appear
.shift();
202 pMove
.appear
.unshift(new PiPo({
208 validMoves
.push(pMove
);
209 if (shift
== 0) unshiftNothing(m
);
213 // Unshift, to show the empty square on the left:
214 validMoves
.unshift(m
);
222 getPotentialHawkMoves(sq
) {
223 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
224 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], 1)
228 getPotentialElephantMoves(sq
) {
229 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
230 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], 1)
234 isAttacked(sq
, color
) {
236 super.isAttacked(sq
, color
) ||
237 this.isAttackedByHawk(sq
, color
) ||
238 this.isAttackedByElephant(sq
, color
)
242 isAttackedByHawk(sq
, color
) {
244 this.isAttackedBySlideNJump(sq
, color
, V
.HAWK
, V
.steps
[V
.BISHOP
]) ||
245 this.isAttackedBySlideNJump(sq
, color
, V
.HAWK
, V
.steps
[V
.KNIGHT
], 1)
249 isAttackedByElephant(sq
, color
) {
251 this.isAttackedBySlideNJump(sq
, color
, V
.ELEPHANT
, V
.steps
[V
.ROOK
]) ||
252 this.isAttackedBySlideNJump(sq
, color
, V
.ELEPHANT
, V
.steps
[V
.KNIGHT
], 1)
257 if (Object
.values(this.pocket
[this.turn
]).some(v
=> v
> 0))
258 // Undercheck tests done in getPotentialMovesFrom()
260 return super.filterValid(moves
);
265 if (move.appear
.length
>= 2) {
266 if ([V
.HAWK
, V
.ELEPHANT
].includes(move.appear
[0].p
)) {
267 // A pocket piece is used
268 const color
= this.turn
;
269 this.pocket
[color
][move.appear
[0].p
] = 0;
275 const color
= move.vanish
[0].c
;
276 const piece
= move.vanish
[0].p
;
277 // Update king position + flags
278 if (piece
== V
.KING
) {
280 ([V
.HAWK
, V
.ELEPHANT
, V
.NOTHING
].includes(move.appear
[0].p
) ? 1 : 0);
281 this.kingPos
[color
][0] = move.appear
[shift
].x
;
282 this.kingPos
[color
][1] = move.appear
[shift
].y
;
284 this.updateCastleFlags(move, piece
);
286 const oppCol
= this.turn
;
287 const firstRank
= (color
== 'w' ? 7 : 0);
288 const oppFirstRank
= 7 - firstRank
;
289 // Does this move turn off a piece init square flag?
290 if (move.start
.x
== firstRank
) {
291 if (this.pieceFlags
[color
][move.start
.y
])
292 this.pieceFlags
[color
][move.start
.y
] = false;
293 // Special castle case:
294 if (move.appear
.length
>= 2 && move.vanish
.length
== 2) {
295 const L
= move.appear
.length
;
296 if (move.appear
[L
-1].p
== V
.ROOK
)
297 this.pieceFlags
[color
][move.vanish
[1].y
] = false;
300 if (move.end
.x
== oppFirstRank
&& this.pieceFlags
[oppCol
][move.end
.y
])
301 this.pieceFlags
[oppCol
][move.end
.y
] = false;
305 super.postUndo(move);
306 if (move.appear
.length
>= 2) {
307 if ([V
.HAWK
, V
.ELEPHANT
].includes(move.appear
[0].p
)) {
308 // A pocket piece was used
309 const color
= this.turn
;
310 this.pocket
[color
][move.appear
[0].p
] = 1;
315 static get SEARCH_DEPTH() {
319 static get VALUES() {
320 return Object
.assign(
330 if (move.appear
.length
>= 2) {
331 const pPieceAppear
= [V
.HAWK
, V
.ELEPHANT
].includes(move.appear
[0].p
);
332 const nothingAppear
= (move.appear
[0].p
== V
.NOTHING
);
333 if (pPieceAppear
|| nothingAppear
) {
336 suffix
= "/" + move.appear
[0].p
.toUpperCase();
337 if (move.appear
.length
== 3) {
338 // Castling; indicate square
340 V
.CoordsToSquare({ x: move.appear
[0].x
, y: move.appear
[0].y
});
343 let cmove
= JSON
.parse(JSON
.stringify(move));
344 cmove
.appear
.shift();
345 return super.getNotation(cmove
) + suffix
;
348 return super.getNotation(move);