1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class SittuyinRules
extends ChessRules
{
10 static get HasFlags() {
14 static get HasEnpassant() {
18 static get Monochrome() {
22 static get Notoodark() {
27 return ChessRules
.Lines
.concat([
33 static get PawnSpecs() {
38 // Promotions are handled differently here
44 static GenRandInitFen() {
45 return "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8 w 0";
48 re_setReserve(subTurn
) {
49 const mc
= this.movesCount
;
50 const wc
= (mc
== 0 ? 1 : 0);
51 const bc
= (mc
<= 1 ? 1 : 0);
68 this.subTurn
= subTurn
|| 1;
71 setOtherVariables(fen
) {
72 super.setOtherVariables(fen
);
73 if (this.movesCount
<= 1) this.re_setReserve();
77 return "Sittuyin/" + b
;
81 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
82 return this.board
[i
][j
].charAt(0);
86 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
87 return this.board
[i
][j
].charAt(1);
90 getReservePpath(index
, color
) {
91 return "Sittuyin/" + color
+ V
.RESERVE_PIECES
[index
];
94 static get RESERVE_PIECES() {
95 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
98 getPotentialMovesFrom([x
, y
]) {
99 if (this.movesCount
>= 2) return super.getPotentialMovesFrom([x
, y
]);
100 // Only reserve moves are allowed for now:
101 if (V
.OnBoard(x
, y
)) return [];
102 const color
= this.turn
;
103 const p
= V
.RESERVE_PIECES
[y
];
104 if (this.reserve
[color
][p
] == 0) return [];
107 ? (color
== 'w' ? [4, 7] : [0, 3])
108 : (color
== 'w' ? [7, 7] : [0, 0]);
109 const jBound
= (i
) => {
110 if (color
== 'w' && i
== 4) return [4, 7];
111 if (color
== 'b' && i
== 3) return [0, 3];
115 for (let i
= iBound
[0]; i
<= iBound
[1]; i
++) {
116 const jb
= jBound(i
);
117 for (let j
= jb
[0]; j
<= jb
[1]; j
++) {
118 if (this.board
[i
][j
] == V
.EMPTY
) {
129 start: { x: x
, y: y
},
139 getPotentialPawnMoves([x
, y
]) {
140 const color
= this.turn
;
141 const shiftX
= V
.PawnSpecs
.directions
[color
];
143 if (x
+ shiftX
>= 0 && x
+ shiftX
< 8) {
144 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
)
145 // One square forward
146 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
148 for (let shiftY
of [-1, 1]) {
150 y
+ shiftY
>= 0 && y
+ shiftY
< 8 &&
151 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
152 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
154 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
158 let queenOnBoard
= false;
160 outerLoop: for (let i
=0; i
<8; i
++) {
161 for (let j
=0; j
<8; j
++) {
162 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
163 const p
= this.getPiece(i
, j
);
168 else if (p
== V
.PAWN
&& pawnsCount
<= 1) pawnsCount
++;
176 (color
== 'w' && ((y
<= 3 && x
== y
) || (y
>= 4 && x
== 7 - y
))) ||
177 (color
== 'b' && ((y
>= 4 && x
== y
) || (y
<= 3 && x
== 7 - y
)))
180 const addPromotion
= ([xx
, yy
], moveTo
) => {
181 // The promoted pawn shouldn't attack anything,
182 // and the promotion shouldn't discover a rook attack on anything.
183 const finalSquare
= (!moveTo
? [x
, y
] : [xx
, yy
]);
185 for (let step
of V
.steps
[V
.BISHOP
]) {
186 const [i
, j
] = [finalSquare
[0] + step
[0], finalSquare
[1] + step
[1]];
189 this.board
[i
][j
] != V
.EMPTY
&&
190 this.getColor(i
, j
) != color
196 if (validP
&& !!moveTo
) {
197 // Also check rook discovered attacks on the enemy king
204 // TODO: check opposite steps one after another, which could
205 // save some time (no need to explore the other line).
206 for (let step
of V
.steps
[V
.ROOK
]) {
207 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
208 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
212 if (V
.OnBoard(i
, j
)) {
213 const colIJ
= this.getColor(i
, j
);
214 const pieceIJ
= this.getPiece(i
, j
);
215 if (colIJ
!= color
&& pieceIJ
== V
.KING
)
216 found
[step
[0] + "," + step
[1]] = -1;
217 else if (colIJ
== color
&& pieceIJ
== V
.ROOK
)
218 found
[step
[0] + "," + step
[1]] = 1;
222 (found
["0,-1"] * found
["0,1"] < 0) ||
223 (found
["-1,0"] * found
["1,0"] < 0)
233 x: !!moveTo
? xx : x
,
234 y: yy
, //yy == y if !!moveTo
247 start: { x: x
, y: y
},
248 end: { x: xx
, y: yy
}
253 // In-place promotion always possible:
254 addPromotion([x
- shiftX
, y
]);
255 for (let step
of V
.steps
[V
.BISHOP
]) {
256 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
257 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
258 addPromotion([i
, j
], "moveTo");
264 getPotentialBishopMoves(sq
) {
265 const forward
= (this.turn
== 'w' ? -1 : 1);
266 return this.getSlideNJumpMoves(
267 sq
, V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]), 1);
270 getPotentialQueenMoves(sq
) {
271 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], 1);
275 if (this.movesCount
>= 2) return super.getAllValidMoves();
276 const color
= this.turn
;
278 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
279 moves
= moves
.concat(
280 this.getPotentialMovesFrom([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
283 return this.filterValid(moves
);
286 isAttackedByBishop(sq
, color
) {
287 const forward
= (this.turn
== 'w' ? 1 : -1);
288 return this.isAttackedBySlideNJump(
289 sq
, color
, V
.BISHOP
, V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]), 1);
292 isAttackedByQueen(sq
, color
) {
293 return this.isAttackedBySlideNJump(
294 sq
, color
, V
.QUEEN
, V
.steps
[V
.BISHOP
], 1);
298 if (this.movesCount
<= 1) return false;
299 return super.underCheck(color
);
303 const color
= move.appear
[0].c
;
304 if (this.movesCount
<= 1) {
305 V
.PlayOnBoard(this.board
, move);
306 const piece
= move.appear
[0].p
;
307 this.reserve
[color
][piece
]--;
308 if (piece
== V
.KING
) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
309 if (this.subTurn
== 8) {
310 // All placement moves are done
312 this.turn
= V
.GetOppCol(color
);
313 if (this.movesCount
== 1) this.subTurn
= 1;
315 // Initial placement is over
316 delete this["reserve"];
317 delete this["subTurn"];
322 else super.play(move);
326 const color
= move.appear
[0].c
;
327 if (this.movesCount
<= 2) {
328 V
.UndoOnBoard(this.board
, move);
329 const piece
= move.appear
[0].p
;
330 if (piece
== V
.KING
) this.kingPos
[color
] = [-1, -1];
331 if (!this.subTurn
|| this.subTurn
== 1) {
332 // All placement moves are undone (if any)
333 if (!this.subTurn
) this.re_setReserve(8);
334 else this.subTurn
= 8;
339 this.reserve
[color
][piece
]++;
341 else super.undo(move);
345 if (this.movesCount
<= 1) return [];
346 return super.getCheckSquares();
350 if (this.movesCount
<= 1) return "*";
351 return super.getCurrentScore();
354 static get VALUES() {
366 if (this.movesCount
>= 2) return super.getComputerMove();
367 // Play a random "initialization move"
369 for (let i
=0; i
<8; i
++) {
370 const moves
= this.getAllValidMoves();
371 const moveIdx
= randInt(moves
.length
);
372 this.play(moves
[moveIdx
]);
373 res
.push(moves
[moveIdx
]);
375 for (let i
=7; i
>=0; i
--) this.undo(res
[i
]);
380 // Do not note placement moves (complete move would be too long)
381 if (move.vanish
.length
== 0) return "";
382 if (move.appear
[0].p
!= move.vanish
[0].p
) {
383 // Pawn promotion: indicate correct final square
385 V
.CoordsToSquare({ x: move.vanish
[0].x
, y: move.vanish
[0].y
})
387 V
.CoordsToSquare({ x: move.appear
[0].x
, y: move.appear
[0].y
})
388 const prefix
= (initSquare
!= destSquare
? initSquare : "");
389 return prefix
+ destSquare
+ "=Q";
391 return super.getNotation(move);