1 import { ChessRules
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export const VariantRules
= class MarseilleRules
extends ChessRules
{
5 static IsGoodEnpassant(enpassant
) {
6 if (enpassant
!= "-") {
7 const squares
= enpassant
.split(",");
8 if (squares
.length
> 2) return false;
9 for (let sq
of squares
) {
10 const ep
= V
.SquareToCoords(sq
);
11 if (isNaN(ep
.x
) || !V
.OnBoard(ep
)) return false;
18 return this.turn
+ this.subTurn
;
21 // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
23 const L
= this.epSquares
.length
;
24 if (this.epSquares
[L
- 1].every(epsq
=> epsq
=== undefined)) return "-"; //no en-passant
26 this.epSquares
[L
- 1].forEach(epsq
=> {
27 if (epsq
) res
+= V
.CoordsToSquare(epsq
) + ",";
29 return res
.slice(0, -1); //remove last comma
32 setOtherVariables(fen
) {
33 const parsedFen
= V
.ParseFen(fen
);
34 this.setFlags(parsedFen
.flags
);
35 if (parsedFen
.enpassant
== "-") this.epSquares
= [[undefined]];
38 const squares
= parsedFen
.enpassant
.split(",");
39 for (let sq
of squares
) res
.push(V
.SquareToCoords(sq
));
40 this.epSquares
= [res
];
42 this.scanKingsRooks(fen
);
43 // Extract subTurn from turn indicator: "w" (first move), or
44 // "w1" or "w2" white subturn 1 or 2, and same for black
45 const fullTurn
= V
.ParseFen(fen
).turn
;
46 this.turn
= fullTurn
[0];
47 this.subTurn
= fullTurn
[1] || 0; //"w0" = special code for first move in game
50 getPotentialPawnMoves([x
, y
]) {
51 const color
= this.turn
;
53 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
54 const shiftX
= color
== "w" ? -1 : 1;
55 const firstRank
= color
== "w" ? sizeX
- 1 : 0;
56 const startRank
= color
== "w" ? sizeX
- 2 : 1;
57 const lastRank
= color
== "w" ? 0 : sizeX
- 1;
59 x
+ shiftX
== lastRank
? [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
] : [V
.PAWN
];
62 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
) {
63 for (let piece
of finalPieces
) {
65 this.getBasicMove([x
, y
], [x
+ shiftX
, y
], { c: color
, p: piece
})
68 // Next condition because pawns on 1st rank can generally jump
70 [startRank
, firstRank
].includes(x
) &&
71 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
74 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
78 for (let shiftY
of [-1, 1]) {
82 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
83 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
85 for (let piece
of finalPieces
) {
87 this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
], {
96 // En passant: always OK if subturn 1,
97 // OK on subturn 2 only if enPassant was played at subturn 1
98 // (and if there are two e.p. squares available).
99 const Lep
= this.epSquares
.length
;
100 const epSquares
= this.epSquares
[Lep
- 1]; //always at least one element
102 epSquares
.forEach(sq
=> {
103 if (sq
) epSqs
.push(sq
);
105 if (epSqs
.length
== 0) return moves
;
106 const oppCol
= V
.GetOppCol(color
);
107 for (let sq
of epSqs
) {
110 (epSqs
.length
== 2 &&
111 // Was this en-passant capture already played at subturn 1 ?
112 // (Or maybe the opponent filled the en-passant square with a piece)
113 this.board
[epSqs
[0].x
][epSqs
[0].y
] != V
.EMPTY
)
116 sq
.x
== x
+ shiftX
&&
117 Math
.abs(sq
.y
- y
) == 1 &&
118 // Add condition "enemy pawn must be present"
119 this.getPiece(x
, sq
.y
) == V
.PAWN
&&
120 this.getColor(x
, sq
.y
) == oppCol
122 let epMove
= this.getBasicMove([x
, y
], [sq
.x
, sq
.y
]);
138 move.flags
= JSON
.stringify(this.aggregateFlags());
139 move.turn
= this.turn
+ this.subTurn
;
140 V
.PlayOnBoard(this.board
, move);
141 const epSq
= this.getEpSquare(move);
142 if (this.subTurn
== 0) {
146 this.epSquares
.push([epSq
]);
148 // Does this move give check on subturn 1? If yes, skip subturn 2
149 else if (this.subTurn
== 1 && this.underCheck(V
.GetOppCol(this.turn
))) {
150 this.turn
= V
.GetOppCol(this.turn
);
151 this.epSquares
.push([epSq
]);
152 move.checkOnSubturn1
= true;
154 if (this.subTurn
== 2) {
155 this.turn
= V
.GetOppCol(this.turn
);
156 let lastEpsq
= this.epSquares
[this.epSquares
.length
- 1];
158 } else this.epSquares
.push([epSq
]);
159 this.subTurn
= 3 - this.subTurn
;
161 this.updateVariables(move);
165 this.disaggregateFlags(JSON
.parse(move.flags
));
166 V
.UndoOnBoard(this.board
, move);
167 if (move.turn
[1] == "0" || move.checkOnSubturn1
|| this.subTurn
== 2)
168 this.epSquares
.pop();
171 let lastEpsq
= this.epSquares
[this.epSquares
.length
- 1];
174 this.turn
= move.turn
[0];
175 this.subTurn
= parseInt(move.turn
[1]);
176 this.unupdateVariables(move);
179 // NOTE: GenRandInitFen() is OK,
180 // since at first move turn indicator is just "w"
182 static get VALUES() {
188 q: 7, //slightly less than in orthodox game
193 // No alpha-beta here, just adapted min-max at depth 2(+1)
195 if (this.subTurn
== 2) return null; //TODO: imperfect interface setup
197 const maxeval
= V
.INFINITY
;
198 const color
= this.turn
;
199 const oppCol
= V
.GetOppCol(this.turn
);
201 // Search best (half) move for opponent turn
202 const getBestMoveEval
= () => {
203 let score
= this.getCurrentScore();
205 if (score
== "1/2") return 0;
206 return maxeval
* (score
== "1-0" ? 1 : -1);
208 let moves
= this.getAllValidMoves();
209 let res
= oppCol
== "w" ? -maxeval : maxeval
;
210 for (let m
of moves
) {
212 score
= this.getCurrentScore();
213 // Now turn is oppCol,2 if m doesn't give check
214 // Otherwise it's color,1. In both cases the next test makes sense
217 res
= oppCol
== "w" ? Math
.max(res
, 0) : Math
.min(res
, 0);
221 return maxeval
* (score
== "1-0" ? 1 : -1);
224 const evalPos
= this.evalPosition();
225 res
= oppCol
== "w" ? Math
.max(res
, evalPos
) : Math
.min(res
, evalPos
);
231 let moves11
= this.getAllValidMoves();
232 let doubleMoves
= [];
233 // Rank moves using a min-max at depth 2
234 for (let i
= 0; i
< moves11
.length
; i
++) {
235 this.play(moves11
[i
]);
236 if (this.turn
!= color
) {
237 // We gave check with last move: search the best opponent move
238 doubleMoves
.push({ moves: [moves11
[i
]], eval: getBestMoveEval() });
240 let moves12
= this.getAllValidMoves();
241 for (let j
= 0; j
< moves12
.length
; j
++) {
242 this.play(moves12
[j
]);
244 moves: [moves11
[i
], moves12
[j
]],
245 eval: getBestMoveEval()
247 this.undo(moves12
[j
]);
250 this.undo(moves11
[i
]);
253 doubleMoves
.sort((a
, b
) => {
254 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
256 let candidates
= [0]; //indices of candidates moves
259 i
< doubleMoves
.length
&& doubleMoves
[i
].eval
== doubleMoves
[0].eval
;
265 const selected
= doubleMoves
[randInt(candidates
.length
)].moves
;
266 if (selected
.length
== 1) return selected
[0];