1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class SittuyinRules
extends ChessRules
{
5 static get HasFlags() {
9 static get HasEnpassant() {
13 static get Monochrome() {
17 static get Notoodark() {
22 return ChessRules
.Lines
.concat([
28 static get PawnSpecs() {
33 // Promotions are handled differently here
39 static GenRandInitFen() {
40 return "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8 w 0";
43 re_setReserve(subTurn
) {
44 const mc
= this.movesCount
;
45 const wc
= (mc
== 0 ? 1 : 0);
46 const bc
= (mc
<= 1 ? 1 : 0);
63 this.subTurn
= subTurn
|| 1;
66 setOtherVariables(fen
) {
67 super.setOtherVariables(fen
);
68 if (this.movesCount
<= 1) this.re_setReserve();
72 return "Sittuyin/" + b
;
76 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
77 return this.board
[i
][j
].charAt(0);
81 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
82 return this.board
[i
][j
].charAt(1);
85 getReservePpath(index
, color
) {
86 return "Sittuyin/" + color
+ V
.RESERVE_PIECES
[index
];
89 static get RESERVE_PIECES() {
90 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
93 getPotentialMovesFrom([x
, y
]) {
94 if (this.movesCount
>= 2) return super.getPotentialMovesFrom([x
, y
]);
95 // Only reserve moves are allowed for now:
96 if (V
.OnBoard(x
, y
)) return [];
97 const color
= this.turn
;
98 const p
= V
.RESERVE_PIECES
[y
];
99 if (this.reserve
[color
][p
] == 0) return [];
102 ? (color
== 'w' ? [4, 7] : [0, 3])
103 : (color
== 'w' ? [7, 7] : [0, 0]);
104 const jBound
= (i
) => {
105 if (color
== 'w' && i
== 4) return [4, 7];
106 if (color
== 'b' && i
== 3) return [0, 3];
110 for (let i
= iBound
[0]; i
<= iBound
[1]; i
++) {
111 const jb
= jBound(i
);
112 for (let j
= jb
[0]; j
<= jb
[1]; j
++) {
113 if (this.board
[i
][j
] == V
.EMPTY
) {
124 start: { x: x
, y: y
},
134 getPotentialPawnMoves([x
, y
]) {
135 const color
= this.turn
;
136 const shiftX
= V
.PawnSpecs
.directions
[color
];
138 if (x
+ shiftX
>= 0 && x
+ shiftX
< 8) {
139 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
)
140 // One square forward
141 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
143 for (let shiftY
of [-1, 1]) {
145 y
+ shiftY
>= 0 && y
+ shiftY
< 8 &&
146 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
147 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
149 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
153 let queenOnBoard
= false;
155 outerLoop: for (let i
=0; i
<8; i
++) {
156 for (let j
=0; j
<8; j
++) {
157 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
158 const p
= this.getPiece(i
, j
);
163 else if (p
== V
.PAWN
&& pawnsCount
<= 1) pawnsCount
++;
171 (color
== 'w' && ((y
<= 3 && x
== y
) || (y
>= 4 && x
== 7 - y
))) ||
172 (color
== 'b' && ((y
>= 4 && x
== y
) || (y
<= 3 && x
== 7 - y
)))
175 const addPromotion
= ([xx
, yy
], moveTo
) => {
176 // The promoted pawn shouldn't attack anything,
177 // and the promotion shouldn't discover a rook attack on anything.
178 const finalSquare
= (!moveTo
? [x
, y
] : [xx
, yy
]);
180 for (let step
of V
.steps
[V
.BISHOP
]) {
181 const [i
, j
] = [finalSquare
[0] + step
[0], finalSquare
[1] + step
[1]];
184 this.board
[i
][j
] != V
.EMPTY
&&
185 this.getColor(i
, j
) != color
191 if (validP
&& !!moveTo
) {
192 // Also check rook discovered attacks on the enemy king
199 // TODO: check opposite steps one after another, which could
200 // save some time (no need to explore the other line).
201 for (let step
of V
.steps
[V
.ROOK
]) {
202 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
203 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
207 if (V
.OnBoard(i
, j
)) {
208 const colIJ
= this.getColor(i
, j
);
209 const pieceIJ
= this.getPiece(i
, j
);
210 if (colIJ
!= color
&& pieceIJ
== V
.KING
)
211 found
[step
[0] + "," + step
[1]] = -1;
212 else if (colIJ
== color
&& pieceIJ
== V
.ROOK
)
213 found
[step
[0] + "," + step
[1]] = 1;
217 (found
["0,-1"] * found
["0,1"] < 0) ||
218 (found
["-1,0"] * found
["1,0"] < 0)
228 x: !!moveTo
? xx : x
,
229 y: yy
, //yy == y if !!moveTo
242 start: { x: x
, y: y
},
243 end: { x: xx
, y: yy
}
248 // In-place promotion always possible:
249 addPromotion([x
- shiftX
, y
]);
250 for (let step
of V
.steps
[V
.BISHOP
]) {
251 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
252 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
253 addPromotion([i
, j
], "moveTo");
259 getPotentialBishopMoves(sq
) {
260 const forward
= (this.turn
== 'w' ? -1 : 1);
261 return this.getSlideNJumpMoves(
263 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
268 getPotentialQueenMoves(sq
) {
269 return this.getSlideNJumpMoves(
277 if (this.movesCount
>= 2) return super.getAllValidMoves();
278 const color
= this.turn
;
280 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
281 moves
= moves
.concat(
282 this.getPotentialMovesFrom([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
285 return this.filterValid(moves
);
288 isAttackedByBishop(sq
, color
) {
289 const forward
= (this.turn
== 'w' ? 1 : -1);
290 return this.isAttackedBySlideNJump(
294 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
299 isAttackedByQueen(sq
, color
) {
300 return this.isAttackedBySlideNJump(
310 if (this.movesCount
<= 1) return false;
311 return super.underCheck(color
);
315 const color
= move.appear
[0].c
;
316 if (this.movesCount
<= 1) {
317 V
.PlayOnBoard(this.board
, move);
318 const piece
= move.appear
[0].p
;
319 this.reserve
[color
][piece
]--;
320 if (piece
== V
.KING
) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
321 if (this.subTurn
== 8) {
322 // All placement moves are done
324 this.turn
= V
.GetOppCol(color
);
325 if (this.movesCount
== 1) this.subTurn
= 1;
327 // Initial placement is over
328 delete this["reserve"];
329 delete this["subTurn"];
334 else super.play(move);
338 const color
= move.appear
[0].c
;
339 if (this.movesCount
<= 2) {
340 V
.UndoOnBoard(this.board
, move);
341 const piece
= move.appear
[0].p
;
342 if (piece
== V
.KING
) this.kingPos
[color
] = [-1, -1];
343 if (!this.subTurn
|| this.subTurn
== 1) {
344 // All placement moves are undone (if any)
345 if (!this.subTurn
) this.re_setReserve(8);
346 else this.subTurn
= 8;
351 this.reserve
[color
][piece
]++;
353 else super.undo(move);
357 if (this.movesCount
<= 1) return [];
358 return super.getCheckSquares();
362 if (this.movesCount
<= 1) return "*";
363 return super.getCurrentScore();
366 static get VALUES() {
378 if (this.movesCount
>= 2) return super.getComputerMove();
379 // Play a random "initialization move"
381 for (let i
=0; i
<8; i
++) {
382 const moves
= this.getAllValidMoves();
383 const moveIdx
= randInt(moves
.length
);
384 this.play(moves
[moveIdx
]);
385 res
.push(moves
[moveIdx
]);
387 for (let i
=7; i
>=0; i
--) this.undo(res
[i
]);
392 // Do not note placement moves (complete move would be too long)
393 if (move.vanish
.length
== 0) return "";
394 if (move.appear
[0].p
!= move.vanish
[0].p
) {
395 // Pawn promotion: indicate correct final square
397 V
.CoordsToSquare({ x: move.vanish
[0].x
, y: move.vanish
[0].y
})
399 V
.CoordsToSquare({ x: move.appear
[0].x
, y: move.appear
[0].y
})
400 const prefix
= (initSquare
!= destSquare
? initSquare : "");
401 return prefix
+ destSquare
+ "=Q";
403 return super.getNotation(move);