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 shiftX
= V
.PawnSpecs
.directions
[color
];
124 if (x
+ shiftX
>= 0 && x
+ shiftX
< 8) {
125 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
)
126 // One square forward
127 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
129 for (let shiftY
of [-1, 1]) {
131 y
+ shiftY
>= 0 && y
+ shiftY
< 8 &&
132 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
133 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
135 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
139 let queenOnBoard
= false;
141 outerLoop: for (let i
=0; i
<8; i
++) {
142 for (let j
=0; j
<8; j
++) {
143 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
144 const p
= this.getPiece(i
, j
);
149 else if (p
== V
.PAWN
&& pawnsCount
<= 1) pawnsCount
++;
157 (color
== 'w' && ((y
<= 3 && x
== y
) || (y
>= 4 && x
== 7 - y
))) ||
158 (color
== 'b' && ((y
>= 4 && x
== y
) || (y
<= 3 && x
== 7 - y
)))
161 const addPromotion
= ([xx
, yy
], moveTo
) => {
162 // The promoted pawn shouldn't attack anything,
163 // and the promotion shouldn't discover a rook attack on anything.
164 const finalSquare
= (!moveTo
? [x
, y
] : [xx
, yy
]);
166 for (let step
of V
.steps
[V
.BISHOP
]) {
167 const [i
, j
] = [finalSquare
[0] + step
[0], finalSquare
[1] + step
[1]];
170 this.board
[i
][j
] != V
.EMPTY
&&
171 this.getColor(i
, j
) != color
177 if (validP
&& !!moveTo
) {
178 // Also check rook discovered attacks on the enemy king
185 // TODO: check opposite steps one after another, which could
186 // save some time (no need to explore the other line).
187 for (let step
of V
.steps
[V
.ROOK
]) {
188 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
189 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
193 if (V
.OnBoard(i
, j
)) {
194 const colIJ
= this.getColor(i
, j
);
195 const pieceIJ
= this.getPiece(i
, j
);
196 if (colIJ
!= color
&& pieceIJ
== V
.KING
)
197 found
[step
[0] + "," + step
[1]] = -1;
198 else if (colIJ
== color
&& pieceIJ
== V
.ROOK
)
199 found
[step
[0] + "," + step
[1]] = 1;
203 (found
["0,-1"] * found
["0,1"] < 0) ||
204 (found
["-1,0"] * found
["1,0"] < 0)
214 x: !!moveTo
? xx : x
,
215 y: yy
, //yy == y if !!moveTo
228 start: { x: x
, y: y
},
229 end: { x: xx
, y: yy
}
234 // In-place promotion always possible:
235 addPromotion([x
- shiftX
, y
]);
236 for (let step
of V
.steps
[V
.BISHOP
]) {
237 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
238 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
239 addPromotion([i
, j
], "moveTo");
245 getPotentialBishopMoves(sq
) {
246 const forward
= (this.turn
== 'w' ? -1 : 1);
247 return this.getSlideNJumpMoves(
249 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
254 getPotentialQueenMoves(sq
) {
255 return this.getSlideNJumpMoves(
263 if (this.movesCount
>= 2) return super.getAllValidMoves();
264 const color
= this.turn
;
266 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
267 moves
= moves
.concat(
268 this.getPotentialMovesFrom([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
271 return this.filterValid(moves
);
274 isAttackedByBishop(sq
, color
) {
275 const forward
= (this.turn
== 'w' ? 1 : -1);
276 return this.isAttackedBySlideNJump(
280 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
285 isAttackedByQueen(sq
, color
) {
286 return this.isAttackedBySlideNJump(
296 if (this.movesCount
<= 1) return false;
297 return super.underCheck(color
);
301 const color
= move.appear
[0].c
;
302 if (this.movesCount
<= 1) {
303 V
.PlayOnBoard(this.board
, move);
304 const piece
= move.appear
[0].p
;
305 this.reserve
[color
][piece
]--;
306 if (piece
== V
.KING
) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
307 if (this.subTurn
== 8) {
308 // All placement moves are done
310 this.turn
= V
.GetOppCol(color
);
311 if (this.movesCount
== 1) this.subTurn
= 1;
313 // Initial placement is over
314 delete this["reserve"];
315 delete this["subTurn"];
320 else super.play(move);
324 const color
= move.appear
[0].c
;
325 if (this.movesCount
<= 2) {
326 V
.UndoOnBoard(this.board
, move);
327 const piece
= move.appear
[0].p
;
328 if (piece
== V
.KING
) this.kingPos
[color
] = [-1, -1];
329 if (!this.subTurn
|| this.subTurn
== 1) {
330 // All placement moves are undone (if any)
331 if (!this.subTurn
) this.re_setReserve(8);
332 else this.subTurn
= 8;
337 this.reserve
[color
][piece
]++;
339 else super.undo(move);
343 if (this.movesCount
<= 1) return [];
344 return super.getCheckSquares();
348 if (this.movesCount
<= 1) return "*";
349 return super.getCurrentScore();
352 static get VALUES() {
364 if (this.movesCount
>= 2) return super.getComputerMove();
365 // Play a random "initialization move"
367 for (let i
=0; i
<8; i
++) {
368 const moves
= this.getAllValidMoves();
369 const moveIdx
= randInt(moves
.length
);
370 this.play(moves
[moveIdx
]);
371 res
.push(moves
[moveIdx
]);
373 for (let i
=7; i
>=0; i
--) this.undo(res
[i
]);
378 // Do not note placement moves (complete move would be too long)
379 if (move.vanish
.length
== 0) return "";
380 if (move.appear
[0].p
!= move.vanish
[0].p
) {
381 // Pawn promotion: indicate correct final square
383 V
.CoordsToSquare({ x: move.vanish
[0].x
, y: move.vanish
[0].y
})
385 V
.CoordsToSquare({ x: move.appear
[0].x
, y: move.appear
[0].y
})
386 const prefix
= (initSquare
!= destSquare
? initSquare : "");
387 return prefix
+ destSquare
+ "=Q";
389 return super.getNotation(move);