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() {
18 // Promotions are handled differently here
24 static GenRandInitFen() {
25 return "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8 w 0";
28 re_setReserve(subTurn
) {
29 const mc
= this.movesCount
;
30 const wc
= (mc
== 0 ? 1 : 0);
31 const bc
= (mc
<= 1 ? 1 : 0);
48 this.subTurn
= subTurn
|| 1;
51 setOtherVariables(fen
) {
52 super.setOtherVariables(fen
);
53 if (this.movesCount
<= 1) this.re_setReserve();
57 return "Sittuyin/" + b
;
61 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
62 return this.board
[i
][j
].charAt(0);
66 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
67 return this.board
[i
][j
].charAt(1);
70 getReservePpath(index
, color
) {
71 return "Sittuyin/" + color
+ V
.RESERVE_PIECES
[index
];
74 static get RESERVE_PIECES() {
75 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
78 getPotentialMovesFrom([x
, y
]) {
79 if (this.movesCount
>= 2) return super.getPotentialMovesFrom([x
, y
]);
80 // Only reserve moves are allowed for now:
81 if (V
.OnBoard(x
, y
)) return [];
82 const color
= this.turn
;
83 const p
= V
.RESERVE_PIECES
[y
];
84 if (this.reserve
[color
][p
] == 0) return [];
87 ? (color
== 'w' ? [4, 7] : [0, 3])
88 : (color
== 'w' ? [7, 7] : [0, 0]);
89 const jBound
= (i
) => {
90 if (color
== 'w' && i
== 4) return [4, 7];
91 if (color
== 'b' && i
== 3) return [0, 3];
95 for (let i
= iBound
[0]; i
<= iBound
[1]; i
++) {
97 for (let j
= jb
[0]; j
<= jb
[1]; j
++) {
98 if (this.board
[i
][j
] == V
.EMPTY
) {
109 start: { x: x
, y: y
},
119 getPotentialPawnMoves([x
, y
]) {
120 const color
= this.turn
;
121 const shiftX
= V
.PawnSpecs
.directions
[color
];
123 if (x
+ shiftX
>= 0 && x
+ shiftX
< 8) {
124 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
)
125 // One square forward
126 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
128 for (let shiftY
of [-1, 1]) {
130 y
+ shiftY
>= 0 && y
+ shiftY
< 8 &&
131 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
132 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
134 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
138 let queenOnBoard
= false;
140 outerLoop: for (let i
=0; i
<8; i
++) {
141 for (let j
=0; j
<8; j
++) {
142 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
143 const p
= this.getPiece(i
, j
);
148 else if (p
== V
.PAWN
&& pawnsCount
<= 1) pawnsCount
++;
156 (color
== 'w' && ((y
<= 3 && x
== y
) || (y
>= 4 && x
== 7 - y
))) ||
157 (color
== 'b' && ((y
>= 4 && x
== y
) || (y
<= 3 && x
== 7 - y
)))
160 const addPromotion
= ([xx
, yy
], moveTo
) => {
161 // The promoted pawn shouldn't attack anything,
162 // and the promotion shouldn't discover a rook attack on anything.
163 const finalSquare
= (!moveTo
? [x
, y
] : [xx
, yy
]);
165 for (let step
of V
.steps
[V
.BISHOP
]) {
166 const [i
, j
] = [finalSquare
[0] + step
[0], finalSquare
[1] + step
[1]];
169 this.board
[i
][j
] != V
.EMPTY
&&
170 this.getColor(i
, j
) != color
176 if (validP
&& !!moveTo
) {
177 // Also check rook discovered attacks on the enemy king
184 // TODO: check opposite steps one after another, which could
185 // save some time (no need to explore the other line).
186 for (let step
of V
.steps
[V
.ROOK
]) {
187 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
188 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
192 if (V
.OnBoard(i
, j
)) {
193 const colIJ
= this.getColor(i
, j
);
194 const pieceIJ
= this.getPiece(i
, j
);
195 if (colIJ
!= color
&& pieceIJ
== V
.KING
)
196 found
[step
[0] + "," + step
[1]] = -1;
197 else if (colIJ
== color
&& pieceIJ
== V
.ROOK
)
198 found
[step
[0] + "," + step
[1]] = 1;
202 (found
["0,-1"] * found
["0,1"] < 0) ||
203 (found
["-1,0"] * found
["1,0"] < 0)
213 x: !!moveTo
? xx : x
,
214 y: yy
, //yy == y if !!moveTo
227 start: { x: x
, y: y
},
228 end: { x: xx
, y: yy
}
233 // In-place promotion always possible:
234 addPromotion([x
- shiftX
, y
]);
235 for (let step
of V
.steps
[V
.BISHOP
]) {
236 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
237 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
238 addPromotion([i
, j
], "moveTo");
244 getPotentialBishopMoves(sq
) {
245 const forward
= (this.turn
== 'w' ? -1 : 1);
246 return this.getSlideNJumpMoves(
248 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
253 getPotentialQueenMoves(sq
) {
254 return this.getSlideNJumpMoves(
262 if (this.movesCount
>= 2) return super.getAllValidMoves();
263 const color
= this.turn
;
265 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
266 moves
= moves
.concat(
267 this.getPotentialMovesFrom([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
270 return this.filterValid(moves
);
273 isAttackedByBishop(sq
, color
) {
274 const forward
= (this.turn
== 'w' ? 1 : -1);
275 return this.isAttackedBySlideNJump(
279 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
284 isAttackedByQueen(sq
, color
) {
285 return this.isAttackedBySlideNJump(
295 if (this.movesCount
<= 1) return false;
296 return super.underCheck(color
);
300 const color
= move.appear
[0].c
;
301 if (this.movesCount
<= 1) {
302 V
.PlayOnBoard(this.board
, move);
303 const piece
= move.appear
[0].p
;
304 this.reserve
[color
][piece
]--;
305 if (piece
== V
.KING
) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
306 if (this.subTurn
== 8) {
307 // All placement moves are done
309 this.turn
= V
.GetOppCol(color
);
310 if (this.movesCount
== 1) this.subTurn
= 1;
312 // Initial placement is over
313 delete this["reserve"];
314 delete this["subTurn"];
319 else super.play(move);
323 const color
= move.appear
[0].c
;
324 if (this.movesCount
<= 2) {
325 V
.UndoOnBoard(this.board
, move);
326 const piece
= move.appear
[0].p
;
327 if (piece
== V
.KING
) this.kingPos
[color
] = [-1, -1];
328 if (!this.subTurn
|| this.subTurn
== 1) {
329 // All placement moves are undone (if any)
330 if (!this.subTurn
) this.re_setReserve(8);
331 else this.subTurn
= 8;
336 this.reserve
[color
][piece
]++;
338 else super.undo(move);
342 if (this.movesCount
<= 1) return [];
343 return super.getCheckSquares();
347 if (this.movesCount
<= 1) return "*";
348 return super.getCurrentScore();
351 static get VALUES() {
363 if (this.movesCount
>= 2) return super.getComputerMove();
364 // Play a random "initialization move"
366 for (let i
=0; i
<8; i
++) {
367 const moves
= this.getAllValidMoves();
368 const moveIdx
= randInt(moves
.length
);
369 this.play(moves
[moveIdx
]);
370 res
.push(moves
[moveIdx
]);
372 for (let i
=7; i
>=0; i
--) this.undo(res
[i
]);
377 // Do not note placement moves (complete move would be too long)
378 if (move.vanish
.length
== 0) return "";
379 if (move.appear
[0].p
!= move.vanish
[0].p
) {
380 // Pawn promotion: indicate correct final square
382 V
.CoordsToSquare({ x: move.vanish
[0].x
, y: move.vanish
[0].y
})
384 V
.CoordsToSquare({ x: move.appear
[0].x
, y: move.appear
[0].y
})
385 const prefix
= (initSquare
!= destSquare
? initSquare : "");
386 return prefix
+ destSquare
+ "=Q";
388 return super.getNotation(move);