1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class PocketknightRules
extends ChessRules
{
6 // Testing move validity results in an infinite update loop.
7 // TODO: find a way to test validity anyway.
8 return (this.subTurn
== 2 && this.board
[x
][y
] == V
.EMPTY
);
11 static IsGoodFlags(flags
) {
12 // 4 for castle + 2 for knights
13 return !!flags
.match(/^[a-z]{4,4}[01]{2,2}$/);
17 super.setFlags(fenflags
); //castleFlags
18 this.knightFlags
= fenflags
.substr(4).split("").map(e
=> e
== "1");
22 return [this.castleFlags
, this.knightFlags
];
25 disaggregateFlags(flags
) {
26 this.castleFlags
= flags
[0];
27 this.knightFlags
= flags
[1];
30 setOtherVariables(fen
) {
31 super.setOtherVariables(fen
);
35 static GenRandInitFen(randomness
) {
37 return ChessRules
.GenRandInitFen(randomness
)
38 .slice(0, -2) + "11 -";
43 super.getFlagsFen() + this.knightFlags
.map(e
=> e
? "1" : "0").join("")
47 getPotentialMovesFrom([x
, y
]) {
48 if (this.subTurn
== 1) {
49 let moves
= super.getPotentialMovesFrom([x
, y
]);
50 // If flag allow it, add "king capture"
52 this.knightFlags
[this.turn
== 'w' ? 0 : 1] &&
53 this.getPiece(x
, y
) == V
.KING
55 const kp
= this.kingPos
[V
.GetOppCol(this.turn
)];
60 start: { x: x
, y: y
},
61 end: { x: kp
[0], y: kp
[1] }
67 // subTurn == 2: a move is a click, not handled here
72 if (this.subTurn
== 2) return super.filterValid(moves
);
73 const color
= this.turn
;
74 return moves
.filter(m
=> {
77 if (m
.appear
.length
> 0)
79 res
= !this.underCheck(color
);
81 // "Capture king": find landing square not resulting in check
82 outerLoop: for (let i
=0; i
<8; i
++) {
83 for (let j
=0; j
<8; j
++) {
84 if (this.board
[i
][j
] == V
.EMPTY
) {
85 const tMove
= new Move({
95 start: { x: -1, y: -1 }
98 const moveOk
= !this.underCheck(color
);
114 if (this.subTurn
== 1) return super.getAllValidMoves();
115 // Subturn == 2: only knight landings
117 const color
= this.turn
;
118 for (let i
=0; i
<8; i
++) {
119 for (let j
=0; j
<8; j
++) {
120 if (this.board
[i
][j
] == V
.EMPTY
) {
121 const tMove
= new Move({
131 start: { x: -1, y: -1 }
134 const moveOk
= !this.underCheck(color
);
136 if (moveOk
) moves
.push(tMove
);
144 if (isNaN(square
[0])) return null;
145 // If subTurn == 2 && square is empty && !underCheck, then drop
146 if (this.subTurn
== 2 && this.board
[square
[0]][square
[1]] == V
.EMPTY
) {
147 const color
= this.turn
;
148 const tMove
= new Move({
158 start: { x: -1, y: -1 }
161 const moveOk
= !this.underCheck(color
);
163 if (moveOk
) return tMove
;
169 move.flags
= JSON
.stringify(this.aggregateFlags());
170 if (move.appear
.length
> 0) {
171 // Usual case or knight landing
172 if (move.vanish
.length
> 0) this.epSquares
.push(this.getEpSquare(move));
173 else this.subTurn
= 1;
174 this.turn
= V
.GetOppCol(this.turn
);
176 V
.PlayOnBoard(this.board
, move);
177 if (move.vanish
.length
> 0) this.postPlay(move);
182 this.knightFlags
[this.turn
== 'w' ? 0 : 1] = false;
187 this.disaggregateFlags(JSON
.parse(move.flags
));
188 if (move.appear
.length
> 0) {
189 if (move.vanish
.length
> 0) this.epSquares
.pop();
190 else this.subTurn
= 2;
191 this.turn
= V
.GetOppCol(this.turn
);
193 V
.UndoOnBoard(this.board
, move);
194 if (move.vanish
.length
> 0) this.postUndo(move);
196 else this.subTurn
= 1;
200 let moves
= this.getAllValidMoves();
201 if (moves
.length
== 0) return null;
202 const maxeval
= V
.INFINITY
;
203 const color
= this.turn
;
204 const oppCol
= V
.GetOppCol(color
);
205 const getOppEval
= () => {
206 let evalOpp
= this.evalPosition();
207 this.getAllValidMoves().forEach(m
=> {
208 // Do not consider knight landings here
209 if (m
.appear
.length
> 0) {
211 const score
= this.getCurrentScore();
213 if (["1-0", "0-1"].includes(score
))
214 mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
215 else if (score
== "*") mvEval
= this.evalPosition();
217 (oppCol
== 'w' && mvEval
> evalOpp
) ||
218 (oppCol
== 'b' && mvEval
< evalOpp
)
227 // Custom "search" at depth 2
230 m
.eval
= (color
== "w" ? -1 : 1) * maxeval
;
231 if (m
.appear
.length
== 0) {
232 const moves2
= this.getAllValidMoves();
234 moves2
.forEach(m2
=> {
236 const score
= this.getCurrentScore();
238 if (["1-0", "0-1"].includes(score
))
239 mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
240 else if (score
== "*")
241 // Add small fluctuations to avoid dropping pieces always on the
242 // first available square.
243 mvEval
= getOppEval() + 0.05 - Math
.random() / 10;
245 (color
== 'w' && mvEval
> m
.eval
) ||
246 (color
== 'b' && mvEval
< m
.eval
)
255 const score
= this.getCurrentScore();
256 if (score
!= "1/2") {
257 if (score
!= "*") m
.eval
= (score
== "1-0" ? 1 : -1) * maxeval
;
258 else m
.eval
= getOppEval();
263 moves
.sort((a
, b
) => {
264 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
266 let candidates
= [0];
267 for (let i
= 1; i
< moves
.length
&& moves
[i
].eval
== moves
[0].eval
; i
++)
269 const mIdx
= candidates
[randInt(candidates
.length
)];
270 if (!moves
[mIdx
].next
) return moves
[mIdx
];
271 const move2
= moves
[mIdx
].next
;
272 delete moves
[mIdx
]["next"];
273 return [moves
[mIdx
], move2
];
277 if (move.vanish
.length
> 0)
278 return super.getNotation(move);
279 if (move.appear
.length
== 0)
283 return "N@" + V
.CoordsToSquare(move.end
);