2410f0a6ddb26f5eb14f46eede9810f2d240c901
1 import { ChessRules
} from "@/base_rules";
2 import { randInt
, shuffle
} from "@/utils/alea";
3 import { ArrayFun
} from "@/utils/array";
5 export class FootballRules
extends ChessRules
{
7 static get HasEnpassant() {
11 static get HasFlags() {
16 return { x: 9, y: 9 };
33 // 'b' is already taken:
37 // Check that exactly one ball is on the board
38 // + at least one piece per color.
39 static IsGoodPosition(position
) {
40 if (position
.length
== 0) return false;
41 const rows
= position
.split("/");
42 if (rows
.length
!= V
.size
.x
) return false;
43 let pieces
= { "w": 0, "b": 0 };
45 for (let row
of rows
) {
47 for (let i
= 0; i
< row
.length
; i
++) {
48 const lowerRi
= row
[i
].toLowerCase();
49 if (!!lowerRi
.match(/^[a-z]$/)) {
50 if (V
.PIECES
.includes(lowerRi
))
51 pieces
[row
[i
] == lowerRi
? "b" : "w"]++;
52 else if (lowerRi
== 'a') ballCount
++;
57 const num
= parseInt(row
[i
], 10);
58 if (isNaN(num
)) return false;
62 if (sumElts
!= V
.size
.y
) return false;
64 if (ballCount
!= 1 || Object
.values(pieces
).some(v
=> v
== 0))
70 if (b
== V
.BALL
) return 'a';
71 return ChessRules
.board2fen(b
);
75 if (f
== 'a') return V
.BALL
;
76 return ChessRules
.fen2board(f
);
80 if (b
== V
.BALL
) return "Football/ball";
84 canIplay(side
, [x
, y
]) {
87 (this.board
[x
][y
] == V
.BALL
|| this.getColor(x
, y
) == side
)
91 // No checks or king tracking etc. But, track ball
93 // Stack of "kicked by" coordinates, to avoid infinite loops
94 this.kickedBy
= [ {} ];
96 this.ballPos
= [-1, -1];
97 for (let i
=0; i
< V
.size
.x
; i
++) {
98 for (let j
=0; j
< V
.size
.y
; j
++) {
99 if (this.board
[i
][j
] == V
.BALL
) {
100 this.ballPos
= [i
, j
];
107 static GenRandInitFen(randomness
) {
109 return "rnbq1knbr/9/9/9/4a4/9/9/9/RNBQ1KNBR w 0";
111 let pieces
= { w: new Array(8), b: new Array(8) };
112 for (let c
of ["w", "b"]) {
113 if (c
== 'b' && randomness
== 1) {
114 pieces
['b'] = pieces
['w'];
118 // Get random squares for every piece, totally freely
119 let positions
= shuffle(ArrayFun
.range(8));
120 const composition
= ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
121 // Fix bishops (on different colors)
123 (pos
) => { return (pos
<= 3 ? pos
% 2 : (pos
+ 1) % 2); };
124 const rem2
= realOddity(positions
[0]);
125 if (rem2
== realOddity(positions
[1])) {
126 for (let i
=2; i
<8; i
++) {
127 if (realOddity(positions
[i
]) != rem2
) {
128 [positions
[1], positions
[i
]] = [positions
[i
], positions
[1]];
133 for (let i
= 0; i
< 8; i
++) pieces
[c
][positions
[i
]] = composition
[i
];
135 const piecesB
= pieces
["b"].join("") ;
136 const piecesW
= pieces
["w"].join("").toUpperCase();
138 piecesB
.substr(0, 4) + "1" + piecesB
.substr(4) +
139 "/9/9/9/4a4/9/9/9/" +
140 piecesW
.substr(0, 4) + "1" + piecesW
.substr(4) +
145 tryKickFrom([x
, y
]) {
146 const bp
= this.ballPos
;
147 const emptySquare
= (i
, j
) => {
148 return V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
;
150 // Kick the (adjacent) ball from x, y with current turn:
151 const step
= [bp
[0] - x
, bp
[1] - y
];
152 const piece
= this.getPiece(x
, y
);
154 if (piece
== V
.KNIGHT
) {
155 // The knight case is particular
156 V
.steps
[V
.KNIGHT
].forEach(s
=> {
157 const [i
, j
] = [bp
[0] + s
[0], bp
[1] + s
[1]];
160 this.board
[i
][j
] == V
.EMPTY
&&
162 // In a corner? The, allow all ball moves
163 ([0, 8].includes(bp
[0]) && [0, 8].includes(bp
[1])) ||
164 // Do not end near the knight
165 (Math
.abs(i
- x
) >= 2 || Math
.abs(j
- y
) >= 2)
168 moves
.push(super.getBasicMove(bp
, [i
, j
]));
173 let compatible
= false,
177 compatible
= (step
[0] == 0 || step
[1] == 0);
180 compatible
= (step
[0] != 0 && step
[1] != 0);
190 if (!compatible
) return [];
191 let [i
, j
] = [bp
[0] + step
[0], bp
[1] + step
[1]];
192 const horizontalStepOnGoalRow
=
193 ([0, 8].includes(bp
[0]) && step
.some(s
=> s
== 0));
194 if (emptySquare(i
, j
) && (!horizontalStepOnGoalRow
|| j
!= 4)) {
195 moves
.push(super.getBasicMove(bp
, [i
, j
]));
200 if (!emptySquare(i
, j
)) break;
201 if (!horizontalStepOnGoalRow
|| j
!= 4)
202 moves
.push(super.getBasicMove(bp
, [i
, j
]));
207 const kickedFrom
= x
+ "-" + y
;
208 moves
.forEach(m
=> m
.by
= kickedFrom
)
212 getPotentialMovesFrom([x
, y
], computer
) {
213 if (V
.PIECES
.includes(this.getPiece(x
, y
))) {
214 if (this.subTurn
> 1) return [];
216 super.getPotentialMovesFrom([x
, y
])
217 .filter(m
=> m
.end
.y
!= 4 || ![0, 8].includes(m
.end
.x
))
220 // Kicking the ball: look for adjacent pieces.
221 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
224 for (let s
of steps
) {
225 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
228 this.board
[i
][j
] != V
.EMPTY
&&
229 this.getColor(i
, j
) == c
231 Array
.prototype.push
.apply(moves
, this.tryKickFrom([i
, j
]));
234 // And, always add the "end" move. For computer, keep only one
235 outerLoop: for (let i
=0; i
< V
.size
.x
; i
++) {
236 for (let j
=0; j
< V
.size
.y
; j
++) {
237 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == c
) {
238 moves
.push(super.getBasicMove([x
, y
], [i
, j
]));
239 if (!!computer
) break outerLoop
;
247 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
249 outerLoop: for (let step
of steps
) {
253 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
254 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
255 if (!!oneStep
) continue outerLoop
;
264 // Extra arg "computer" to avoid trimming all redundant pass moves:
265 getAllPotentialMoves(computer
) {
266 const color
= this.turn
;
267 let potentialMoves
= [];
268 for (let i
= 0; i
< V
.size
.x
; i
++) {
269 for (let j
= 0; j
< V
.size
.y
; j
++) {
270 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
271 Array
.prototype.push
.apply(
273 this.getPotentialMovesFrom([i
, j
], computer
)
278 return potentialMoves
;
282 return this.filterValid(this.getAllPotentialMoves("computer"));
286 const L
= this.kickedBy
.length
;
287 const kb
= this.kickedBy
[L
-1];
288 return moves
.filter(m
=> !m
.by
|| !kb
[m
.by
]);
295 allowAnotherPass(color
) {
296 // Two cases: a piece moved, or the ball moved.
297 // In both cases, check our pieces and ball proximity,
298 // so the move played doesn't matter (if ball position updated)
299 const bp
= this.ballPos
;
300 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
301 for (let s
of steps
) {
302 const [i
, j
] = [this.ballPos
[0] + s
[0], this.ballPos
[1] + s
[1]];
305 this.board
[i
][j
] != V
.EMPTY
&&
306 this.getColor(i
, j
) == color
308 return true; //potentially...
315 if (move.appear
[0].p
== 'a')
316 this.ballPos
= [move.appear
[0].x
, move.appear
[0].y
];
320 // Special message saying "passes are over"
321 const passesOver
= (move.vanish
.length
== 2);
324 V
.PlayOnBoard(this.board
, move);
326 move.turn
= [this.turn
, this.subTurn
]; //easier undo
327 if (passesOver
|| !this.allowAnotherPass(this.turn
)) {
328 this.turn
= V
.GetOppCol(this.turn
);
331 this.kickedBy
.push( {} );
336 const L
= this.kickedBy
.length
;
337 this.kickedBy
[L
-1][move.by
] = true;
343 const passesOver
= (move.vanish
.length
== 2);
344 if (move.turn
[0] != this.turn
) {
345 [this.turn
, this.subTurn
] = move.turn
;
352 const L
= this.kickedBy
.length
;
353 delete this.kickedBy
[L
-1][move.by
];
357 V
.UndoOnBoard(this.board
, move);
363 if (move.vanish
[0].p
== 'a')
364 this.ballPos
= [move.vanish
[0].x
, move.vanish
[0].y
];
368 if (this.board
[0][4] == V
.BALL
) return "1-0";
369 if (this.board
[8][4] == V
.BALL
) return "0-1";
374 let initMoves
= this.getAllValidMoves();
375 if (initMoves
.length
== 0) return null;
376 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
379 // Just play random moves (for now at least. TODO?)
381 while (moves
.length
> 0) {
382 mv
= moves
[randInt(moves
.length
)];
385 if (mv
.vanish
.length
== 1 && this.allowAnotherPass(c
))
387 moves
= this.getPotentialMovesFrom(this.ballPos
);
390 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
391 return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
394 // NOTE: evalPosition() is wrong, but unused since bot plays at random
397 if (move.vanish
.length
== 2) return "pass";
398 if (move.vanish
[0].p
!= 'a') return super.getNotation(move);
399 // Kick: simple notation (TODO?)
400 return V
.CoordsToSquare(move.end
);