31c363d2458627c8654ae6e6660014608105c2fa
1 import { ChessRules
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export const VariantRules
= class MarseilleRules
extends ChessRules
6 static IsGoodEnpassant(enpassant
)
10 const squares
= enpassant
.split(",");
11 if (squares
.length
> 2)
13 for (let sq
of squares
)
15 const ep
= V
.SquareToCoords(sq
);
16 if (isNaN(ep
.x
) || !V
.OnBoard(ep
))
25 return this.turn
+ this.subTurn
;
28 // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
31 const L
= this.epSquares
.length
;
32 if (this.epSquares
[L
-1].every(epsq
=> epsq
=== undefined))
33 return "-"; //no en-passant
35 this.epSquares
[L
-1].forEach(epsq
=> {
37 res
+= V
.CoordsToSquare(epsq
) + ",";
39 return res
.slice(0,-1); //remove last comma
42 setOtherVariables(fen
)
44 const parsedFen
= V
.ParseFen(fen
);
45 this.setFlags(parsedFen
.flags
);
46 if (parsedFen
.enpassant
== "-")
47 this.epSquares
= [ [undefined] ];
51 const squares
= parsedFen
.enpassant
.split(",");
52 for (let sq
of squares
)
53 res
.push(V
.SquareToCoords(sq
));
54 this.epSquares
= [ res
];
56 this.scanKingsRooks(fen
);
57 // Extract subTurn from turn indicator: "w" (first move), or
58 // "w1" or "w2" white subturn 1 or 2, and same for black
59 const fullTurn
= V
.ParseFen(fen
).turn
;
60 this.turn
= fullTurn
[0];
61 this.subTurn
= (fullTurn
[1] || 0); //"w0" = special code for first move in game
64 getPotentialPawnMoves([x
,y
])
66 const color
= this.turn
;
68 const [sizeX
,sizeY
] = [V
.size
.x
,V
.size
.y
];
69 const shiftX
= (color
== "w" ? -1 : 1);
70 const firstRank
= (color
== 'w' ? sizeX
-1 : 0);
71 const startRank
= (color
== "w" ? sizeX
-2 : 1);
72 const lastRank
= (color
== "w" ? 0 : sizeX
-1);
73 const finalPieces
= x
+ shiftX
== lastRank
74 ? [V
.ROOK
,V
.KNIGHT
,V
.BISHOP
,V
.QUEEN
]
78 if (this.board
[x
+shiftX
][y
] == V
.EMPTY
)
80 for (let piece
of finalPieces
)
82 moves
.push(this.getBasicMove([x
,y
], [x
+shiftX
,y
],
85 // Next condition because pawns on 1st rank can generally jump
86 if ([startRank
,firstRank
].includes(x
)
87 && this.board
[x
+2*shiftX
][y
] == V
.EMPTY
)
90 moves
.push(this.getBasicMove([x
,y
], [x
+2*shiftX
,y
]));
94 for (let shiftY
of [-1,1])
96 if (y
+ shiftY
>= 0 && y
+ shiftY
< sizeY
97 && this.board
[x
+shiftX
][y
+shiftY
] != V
.EMPTY
98 && this.canTake([x
,y
], [x
+shiftX
,y
+shiftY
]))
100 for (let piece
of finalPieces
)
102 moves
.push(this.getBasicMove([x
,y
], [x
+shiftX
,y
+shiftY
],
108 // En passant: always OK if subturn 1,
109 // OK on subturn 2 only if enPassant was played at subturn 1
110 // (and if there are two e.p. squares available).
111 const Lep
= this.epSquares
.length
;
112 const epSquares
= this.epSquares
[Lep
-1]; //always at least one element
114 epSquares
.forEach(sq
=> {
118 if (epSqs
.length
== 0)
120 const oppCol
= V
.GetOppCol(color
);
121 for (let sq
of epSqs
)
123 if (this.subTurn
== 1 || (epSqs
.length
== 2 &&
124 // Was this en-passant capture already played at subturn 1 ?
125 // (Or maybe the opponent filled the en-passant square with a piece)
126 this.board
[epSqs
[0].x
][epSqs
[0].y
] != V
.EMPTY
))
128 if (sq
.x
== x
+shiftX
&& Math
.abs(sq
.y
- y
) == 1
129 // Add condition "enemy pawn must be present"
130 && this.getPiece(x
,sq
.y
) == V
.PAWN
&& this.getColor(x
,sq
.y
) == oppCol
)
132 let epMove
= this.getBasicMove([x
,y
], [sq
.x
,sq
.y
]);
149 move.flags
= JSON
.stringify(this.aggregateFlags());
150 move.turn
= this.turn
+ this.subTurn
;
151 V
.PlayOnBoard(this.board
, move);
152 const epSq
= this.getEpSquare(move);
153 if (this.subTurn
== 0) //first move in game
157 this.epSquares
.push([epSq
]);
159 // Does this move give check on subturn 1? If yes, skip subturn 2
160 else if (this.subTurn
==1 && this.underCheck(V
.GetOppCol(this.turn
)))
162 this.turn
= V
.GetOppCol(this.turn
);
163 this.epSquares
.push([epSq
]);
164 move.checkOnSubturn1
= true;
168 if (this.subTurn
== 2)
170 this.turn
= V
.GetOppCol(this.turn
);
171 let lastEpsq
= this.epSquares
[this.epSquares
.length
-1];
175 this.epSquares
.push([epSq
]);
176 this.subTurn
= 3 - this.subTurn
;
178 this.updateVariables(move);
183 this.disaggregateFlags(JSON
.parse(move.flags
));
184 V
.UndoOnBoard(this.board
, move);
185 if (move.turn
[1] == '0' || move.checkOnSubturn1
|| this.subTurn
== 2)
186 this.epSquares
.pop();
187 else //this.subTurn == 1
189 let lastEpsq
= this.epSquares
[this.epSquares
.length
-1];
192 this.turn
= move.turn
[0];
193 this.subTurn
= parseInt(move.turn
[1]);
194 this.unupdateVariables(move);
197 // NOTE: GenRandInitFen() is OK,
198 // since at first move turn indicator is just "w"
207 'q': 7, //slightly less than in orthodox game
212 // No alpha-beta here, just adapted min-max at depth 2(+1)
215 if (this.subTurn
== 2)
216 return null; //TODO: imperfect interface setup
218 const maxeval
= V
.INFINITY
;
219 const color
= this.turn
;
220 const oppCol
= V
.GetOppCol(this.turn
);
222 // Search best (half) move for opponent turn
223 const getBestMoveEval
= () => {
224 const turnBefore
= this.turn
+ this.subTurn
;
225 let score
= this.getCurrentScore();
230 return maxeval
* (score
== "1-0" ? 1 : -1);
232 let moves
= this.getAllValidMoves();
233 let res
= (oppCol
== "w" ? -maxeval : maxeval
);
237 score
= this.getCurrentScore();
238 // Now turn is oppCol,2 if m doesn't give check
239 // Otherwise it's color,1. In both cases the next test makes sense
243 res
= (oppCol
== "w" ? Math
.max(res
, 0) : Math
.min(res
, 0));
248 return maxeval
* (score
== "1-0" ? 1 : -1);
251 const evalPos
= this.evalPosition();
252 res
= (oppCol
== "w" ? Math
.max(res
, evalPos
) : Math
.min(res
, evalPos
));
258 let moves11
= this.getAllValidMoves();
259 let doubleMoves
= [];
260 // Rank moves using a min-max at depth 2
261 for (let i
=0; i
<moves11
.length
; i
++)
263 this.play(moves11
[i
]);
264 if (this.turn
!= color
)
266 // We gave check with last move: search the best opponent move
267 doubleMoves
.push({moves:[moves11
[i
]], eval:getBestMoveEval()});
271 let moves12
= this.getAllValidMoves();
272 for (let j
=0; j
<moves12
.length
; j
++)
274 this.play(moves12
[j
]);
276 moves:[moves11
[i
],moves12
[j
]],
277 eval:getBestMoveEval()});
278 this.undo(moves12
[j
]);
281 this.undo(moves11
[i
]);
284 doubleMoves
.sort( (a
,b
) => {
285 return (color
=="w" ? 1 : -1) * (b
.eval
- a
.eval
); });
286 let candidates
= [0]; //indices of candidates moves
288 i
<doubleMoves
.length
&& doubleMoves
[i
].eval
== doubleMoves
[0].eval
;
294 const selected
= doubleMoves
[randInt(candidates
.length
)].moves
;
295 if (selected
.length
== 1)