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() {
18 return ChessRules
.Lines
.concat([
24 static get PawnSpecs() {
29 // Promotions are handled differently here
35 static GenRandInitFen() {
36 return "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8 w 0";
39 re_setReserve(subTurn
) {
40 const mc
= this.movesCount
;
41 const wc
= (mc
== 0 ? 1 : 0);
42 const bc
= (mc
<= 1 ? 1 : 0);
59 this.subTurn
= subTurn
|| 1;
62 setOtherVariables(fen
) {
63 super.setOtherVariables(fen
);
64 if (this.movesCount
<= 1) this.re_setReserve();
68 return "Sittuyin/" + b
;
72 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
73 return this.board
[i
][j
].charAt(0);
77 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
78 return this.board
[i
][j
].charAt(1);
81 getReservePpath(index
, color
) {
82 return "Sittuyin/" + color
+ V
.RESERVE_PIECES
[index
];
85 static get RESERVE_PIECES() {
86 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
89 getPotentialMovesFrom([x
, y
]) {
90 if (this.movesCount
>= 2) return super.getPotentialMovesFrom([x
, y
]);
91 // Only reserve moves are allowed for now:
92 if (V
.OnBoard(x
, y
)) return [];
93 const color
= this.turn
;
94 const p
= V
.RESERVE_PIECES
[y
];
95 if (this.reserve
[color
][p
] == 0) return [];
98 ? (color
== 'w' ? [4, 7] : [0, 3])
99 : (color
== 'w' ? [7, 7] : [0, 0]);
100 const jBound
= (i
) => {
101 if (color
== 'w' && i
== 4) return [4, 7];
102 if (color
== 'b' && i
== 3) return [0, 3];
106 for (let i
= iBound
[0]; i
<= iBound
[1]; i
++) {
107 const jb
= jBound(i
);
108 for (let j
= jb
[0]; j
<= jb
[1]; j
++) {
109 if (this.board
[i
][j
] == V
.EMPTY
) {
120 start: { x: x
, y: y
},
130 getPotentialPawnMoves([x
, y
]) {
131 const color
= this.turn
;
132 const shiftX
= V
.PawnSpecs
.directions
[color
];
134 if (x
+ shiftX
>= 0 && x
+ shiftX
< 8) {
135 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
)
136 // One square forward
137 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
139 for (let shiftY
of [-1, 1]) {
141 y
+ shiftY
>= 0 && y
+ shiftY
< 8 &&
142 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
143 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
145 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
149 let queenOnBoard
= false;
151 outerLoop: for (let i
=0; i
<8; i
++) {
152 for (let j
=0; j
<8; j
++) {
153 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
154 const p
= this.getPiece(i
, j
);
159 else if (p
== V
.PAWN
&& pawnsCount
<= 1) pawnsCount
++;
167 (color
== 'w' && ((y
<= 3 && x
== y
) || (y
>= 4 && x
== 7 - y
))) ||
168 (color
== 'b' && ((y
>= 4 && x
== y
) || (y
<= 3 && x
== 7 - y
)))
171 const addPromotion
= ([xx
, yy
], moveTo
) => {
172 // The promoted pawn shouldn't attack anything,
173 // and the promotion shouldn't discover a rook attack on anything.
174 const finalSquare
= (!moveTo
? [x
, y
] : [xx
, yy
]);
176 for (let step
of V
.steps
[V
.BISHOP
]) {
177 const [i
, j
] = [finalSquare
[0] + step
[0], finalSquare
[1] + step
[1]];
180 this.board
[i
][j
] != V
.EMPTY
&&
181 this.getColor(i
, j
) != color
187 if (validP
&& !!moveTo
) {
188 // Also check rook discovered attacks on the enemy king
195 // TODO: check opposite steps one after another, which could
196 // save some time (no need to explore the other line).
197 for (let step
of V
.steps
[V
.ROOK
]) {
198 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
199 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
203 if (V
.OnBoard(i
, j
)) {
204 const colIJ
= this.getColor(i
, j
);
205 const pieceIJ
= this.getPiece(i
, j
);
206 if (colIJ
!= color
&& pieceIJ
== V
.KING
)
207 found
[step
[0] + "," + step
[1]] = -1;
208 else if (colIJ
== color
&& pieceIJ
== V
.ROOK
)
209 found
[step
[0] + "," + step
[1]] = 1;
213 (found
["0,-1"] * found
["0,1"] < 0) ||
214 (found
["-1,0"] * found
["1,0"] < 0)
224 x: !!moveTo
? xx : x
,
225 y: yy
, //yy == y if !!moveTo
238 start: { x: x
, y: y
},
239 end: { x: xx
, y: yy
}
244 // In-place promotion always possible:
245 addPromotion([x
- shiftX
, y
]);
246 for (let step
of V
.steps
[V
.BISHOP
]) {
247 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
248 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
249 addPromotion([i
, j
], "moveTo");
255 getPotentialBishopMoves(sq
) {
256 const forward
= (this.turn
== 'w' ? -1 : 1);
257 return this.getSlideNJumpMoves(
259 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
264 getPotentialQueenMoves(sq
) {
265 return this.getSlideNJumpMoves(
273 if (this.movesCount
>= 2) return super.getAllValidMoves();
274 const color
= this.turn
;
276 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
277 moves
= moves
.concat(
278 this.getPotentialMovesFrom([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
281 return this.filterValid(moves
);
284 isAttackedByBishop(sq
, color
) {
285 const forward
= (this.turn
== 'w' ? 1 : -1);
286 return this.isAttackedBySlideNJump(
290 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
295 isAttackedByQueen(sq
, color
) {
296 return this.isAttackedBySlideNJump(
306 if (this.movesCount
<= 1) return false;
307 return super.underCheck(color
);
311 const color
= move.appear
[0].c
;
312 if (this.movesCount
<= 1) {
313 V
.PlayOnBoard(this.board
, move);
314 const piece
= move.appear
[0].p
;
315 this.reserve
[color
][piece
]--;
316 if (piece
== V
.KING
) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
317 if (this.subTurn
== 8) {
318 // All placement moves are done
320 this.turn
= V
.GetOppCol(color
);
321 if (this.movesCount
== 1) this.subTurn
= 1;
323 // Initial placement is over
324 delete this["reserve"];
325 delete this["subTurn"];
330 else super.play(move);
334 const color
= move.appear
[0].c
;
335 if (this.movesCount
<= 2) {
336 V
.UndoOnBoard(this.board
, move);
337 const piece
= move.appear
[0].p
;
338 if (piece
== V
.KING
) this.kingPos
[color
] = [-1, -1];
339 if (!this.subTurn
|| this.subTurn
== 1) {
340 // All placement moves are undone (if any)
341 if (!this.subTurn
) this.re_setReserve(8);
342 else this.subTurn
= 8;
347 this.reserve
[color
][piece
]++;
349 else super.undo(move);
353 if (this.movesCount
<= 1) return [];
354 return super.getCheckSquares();
358 if (this.movesCount
<= 1) return "*";
359 return super.getCurrentScore();
362 static get VALUES() {
374 if (this.movesCount
>= 2) return super.getComputerMove();
375 // Play a random "initialization move"
377 for (let i
=0; i
<8; i
++) {
378 const moves
= this.getAllValidMoves();
379 const moveIdx
= randInt(moves
.length
);
380 this.play(moves
[moveIdx
]);
381 res
.push(moves
[moveIdx
]);
383 for (let i
=7; i
>=0; i
--) this.undo(res
[i
]);
388 // Do not note placement moves (complete move would be too long)
389 if (move.vanish
.length
== 0) return "";
390 if (move.appear
[0].p
!= move.vanish
[0].p
) {
391 // Pawn promotion: indicate correct final square
393 V
.CoordsToSquare({ x: move.vanish
[0].x
, y: move.vanish
[0].y
})
395 V
.CoordsToSquare({ x: move.appear
[0].x
, y: move.appear
[0].y
})
396 const prefix
= (initSquare
!= destSquare
? initSquare : "");
397 return prefix
+ destSquare
+ "=Q";
399 return super.getNotation(move);