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 PawnSpecs() {
19 // Promotions are handled differently here
25 static GenRandInitFen() {
26 return "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8 w 0";
29 re_setReserve(subTurn
) {
30 const mc
= this.movesCount
;
31 const wc
= (mc
== 0 ? 1 : 0);
32 const bc
= (mc
<= 1 ? 1 : 0);
49 this.subTurn
= subTurn
|| 1;
52 setOtherVariables(fen
) {
53 super.setOtherVariables(fen
);
54 if (this.movesCount
<= 1) this.re_setReserve();
58 return "Sittuyin/" + b
;
62 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
63 return this.board
[i
][j
].charAt(0);
67 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
68 return this.board
[i
][j
].charAt(1);
71 getReservePpath(index
, color
) {
72 return "Sittuyin/" + color
+ V
.RESERVE_PIECES
[index
];
75 static get RESERVE_PIECES() {
76 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
79 getPotentialMovesFrom([x
, y
]) {
80 if (this.movesCount
>= 2) return super.getPotentialMovesFrom([x
, y
]);
81 // Only reserve moves are allowed for now:
82 if (V
.OnBoard(x
, y
)) return [];
83 const color
= this.turn
;
84 const p
= V
.RESERVE_PIECES
[y
];
85 if (this.reserve
[color
][p
] == 0) return [];
88 ? (color
== 'w' ? [4, 7] : [0, 3])
89 : (color
== 'w' ? [7, 7] : [0, 0]);
90 const jBound
= (i
) => {
91 if (color
== 'w' && i
== 4) return [4, 7];
92 if (color
== 'b' && i
== 3) return [0, 3];
96 for (let i
= iBound
[0]; i
<= iBound
[1]; i
++) {
98 for (let j
= jb
[0]; j
<= jb
[1]; j
++) {
99 if (this.board
[i
][j
] == V
.EMPTY
) {
110 start: { x: x
, y: y
},
120 getPotentialPawnMoves([x
, y
]) {
121 const color
= this.turn
;
122 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
123 const shiftX
= V
.PawnSpecs
.directions
[color
];
125 // NOTE: next condition is generally true (no pawn on last rank)
126 if (x
+ shiftX
>= 0 && x
+ shiftX
< sizeX
) {
127 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
) {
128 // One square forward
129 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
132 if (V
.PawnSpecs
.canCapture
) {
133 for (let shiftY
of [-1, 1]) {
139 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
140 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
142 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
148 let queenOnBoard
= false;
150 outerLoop: for (let i
=0; i
<8; i
++) {
151 for (let j
=0; j
<8; j
++) {
152 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
153 const p
= this.getPiece(i
, j
);
158 else if (p
== V
.PAWN
&& pawnsCount
<= 1) pawnsCount
++;
166 (color
== 'w' && ((y
<= 3 && x
== y
) || (y
>= 4 && x
== 7 - y
))) ||
167 (color
== 'b' && ((y
>= 4 && x
== y
) || (y
<= 3 && x
== 7 - y
)))
170 const addPromotion
= ([xx
, yy
], moveTo
) => {
171 // The promoted pawn shouldn't attack anything,
172 // and the promotion shouldn't discover a rook attack on anything.
173 const finalSquare
= (!moveTo
? [x
, y
] : [xx
, yy
]);
175 for (let step
of V
.steps
[V
.BISHOP
]) {
176 const [i
, j
] = [finalSquare
[0] + step
[0], finalSquare
[1] + step
[1]];
179 this.board
[i
][j
] != V
.EMPTY
&&
180 this.getColor(i
, j
) != color
186 if (validP
&& !!moveTo
) {
187 // Also check rook discovered attacks on the enemy king
194 // TODO: check opposite steps one after another, which could
195 // save some time (no need to explore the other line).
196 for (let step
of V
.steps
[V
.ROOK
]) {
197 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
198 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
202 if (V
.OnBoard(i
, j
)) {
203 const colIJ
= this.getColor(i
, j
);
204 const pieceIJ
= this.getPiece(i
, j
);
205 if (colIJ
!= color
&& pieceIJ
== V
.KING
)
206 found
[step
[0] + "," + step
[1]] = -1;
207 else if (colIJ
== color
&& pieceIJ
== V
.ROOK
)
208 found
[step
[0] + "," + step
[1]] = 1;
212 (found
["0,-1"] * found
["0,1"] < 0) ||
213 (found
["-1,0"] * found
["1,0"] < 0)
223 x: !!moveTo
? xx : x
,
224 y: yy
, //yy == y if !!moveTo
237 start: { x: x
, y: y
},
238 end: { x: xx
, y: yy
}
243 // In-place promotion always possible:
244 addPromotion([x
- shiftX
, y
]);
245 for (let step
of V
.steps
[V
.BISHOP
]) {
246 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
247 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
248 addPromotion([i
, j
], "moveTo");
254 getPotentialBishopMoves(sq
) {
255 const forward
= (this.turn
== 'w' ? -1 : 1);
256 return this.getSlideNJumpMoves(
258 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
263 getPotentialQueenMoves(sq
) {
264 return this.getSlideNJumpMoves(
272 if (this.movesCount
>= 2) return super.getAllValidMoves();
273 const color
= this.turn
;
275 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
276 moves
= moves
.concat(
277 this.getPotentialMovesFrom([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
280 return this.filterValid(moves
);
283 isAttackedByBishop(sq
, color
) {
284 const forward
= (this.turn
== 'w' ? 1 : -1);
285 return this.isAttackedBySlideNJump(
289 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
294 isAttackedByQueen(sq
, color
) {
295 return this.isAttackedBySlideNJump(
305 if (this.movesCount
<= 1) return false;
306 return super.underCheck(color
);
310 const color
= move.appear
[0].c
;
311 if (this.movesCount
<= 1) {
312 V
.PlayOnBoard(this.board
, move);
313 const piece
= move.appear
[0].p
;
314 this.reserve
[color
][piece
]--;
315 if (piece
== V
.KING
) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
316 if (this.subTurn
== 8) {
317 // All placement moves are done
319 this.turn
= V
.GetOppCol(color
);
320 if (this.movesCount
== 1) this.subTurn
= 1;
322 // Initial placement is over
323 delete this["reserve"];
324 delete this["subTurn"];
329 else super.play(move);
333 const color
= move.appear
[0].c
;
334 if (this.movesCount
<= 2) {
335 V
.UndoOnBoard(this.board
, move);
336 const piece
= move.appear
[0].p
;
337 if (piece
== V
.KING
) this.kingPos
[color
] = [-1, -1];
338 if (!this.subTurn
|| this.subTurn
== 1) {
339 // All placement moves are undone (if any)
340 if (!this.subTurn
) this.re_setReserve(8);
341 else this.subTurn
= 8;
346 this.reserve
[color
][piece
]++;
348 else super.undo(move);
352 if (this.movesCount
<= 1) return [];
353 return super.getCheckSquares();
357 if (this.movesCount
<= 1) return "*";
358 return super.getCurrentScore();
361 static get VALUES() {
373 if (this.movesCount
>= 2) return super.getComputerMove();
374 // Play a random "initialization move"
376 for (let i
=0; i
<8; i
++) {
377 const moves
= this.getAllValidMoves();
378 const moveIdx
= randInt(moves
.length
);
379 this.play(moves
[moveIdx
]);
380 res
.push(moves
[moveIdx
]);
382 for (let i
=7; i
>=0; i
--) this.undo(res
[i
]);
387 // Do not note placement moves (complete move would be too long)
388 if (move.vanish
.length
== 0) return "";
389 if (move.appear
[0].p
!= move.vanish
[0].p
) {
390 // Pawn promotion: indicate correct final square
392 V
.CoordsToSquare({ x: move.vanish
[0].x
, y: move.vanish
[0].y
})
394 V
.CoordsToSquare({ x: move.appear
[0].x
, y: move.appear
[0].y
})
395 const prefix
= (initSquare
!= destSquare
? initSquare : "");
396 return prefix
+ destSquare
+ "=Q";
398 return super.getNotation(move);