8756ab9fb0a33aac64e79412dfd8dbeffee85208
1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class PocketknightRules
extends ChessRules
{
7 // Testing move validity results in an infinite update loop.
8 // TODO: find a way to test validity anyway.
9 return (this.subTurn
== 2 && this.board
[x
][y
] == V
.EMPTY
);
12 static IsGoodFlags(flags
) {
13 // 4 for castle + 2 for knights
14 return !!flags
.match(/^[a-z]{4,4}[01]{2,2}$/);
18 super.setFlags(fenflags
); //castleFlags
19 this.knightFlags
= fenflags
.substr(4).split("").map(e
=> e
== "1");
23 return [this.castleFlags
, this.knightFlags
];
26 disaggregateFlags(flags
) {
27 this.castleFlags
= flags
[0];
28 this.knightFlags
= flags
[1];
31 setOtherVariables(fen
) {
32 super.setOtherVariables(fen
);
36 static GenRandInitFen(randomness
) {
38 return ChessRules
.GenRandInitFen(randomness
)
39 .slice(0, -2) + "11 -";
44 super.getFlagsFen() + this.knightFlags
.map(e
=> e
? "1" : "0").join("")
48 getPotentialMovesFrom([x
, y
]) {
49 if (this.subTurn
== 1) {
50 let moves
= super.getPotentialMovesFrom([x
, y
]);
51 // If flag allow it, add "king capture"
53 this.knightFlags
[this.turn
== 'w' ? 0 : 1] &&
54 this.getPiece(x
, y
) == V
.KING
56 const kp
= this.kingPos
[V
.GetOppCol(this.turn
)];
61 start: { x: x
, y: y
},
62 end: { x: kp
[0], y: kp
[1] }
68 // subTurn == 2: a move is a click, not handled here
73 if (this.subTurn
== 2) return super.filterValid(moves
);
74 const color
= this.turn
;
75 return moves
.filter(m
=> {
78 if (m
.appear
.length
> 0)
80 res
= !this.underCheck(color
);
82 // "Capture king": find landing square not resulting in check
83 outerLoop: for (let i
=0; i
<8; i
++) {
84 for (let j
=0; j
<8; j
++) {
85 if (this.board
[i
][j
] == V
.EMPTY
) {
86 const tMove
= new Move({
96 start: { x: -1, y: -1 }
99 const moveOk
= !this.underCheck(color
);
115 if (this.subTurn
== 1) return super.getAllValidMoves();
116 // Subturn == 2: only knight landings
118 const color
= this.turn
;
119 for (let i
=0; i
<8; i
++) {
120 for (let j
=0; j
<8; j
++) {
121 if (this.board
[i
][j
] == V
.EMPTY
) {
122 const tMove
= new Move({
132 start: { x: -1, y: -1 }
135 const moveOk
= !this.underCheck(color
);
137 if (moveOk
) moves
.push(tMove
);
145 if (isNaN(square
[0])) return null;
146 // If subTurn == 2 && square is empty && !underCheck, then drop
147 if (this.subTurn
== 2 && this.board
[square
[0]][square
[1]] == V
.EMPTY
) {
148 const color
= this.turn
;
149 const tMove
= new Move({
159 start: { x: -1, y: -1 }
162 const moveOk
= !this.underCheck(color
);
164 if (moveOk
) return tMove
;
170 move.flags
= JSON
.stringify(this.aggregateFlags());
171 if (move.appear
.length
> 0) {
172 // Usual case or knight landing
173 if (move.vanish
.length
> 0) this.epSquares
.push(this.getEpSquare(move));
174 else this.subTurn
= 1;
175 this.turn
= V
.GetOppCol(this.turn
);
177 V
.PlayOnBoard(this.board
, move);
178 if (move.vanish
.length
> 0) this.postPlay(move);
183 this.knightFlags
[this.turn
== 'w' ? 0 : 1] = false;
188 this.disaggregateFlags(JSON
.parse(move.flags
));
189 if (move.appear
.length
> 0) {
190 if (move.vanish
.length
> 0) this.epSquares
.pop();
191 else this.subTurn
= 2;
192 this.turn
= V
.GetOppCol(this.turn
);
194 V
.UndoOnBoard(this.board
, move);
195 if (move.vanish
.length
> 0) this.postUndo(move);
197 else this.subTurn
= 1;
201 let moves
= this.getAllValidMoves();
202 if (moves
.length
== 0) return null;
203 const maxeval
= V
.INFINITY
;
204 const color
= this.turn
;
205 const oppCol
= V
.GetOppCol(color
);
206 const getOppEval
= () => {
207 let evalOpp
= this.evalPosition();
208 this.getAllValidMoves().forEach(m
=> {
209 // Do not consider knight landings here
210 if (m
.appear
.length
> 0) {
212 const score
= this.getCurrentScore();
214 if (["1-0", "0-1"].includes(score
))
215 mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
216 else if (score
== "*") mvEval
= this.evalPosition();
218 (oppCol
== 'w' && mvEval
> evalOpp
) ||
219 (oppCol
== 'b' && mvEval
< evalOpp
)
228 // Custom "search" at depth 2
231 m
.eval
= (color
== "w" ? -1 : 1) * maxeval
;
232 if (m
.appear
.length
== 0) {
233 const moves2
= this.getAllValidMoves();
235 moves2
.forEach(m2
=> {
237 const score
= this.getCurrentScore();
239 if (["1-0", "0-1"].includes(score
))
240 mvEval
= (score
== "1-0" ? 1 : -1) * maxeval
;
241 else if (score
== "*")
242 // Add small fluctuations to avoid dropping pieces always on the
243 // first available square.
244 mvEval
= getOppEval() + 0.05 - Math
.random() / 10;
246 (color
== 'w' && mvEval
> m
.eval
) ||
247 (color
== 'b' && mvEval
< m
.eval
)
256 const score
= this.getCurrentScore();
257 if (score
!= "1/2") {
258 if (score
!= "*") m
.eval
= (score
== "1-0" ? 1 : -1) * maxeval
;
259 else m
.eval
= getOppEval();
264 moves
.sort((a
, b
) => {
265 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
267 let candidates
= [0];
268 for (let i
= 1; i
< moves
.length
&& moves
[i
].eval
== moves
[0].eval
; i
++)
270 const mIdx
= candidates
[randInt(candidates
.length
)];
271 if (!moves
[mIdx
].next
) return moves
[mIdx
];
272 const move2
= moves
[mIdx
].next
;
273 delete moves
[mIdx
]["next"];
274 return [moves
[mIdx
], move2
];
278 if (move.vanish
.length
> 0)
279 return super.getNotation(move);
280 if (move.appear
.length
== 0)
284 return "N@" + V
.CoordsToSquare(move.end
);