a6630584c059073ddad55672f40c6ce303f4b865
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 // TODO: following is mostly copy-paste from Suicide variant
112 let pieces
= { w: new Array(8), b: new Array(8) };
113 for (let c
of ["w", "b"]) {
114 if (c
== 'b' && randomness
== 1) {
115 pieces
['b'] = pieces
['w'];
119 // Get random squares for every piece, totally freely
120 let positions
= shuffle(ArrayFun
.range(8));
121 const composition
= ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
122 const rem2
= positions
[0] % 2;
123 if (rem2
== positions
[1] % 2) {
124 // Fix bishops (on different colors)
125 for (let i
=2; i
<8; i
++) {
126 if (positions
[i
] % 2 != rem2
)
127 [positions
[1], positions
[i
]] = [positions
[i
], positions
[1]];
130 for (let i
= 0; i
< 8; i
++) pieces
[c
][positions
[i
]] = composition
[i
];
132 const piecesB
= pieces
["b"].join("") ;
133 const piecesW
= pieces
["w"].join("").toUpperCase();
135 piecesB
.substr(0, 4) + "1" + piecesB
.substr(4) +
136 "/9/9/9/4a4/9/9/9/" +
137 piecesW
.substr(0, 4) + "1" + piecesW
.substr(4) +
142 tryKickFrom([x
, y
]) {
143 const bp
= this.ballPos
;
144 const emptySquare
= (i
, j
) => {
145 return V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
;
147 // Kick the (adjacent) ball from x, y with current turn:
148 const step
= [bp
[0] - x
, bp
[1] - y
];
149 const piece
= this.getPiece(x
, y
);
151 if (piece
== V
.KNIGHT
) {
152 // The knight case is particular
153 V
.steps
[V
.KNIGHT
].forEach(s
=> {
154 const [i
, j
] = [bp
[0] + s
[0], bp
[1] + s
[1]];
157 this.board
[i
][j
] == V
.EMPTY
&&
159 // In a corner? The, allow all ball moves
160 ([0, 8].includes(bp
[0]) && [0, 8].includes(bp
[1])) ||
161 // Do not end near the knight
162 (Math
.abs(i
- x
) >= 2 || Math
.abs(j
- y
) >= 2)
165 moves
.push(super.getBasicMove(bp
, [i
, j
]));
170 let compatible
= false,
174 compatible
= (step
[0] == 0 || step
[1] == 0);
177 compatible
= (step
[0] != 0 && step
[1] != 0);
187 if (!compatible
) return [];
188 let [i
, j
] = [bp
[0] + step
[0], bp
[1] + step
[1]];
189 const horizontalStepOnGoalRow
=
190 ([0, 8].includes(bp
[0]) && step
.some(s
=> s
== 0));
191 if (emptySquare(i
, j
) && (!horizontalStepOnGoalRow
|| j
!= 4)) {
192 moves
.push(super.getBasicMove(bp
, [i
, j
]));
197 if (!emptySquare(i
, j
)) break;
198 if (!horizontalStepOnGoalRow
|| j
!= 4)
199 moves
.push(super.getBasicMove(bp
, [i
, j
]));
204 const kickedFrom
= x
+ "-" + y
;
205 moves
.forEach(m
=> m
.by
= kickedFrom
)
209 getPotentialMovesFrom([x
, y
], computer
) {
210 if (V
.PIECES
.includes(this.getPiece(x
, y
))) {
211 if (this.subTurn
> 1) return [];
213 super.getPotentialMovesFrom([x
, y
])
214 .filter(m
=> m
.end
.y
!= 4 || ![0, 8].includes(m
.end
.x
))
217 // Kicking the ball: look for adjacent pieces.
218 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
221 for (let s
of steps
) {
222 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
225 this.board
[i
][j
] != V
.EMPTY
&&
226 this.getColor(i
, j
) == c
228 Array
.prototype.push
.apply(moves
, this.tryKickFrom([i
, j
]));
231 // And, always add the "end" move. For computer, keep only one
232 outerLoop: for (let i
=0; i
< V
.size
.x
; i
++) {
233 for (let j
=0; j
< V
.size
.y
; j
++) {
234 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == c
) {
235 moves
.push(super.getBasicMove([x
, y
], [i
, j
]));
236 if (!!computer
) break outerLoop
;
244 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
246 outerLoop: for (let step
of steps
) {
250 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
251 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
252 if (!!oneStep
) continue outerLoop
;
261 // Extra arg "computer" to avoid trimming all redundant pass moves:
262 getAllPotentialMoves(computer
) {
263 const color
= this.turn
;
264 let potentialMoves
= [];
265 for (let i
= 0; i
< V
.size
.x
; i
++) {
266 for (let j
= 0; j
< V
.size
.y
; j
++) {
267 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
268 Array
.prototype.push
.apply(
270 this.getPotentialMovesFrom([i
, j
], computer
)
275 return potentialMoves
;
279 return this.filterValid(this.getAllPotentialMoves("computer"));
283 const L
= this.kickedBy
.length
;
284 const kb
= this.kickedBy
[L
-1];
285 return moves
.filter(m
=> !m
.by
|| !kb
[m
.by
]);
292 allowAnotherPass(color
) {
293 // Two cases: a piece moved, or the ball moved.
294 // In both cases, check our pieces and ball proximity,
295 // so the move played doesn't matter (if ball position updated)
296 const bp
= this.ballPos
;
297 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
298 for (let s
of steps
) {
299 const [i
, j
] = [this.ballPos
[0] + s
[0], this.ballPos
[1] + s
[1]];
302 this.board
[i
][j
] != V
.EMPTY
&&
303 this.getColor(i
, j
) == color
305 return true; //potentially...
312 if (move.appear
[0].p
== 'a')
313 this.ballPos
= [move.appear
[0].x
, move.appear
[0].y
];
317 // Special message saying "passes are over"
318 const passesOver
= (move.vanish
.length
== 2);
321 V
.PlayOnBoard(this.board
, move);
323 move.turn
= [this.turn
, this.subTurn
]; //easier undo
324 if (passesOver
|| !this.allowAnotherPass(this.turn
)) {
325 this.turn
= V
.GetOppCol(this.turn
);
328 this.kickedBy
.push( {} );
333 const L
= this.kickedBy
.length
;
334 this.kickedBy
[L
-1][move.by
] = true;
340 const passesOver
= (move.vanish
.length
== 2);
341 if (move.turn
[0] != this.turn
) {
342 [this.turn
, this.subTurn
] = move.turn
;
349 const L
= this.kickedBy
.length
;
350 delete this.kickedBy
[L
-1][move.by
];
354 V
.UndoOnBoard(this.board
, move);
360 if (move.vanish
[0].p
== 'a')
361 this.ballPos
= [move.vanish
[0].x
, move.vanish
[0].y
];
365 if (this.board
[0][4] == V
.BALL
) return "1-0";
366 if (this.board
[8][4] == V
.BALL
) return "0-1";
371 let initMoves
= this.getAllValidMoves();
372 if (initMoves
.length
== 0) return null;
373 let moves
= JSON
.parse(JSON
.stringify(initMoves
));
376 // Just play random moves (for now at least. TODO?)
378 while (moves
.length
> 0) {
379 mv
= moves
[randInt(moves
.length
)];
382 if (mv
.vanish
.length
== 1 && this.allowAnotherPass(c
))
384 moves
= this.getPotentialMovesFrom(this.ballPos
);
387 for (let i
= mvArray
.length
- 1; i
>= 0; i
--) this.undo(mvArray
[i
]);
388 return (mvArray
.length
> 1 ? mvArray : mvArray
[0]);
391 // NOTE: evalPosition() is wrong, but unused since bot plays at random
394 if (move.vanish
.length
== 2) return "pass";
395 if (move.vanish
[0].p
!= 'a') return super.getNotation(move);
396 // Kick: simple notation (TODO?)
397 return V
.CoordsToSquare(move.end
);