1 import { ChessRules
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class FootballRules
extends ChessRules
{
6 static get HasEnpassant() {
10 static get HasFlags() {
15 return { x: 9, y: 9 };
32 // 'b' is already taken:
36 // Check that exactly one ball is on the board
37 // + at least one piece per color.
38 static IsGoodPosition(position
) {
39 if (position
.length
== 0) return false;
40 const rows
= position
.split("/");
41 if (rows
.length
!= V
.size
.x
) return false;
42 let pieces
= { "w": 0, "b": 0 };
44 for (let row
of rows
) {
46 for (let i
= 0; i
< row
.length
; i
++) {
47 const lowerRi
= row
[i
].toLowerCase();
48 if (!!lowerRi
.match(/^[a-z]$/)) {
49 if (V
.PIECES
.includes(lowerRi
))
50 pieces
[row
[i
] == lowerRi
? "b" : "w"]++;
51 else if (lowerRi
== 'a') ballCount
++;
56 const num
= parseInt(row
[i
], 10);
57 if (isNaN(num
)) return false;
61 if (sumElts
!= V
.size
.y
) return false;
63 if (ballCount
!= 1 || Object
.values(pieces
).some(v
=> v
== 0))
69 if (b
== V
.BALL
) return 'a';
70 return ChessRules
.board2fen(b
);
74 if (f
== 'a') return V
.BALL
;
75 return ChessRules
.fen2board(f
);
79 if (b
== V
.BALL
) return "Football/ball";
83 canIplay(side
, [x
, y
]) {
86 (this.board
[x
][y
] == V
.BALL
|| this.getColor(x
, y
) == side
)
90 // No checks or king tracking etc. But, track ball
92 // Stack of "kicked by" coordinates, to avoid infinite loops
93 this.kickedBy
= [ {} ];
95 this.ballPos
= [-1, -1];
96 for (let i
=0; i
< V
.size
.x
; i
++) {
97 for (let j
=0; j
< V
.size
.y
; j
++) {
98 if (this.board
[i
][j
] == V
.BALL
) {
99 this.ballPos
= [i
, j
];
106 static GenRandInitFen(randomness
) {
108 return "rnbq1knbr/9/9/9/4a4/9/9/9/RNBQ1KNBR w 0";
110 // TODO: following is mostly copy-paste from Suicide variant
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 const rem2
= positions
[0] % 2;
122 if (rem2
== positions
[1] % 2) {
123 // Fix bishops (on different colors)
124 for (let i
=2; i
<8; i
++) {
125 if (positions
[i
] % 2 != rem2
)
126 [positions
[1], positions
[i
]] = [positions
[i
], positions
[1]];
129 for (let i
= 0; i
< 8; i
++) pieces
[c
][positions
[i
]] = composition
[i
];
131 const piecesB
= pieces
["b"].join("") ;
132 const piecesW
= pieces
["w"].join("").toUpperCase();
134 piecesB
.substr(0, 4) + "1" + piecesB
.substr(4) +
135 "/9/9/9/4a4/9/9/9/" +
136 piecesW
.substr(0, 4) + "1" + piecesW
.substr(4) +
141 tryKickFrom([x
, y
]) {
142 const bp
= this.ballPos
;
143 const emptySquare
= (i
, j
) => {
144 return V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
;
146 // Kick the (adjacent) ball from x, y with current turn:
147 const step
= [bp
[0] - x
, bp
[1] - y
];
148 const piece
= this.getPiece(x
, y
);
150 if (piece
== V
.KNIGHT
) {
151 // The knight case is particular
152 V
.steps
[V
.KNIGHT
].forEach(s
=> {
153 const [i
, j
] = [bp
[0] + s
[0], bp
[1] + s
[1]];
156 this.board
[i
][j
] == V
.EMPTY
&&
158 // In a corner? The, allow all ball moves
159 ([0, 8].includes(bp
[0]) && [0, 8].includes(bp
[1])) ||
160 // Do not end near the knight
161 (Math
.abs(i
- x
) >= 2 || Math
.abs(j
- y
) >= 2)
164 moves
.push(super.getBasicMove(bp
, [i
, j
]));
169 let compatible
= false,
173 compatible
= (step
[0] == 0 || step
[1] == 0);
176 compatible
= (step
[0] != 0 && step
[1] != 0);
186 if (!compatible
) return [];
187 let [i
, j
] = [bp
[0] + step
[0], bp
[1] + step
[1]];
188 const horizontalStepOnGoalRow
=
189 ([0, 8].includes(bp
[0]) && step
.some(s
=> s
== 0));
190 if (emptySquare(i
, j
) && (!horizontalStepOnGoalRow
|| j
!= 4)) {
191 moves
.push(super.getBasicMove(bp
, [i
, j
]));
196 if (!emptySquare(i
, j
)) break;
197 if (!horizontalStepOnGoalRow
|| j
!= 4)
198 moves
.push(super.getBasicMove(bp
, [i
, j
]));
203 const kickedFrom
= x
+ "-" + y
;
204 moves
.forEach(m
=> m
.by
= kickedFrom
)
208 getPotentialMovesFrom([x
, y
], computer
) {
209 if (V
.PIECES
.includes(this.getPiece(x
, y
))) {
210 if (this.subTurn
> 1) return [];
212 super.getPotentialMovesFrom([x
, y
])
213 .filter(m
=> m
.end
.y
!= 4 || ![0, 8].includes(m
.end
.x
))
216 // Kicking the ball: look for adjacent pieces.
217 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
220 for (let s
of steps
) {
221 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
224 this.board
[i
][j
] != V
.EMPTY
&&
225 this.getColor(i
, j
) == c
227 Array
.prototype.push
.apply(moves
, this.tryKickFrom([i
, j
]));
230 // And, always add the "end" move. For computer, keep only one
231 outerLoop: for (let i
=0; i
< V
.size
.x
; i
++) {
232 for (let j
=0; j
< V
.size
.y
; j
++) {
233 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == c
) {
234 moves
.push(super.getBasicMove([x
, y
], [i
, j
]));
235 if (!!computer
) break outerLoop
;
243 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
245 outerLoop: for (let step
of steps
) {
249 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
250 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
251 if (!!oneStep
) continue outerLoop
;
260 // Extra arg "computer" to avoid trimming all redundant pass moves:
261 getAllPotentialMoves(computer
) {
262 const color
= this.turn
;
263 let potentialMoves
= [];
264 for (let i
= 0; i
< V
.size
.x
; i
++) {
265 for (let j
= 0; j
< V
.size
.y
; j
++) {
266 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
267 Array
.prototype.push
.apply(
269 this.getPotentialMovesFrom([i
, j
], computer
)
274 return potentialMoves
;
278 return this.filterValid(this.getAllPotentialMoves("computer"));
282 const L
= this.kickedBy
.length
;
283 const kb
= this.kickedBy
[L
-1];
284 return moves
.filter(m
=> !m
.by
|| !kb
[m
.by
]);
291 allowAnotherPass(color
) {
292 // Two cases: a piece moved, or the ball moved.
293 // In both cases, check our pieces and ball proximity,
294 // so the move played doesn't matter (if ball position updated)
295 const bp
= this.ballPos
;
296 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
297 for (let s
of steps
) {
298 const [i
, j
] = [this.ballPos
[0] + s
[0], this.ballPos
[1] + s
[1]];
301 this.board
[i
][j
] != V
.EMPTY
&&
302 this.getColor(i
, j
) == color
304 return true; //potentially...
311 if (move.appear
[0].p
== 'a')
312 this.ballPos
= [move.appear
[0].x
, move.appear
[0].y
];
316 // Special message saying "passes are over"
317 const passesOver
= (move.vanish
.length
== 2);
320 V
.PlayOnBoard(this.board
, move);
322 move.turn
= [this.turn
, this.subTurn
]; //easier undo
323 if (passesOver
|| !this.allowAnotherPass(this.turn
)) {
324 this.turn
= V
.GetOppCol(this.turn
);
327 this.kickedBy
.push( {} );
332 const L
= this.kickedBy
.length
;
333 this.kickedBy
[L
-1][move.by
] = true;
339 const passesOver
= (move.vanish
.length
== 2);
340 if (move.turn
[0] != this.turn
) {
341 [this.turn
, this.subTurn
] = move.turn
;
348 const L
= this.kickedBy
.length
;
349 delete this.kickedBy
[L
-1][move.by
];
353 V
.UndoOnBoard(this.board
, move);
359 if (move.vanish
[0].p
== 'a')
360 this.ballPos
= [move.vanish
[0].x
, move.vanish
[0].y
];
364 if (this.board
[0][4] == V
.BALL
) return "1-0";
365 if (this.board
[8][4] == V
.BALL
) return "0-1";
370 let initMoves
= this.getAllValidMoves();
371 if (initMoves
.length
== 0) return null;
372 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
375 // Just play random moves (for now at least. TODO?)
377 while (moves
.length
> 0) {
378 mv
= moves
[randInt(moves
.length
)];
381 if (mv
.vanish
.length
== 1 && this.allowAnotherPass(c
))
383 moves
= this.getPotentialMovesFrom(this.ballPos
);
386 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
387 return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
390 // NOTE: evalPosition() is wrong, but unused since bot plays at random
393 if (move.vanish
.length
== 2) return "pass";
394 if (move.vanish
[0].p
!= 'a') return super.getNotation(move);
395 // Kick: simple notation (TODO?)
396 return V
.CoordsToSquare(move.end
);