1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class SittuyinRules
extends ChessRules
{
6 static get HasFlags() {
10 static get HasEnpassant() {
14 static get Monochrome() {
18 static get Notoodark() {
23 return ChessRules
.Lines
.concat([
29 static get PawnSpecs() {
34 // Promotions are handled differently here
40 static GenRandInitFen() {
41 return "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8 w 0";
44 re_setReserve(subTurn
) {
45 const mc
= this.movesCount
;
46 const wc
= (mc
== 0 ? 1 : 0);
47 const bc
= (mc
<= 1 ? 1 : 0);
64 this.subTurn
= subTurn
|| 1;
67 setOtherVariables(fen
) {
68 super.setOtherVariables(fen
);
69 if (this.movesCount
<= 1) this.re_setReserve();
73 return "Sittuyin/" + b
;
77 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
78 return this.board
[i
][j
].charAt(0);
82 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
83 return this.board
[i
][j
].charAt(1);
86 getReservePpath(index
, color
) {
87 return "Sittuyin/" + color
+ V
.RESERVE_PIECES
[index
];
90 static get RESERVE_PIECES() {
91 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
94 getPotentialMovesFrom([x
, y
]) {
95 if (this.movesCount
>= 2) return super.getPotentialMovesFrom([x
, y
]);
96 // Only reserve moves are allowed for now:
97 if (V
.OnBoard(x
, y
)) return [];
98 const color
= this.turn
;
99 const p
= V
.RESERVE_PIECES
[y
];
100 if (this.reserve
[color
][p
] == 0) return [];
103 ? (color
== 'w' ? [4, 7] : [0, 3])
104 : (color
== 'w' ? [7, 7] : [0, 0]);
105 const jBound
= (i
) => {
106 if (color
== 'w' && i
== 4) return [4, 7];
107 if (color
== 'b' && i
== 3) return [0, 3];
111 for (let i
= iBound
[0]; i
<= iBound
[1]; i
++) {
112 const jb
= jBound(i
);
113 for (let j
= jb
[0]; j
<= jb
[1]; j
++) {
114 if (this.board
[i
][j
] == V
.EMPTY
) {
125 start: { x: x
, y: y
},
135 getPotentialPawnMoves([x
, y
]) {
136 const color
= this.turn
;
137 const shiftX
= V
.PawnSpecs
.directions
[color
];
139 if (x
+ shiftX
>= 0 && x
+ shiftX
< 8) {
140 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
)
141 // One square forward
142 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
]));
144 for (let shiftY
of [-1, 1]) {
146 y
+ shiftY
>= 0 && y
+ shiftY
< 8 &&
147 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
148 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
150 moves
.push(this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
]));
154 let queenOnBoard
= false;
156 outerLoop: for (let i
=0; i
<8; i
++) {
157 for (let j
=0; j
<8; j
++) {
158 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
159 const p
= this.getPiece(i
, j
);
164 else if (p
== V
.PAWN
&& pawnsCount
<= 1) pawnsCount
++;
172 (color
== 'w' && ((y
<= 3 && x
== y
) || (y
>= 4 && x
== 7 - y
))) ||
173 (color
== 'b' && ((y
>= 4 && x
== y
) || (y
<= 3 && x
== 7 - y
)))
176 const addPromotion
= ([xx
, yy
], moveTo
) => {
177 // The promoted pawn shouldn't attack anything,
178 // and the promotion shouldn't discover a rook attack on anything.
179 const finalSquare
= (!moveTo
? [x
, y
] : [xx
, yy
]);
181 for (let step
of V
.steps
[V
.BISHOP
]) {
182 const [i
, j
] = [finalSquare
[0] + step
[0], finalSquare
[1] + step
[1]];
185 this.board
[i
][j
] != V
.EMPTY
&&
186 this.getColor(i
, j
) != color
192 if (validP
&& !!moveTo
) {
193 // Also check rook discovered attacks on the enemy king
200 // TODO: check opposite steps one after another, which could
201 // save some time (no need to explore the other line).
202 for (let step
of V
.steps
[V
.ROOK
]) {
203 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
204 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
208 if (V
.OnBoard(i
, j
)) {
209 const colIJ
= this.getColor(i
, j
);
210 const pieceIJ
= this.getPiece(i
, j
);
211 if (colIJ
!= color
&& pieceIJ
== V
.KING
)
212 found
[step
[0] + "," + step
[1]] = -1;
213 else if (colIJ
== color
&& pieceIJ
== V
.ROOK
)
214 found
[step
[0] + "," + step
[1]] = 1;
218 (found
["0,-1"] * found
["0,1"] < 0) ||
219 (found
["-1,0"] * found
["1,0"] < 0)
229 x: !!moveTo
? xx : x
,
230 y: yy
, //yy == y if !!moveTo
243 start: { x: x
, y: y
},
244 end: { x: xx
, y: yy
}
249 // In-place promotion always possible:
250 addPromotion([x
- shiftX
, y
]);
251 for (let step
of V
.steps
[V
.BISHOP
]) {
252 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
253 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
254 addPromotion([i
, j
], "moveTo");
260 getPotentialBishopMoves(sq
) {
261 const forward
= (this.turn
== 'w' ? -1 : 1);
262 return this.getSlideNJumpMoves(
264 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
269 getPotentialQueenMoves(sq
) {
270 return this.getSlideNJumpMoves(
278 if (this.movesCount
>= 2) return super.getAllValidMoves();
279 const color
= this.turn
;
281 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
282 moves
= moves
.concat(
283 this.getPotentialMovesFrom([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
286 return this.filterValid(moves
);
289 isAttackedByBishop(sq
, color
) {
290 const forward
= (this.turn
== 'w' ? 1 : -1);
291 return this.isAttackedBySlideNJump(
295 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
300 isAttackedByQueen(sq
, color
) {
301 return this.isAttackedBySlideNJump(
311 if (this.movesCount
<= 1) return false;
312 return super.underCheck(color
);
316 const color
= move.appear
[0].c
;
317 if (this.movesCount
<= 1) {
318 V
.PlayOnBoard(this.board
, move);
319 const piece
= move.appear
[0].p
;
320 this.reserve
[color
][piece
]--;
321 if (piece
== V
.KING
) this.kingPos
[color
] = [move.end
.x
, move.end
.y
];
322 if (this.subTurn
== 8) {
323 // All placement moves are done
325 this.turn
= V
.GetOppCol(color
);
326 if (this.movesCount
== 1) this.subTurn
= 1;
328 // Initial placement is over
329 delete this["reserve"];
330 delete this["subTurn"];
335 else super.play(move);
339 const color
= move.appear
[0].c
;
340 if (this.movesCount
<= 2) {
341 V
.UndoOnBoard(this.board
, move);
342 const piece
= move.appear
[0].p
;
343 if (piece
== V
.KING
) this.kingPos
[color
] = [-1, -1];
344 if (!this.subTurn
|| this.subTurn
== 1) {
345 // All placement moves are undone (if any)
346 if (!this.subTurn
) this.re_setReserve(8);
347 else this.subTurn
= 8;
352 this.reserve
[color
][piece
]++;
354 else super.undo(move);
358 if (this.movesCount
<= 1) return [];
359 return super.getCheckSquares();
363 if (this.movesCount
<= 1) return "*";
364 return super.getCurrentScore();
367 static get VALUES() {
379 if (this.movesCount
>= 2) return super.getComputerMove();
380 // Play a random "initialization move"
382 for (let i
=0; i
<8; i
++) {
383 const moves
= this.getAllValidMoves();
384 const moveIdx
= randInt(moves
.length
);
385 this.play(moves
[moveIdx
]);
386 res
.push(moves
[moveIdx
]);
388 for (let i
=7; i
>=0; i
--) this.undo(res
[i
]);
393 // Do not note placement moves (complete move would be too long)
394 if (move.vanish
.length
== 0) return "";
395 if (move.appear
[0].p
!= move.vanish
[0].p
) {
396 // Pawn promotion: indicate correct final square
398 V
.CoordsToSquare({ x: move.vanish
[0].x
, y: move.vanish
[0].y
})
400 V
.CoordsToSquare({ x: move.appear
[0].x
, y: move.appear
[0].y
})
401 const prefix
= (initSquare
!= destSquare
? initSquare : "");
402 return prefix
+ destSquare
+ "=Q";
404 return super.getNotation(move);