Is this Chessboard Reachable?
The goal of this challenge is to determine, given the state of a
chessboard, whether or not that chessboard can actually be reached in
the course of standard play. Of course, doing this in general is a
rather hard problem, so we'll be simplifying the problem to a set of a
few rules which should approximate the "reachability" constraint.
Your input will be a chessboard, specifying what pieces are at what
positions on the 8x8 board. At each position, there can be either
nothing or a piece. If there is a piece, it is either a pawn, bishop,
knight, rook, queen, or king, and it is either white or black. Input
can be taken in any reasonable form. Your output should be truthy or
falsy, indicating whether all of the below rules are satisfied.
For the below rules, I'll be using standard chess notation to refer to
the squares on the board. That is, I'll be referring to squares on the
board by their rank (1-8) and their file (a-h), as such
8........
7........
6........
5........
4........
3........
2........
1........
abcdefgh
where the white player starts on ranks 1-2 and the black player starts
on ranks 7-8. Obviously, you don't have to use the same notation, and
if it's easier for you to take the board input flipped or rotated,
that's fine too as long as you specify it in your answer.
For one of the rules, you have to distinguish between white and black
squares on the board. The board is layered with a checkerboard
pattern, so white squares are always immediately surrounded by black
on all four sides, and vice versa. In typical chess, the a1 square is
black, but that doesn't really matter for the below criteria.
The Rules
In order for a board to be considered reachable, it must satisfy all
of the following rules. This is decision-problem, so you don't have
to tell me which rule an unreachable board violated; all I expect of
your output is a "yes" or a "no".
White and black each have exactly one king on the board: no more,
no less.
Pawns cannot appear on rank 1 or rank 8.
Each player has a maximum of 16 pieces on the board total. These
pieces must be a subset of the following: 2 bishops, 2 rooks, 2
knights, 1 queen, 1 king, and 8 wildcards. The "wildcard" pieces
can be any piece they please (since we assume pawns could have been
promoted).
For either player, if that player has at least two bishops, and
those two bishops cannot have been promoted from pawns (i.e. they
must be the "bishops" in rule 3, not the "wildcards"), then that
player must have at least one bishop on a white square and at least
one bishop on a black square.
All of a player's pawns must be able to reach the square they're
occupying. More formally, for each player, there must be an
assignment (an injective function) from the set of that player's
surviving pawns to the files (a-h) they started on, such that each
pawn can reach its current position from its starting position with
only forward and forward-diagonal movements.
Pawn Movements
Rule 5 may require some elaboration. Suppose a white pawn is on d5. Then it could have come from the following
places (indicated by X
)
8........
7........
6........
5...♙....
4..XXX...
3.XXXXX..
2XXXXXXX.
1........
abcdefgh
So it could have started on a2, b2, c2, d2, e2, f2, or g2, but not h2.
There must be an assignment of pawns to starting positions such that
no two pawns started at the same position and every pawn can reach its
current position from where it began. A black pawn follows the same
rules but started on rank 7 and moves down rather than up. So a black
pawn at the same position could have come from b7, c7, d7, e7, or f7,
as follows.
8........
7.XXXXX..
6..XXX...
5...♟....
4........
3........
2........
1........
abcdefgh
Notes
- Only the rules above apply. Other complexities of a standard game
of chess (in particular, castling or en passant) are not part of
this problem and should not be considered.
- This is code-golf, so the shortest solution wins.
- Input can be taken in whatever form is most convenient. Output
follows the usual decision-problem rules, so any two distinct
outputs for truthy/falsy are acceptable.
- This is an oversimplification of the reachability problem in chess.
As such, an answer which provably enumerates every chessboard and
tests for membership is not correct.
Examples
Reachable chessboards (true):
8♜♞♝♛♚♝♞♜
7♟♟♟♟♟♟♟♟
6........
5........
4........
3........
2♙♙♙♙♙♙♙♙
1♖♘♗♕♔♗♘♖
abcdefgh
8........
7........
6........
5........
4........
3........
2..♚.....
1.....♔..
abcdefgh
8........
7.....♚..
6.♛......
5...♟....
4........
3.....♕..
2........
1...♔....
abcdefgh
8.....♚..
7♟♟♟♟♟♟♟♟
6........
5........
4........
3........
2♙♙♙♙♙♙♙♙
1....♔...
abcdefgh
8.....♚...
7♟♟♟♟♟♟♟♟
6........
5........
4........
3........
2♙♙♙♙♙♙♙♙
1....♔...
abcdefgh
8........
7.♚......
6...♕♕♕♕.
5..♕....♕
4..♕..♔.♕
3.......♕
2........
1........
abcdefgh
8........
7...♚.♟.♙
6.♙.♙..♙.
5.....♘..
4.♕..♕♘♗♖
3.....♘♗♖
2..♕....♖
1......♔.
abcdefgh
8♜..♛♚♜♜♜
7♟♟♟♟♟♟..
6........
5........
4........
3........
2♙♙♙♙♙♙♙♙
1♖♘♗♕♔♗♘♖
abcdefgh
8♜♞♝♛♚♝♞♜
7♟♟♟♟♟♟♟♟
6........
5........
4........
3........
2♙♙♙♙♙♙♙.
1♖♗♘♕♔♗♘♖
abcdefgh
8♛♛♛♛♛♛.♛
7........
6......♚.
5.♝.♝....
4.....♟..
3........
2..♖♖♖.♔.
1........
abcdefgh
8.....♚..
7........
6........
5........
4........
3.♙♙.....
2..♙♙♙♙♙♙
1.....♔..
abcdefgh
8.....♚..
7........
6........
5........
4........
3.....♙..
2...♙♙♙.♙
1.....♔..
abcdefgh
8.....♚..
7........
6.♟♟♟♟...
5.♟♟.....
4........
3........
2........
1.....♔..
abcdefgh
Unreachable chessboards (false):
(Rule 1: Not enough kings)
8........
7........
6........
5........
4........
3........
2........
1........
abcdefgh
(Rule 1: Too many kings)
8♚♚♚♚♚♚♚♚
7♚♚♚♚♚♚♚♚
6♚♚♚♚♚♚♚♚
5♚♚♚♚♚♚♚♚
4♚♚♚♚♚♚♚♚
3........
2........
1...♔....
abcdefgh
(Rule 2: Bad white pawn placement)
8.....♚..
7♟♟♟♟♟♟♟♟
6........
5........
4........
3........
2.♙♙♙♙♙♙♙
1♙...♔...
abcdefgh
(Rule 2: Bad black pawn placement)
8.♟...♚..
7♟.♟♟♟♟♟♟
6........
5........
4........
3........
2♙♙♙♙♙♙♙♙
1....♔...
abcdefgh
(Rule 3: Too many white queens)
8........
7.♚......
6...♕♕♕♕.
5..♕....♕
4..♕..♔.♕
3..♕....♕
2........
1........
abcdefgh
(Rule 3: Too many black pieces)
8♜♞♝♛♚♝♞♜
7♟♟♟♟♟♟♟♟
6......♟.
5........
4........
3........
2♙♙♙♙♙♙♙♙
1♖♘♗♕♔♗♘♖
abcdefgh
(Rule 3: Too many white pieces)
8........
7...♚.♙.♙
6.♙.♙..♙.
5.....♘..
4.♕..♕♘♗♖
3.....♘♗♖
2..♕....♖
1......♔.
abcdefgh
(Rule 3: Too many black rooks)
8♜..♛♚♜♜♜
7♟♟♟♟♟♟♟.
6........
5........
4........
3........
2♙♙♙♙♙♙♙♙
1♖♘♗♕♔♗♘♖
abcdefgh
(Rule 4: White bishops are the same color)
8♜♞♝♛♚♝♞♜
7♟♟♟♟♟♟♟♟
6........
5........
4........
3........
2♙♙♙♙♙♙♙♙
1♖♗♘♕♔♗♘♖
abcdefgh
(Rule 4: Black bishops are the same color)
8♛♛♛♛♛♛♛♛
7........
6......♚.
5.♝.♝....
4.....♟..
3........
2..♖♖♖.♔.
1........
abcdefgh
(Rule 5: White pawn placement is impossible)
8.....♚..
7........
6........
5........
4........
3..♙.....
2.♙♙♙♙♙♙♙
1.....♔..
abcdefgh
(Rule 5: White pawn placement is impossible)
8.....♚..
7........
6........
5........
4........
3.....♙.♙
2...♙♙♙.♙
1.....♔..
abcdefgh
(Rule 5: Black pawn placement is impossible)
8.....♚..
7.♟♟♟♟...
6........
5.♟♟.....
4.♟♟.....
3........
2........
1.....♔..
abcdefgh
Example Implementation (Python 3)
# Takes input from stdin in the form shown above (a grid of Unicode
# chess characters and dots). Prints True if reachable. Prints False
# and the first rule number which is violated if unreachable.
import sys
from collections import defaultdict, namedtuple
Piece = namedtuple('Piece', ['color', 'type'])
# Read in the data from stdin.
data = []
for line in sys.stdin:
chars = list(filter(lambda x: x in ".♟♙♝♗♞♘♜♖♛♕♚♔", line))
if chars:
data.append(chars)
if len(data) == 8:
break
assert len(data) == 8
for line in data:
assert len(line) == 8
# Parse it into a more convenient format.
translation = {
".": None,
"♟": Piece('black', 'pawn'),
"♙": Piece('white', 'pawn'),
"♝": Piece('black', 'bishop'),
"♗": Piece('white', 'bishop'),
"♞": Piece('black', 'knight'),
"♘": Piece('white', 'knight'),
"♜": Piece('black', 'rook'),
"♖": Piece('white', 'rook'),
"♛": Piece('black', 'queen'),
"♕": Piece('white', 'queen'),
"♚": Piece('black', 'king'),
"♔": Piece('white', 'king'),
}
for rank in data:
for i in range(8):
rank[i] = translation[rank[i]]
# Count the number of each piece that each player has, slotting
# necessary pawn promotions into their own category.
allowed = { 'bishop': 2, 'rook': 2, 'knight': 2, 'king': 999, 'queen': 1, 'pawn': 0 }
pieces = defaultdict(lambda: 0)
for rank in data:
for piece in rank:
if piece is None:
continue
if pieces[piece] >= allowed[piece.type]:
# Already have too many; it's a promoted pawn
pieces[Piece(piece.color, 'pawn')] += 1
else:
# Count it normally
pieces[piece] += 1
# Rule 1: Each color should have exactly one king.
if pieces[Piece('white', 'king')] != 1 or pieces[Piece('black', 'king')] != 1:
print(False, 1)
exit(0)
# Rule 2: Pawns cannot appear on rank 1 or rank 8.
for piece in data[0] + data[7]:
if piece is not None and piece.type == 'pawn':
print(False, 2)
exit(0)
# Rule 3: Since we already put any "overflow" pieces at the pawn key,
# we just need to make sure we have at most eight pawns.
for color in ['white', 'black']:
if pieces[Piece(color, 'pawn')] > 8:
print(False, 3)
exit(0)
# Rule 4: If we have both bishops and our pawns are all accounted for,
# then we have to have a bishop in each color.
for color in ['white', 'black']:
if pieces[Piece(color, 'bishop')] >= 2 and pieces[Piece(color, 'pawn')] >= 8:
squares = { 'white': False, 'black': False }
for y, rank in enumerate(data):
for x, piece in enumerate(rank):
square_color = 'white' if (x + y) % 2 == 0 else 'black'
if piece == Piece(color, 'bishop'):
squares[square_color] = True
if not (squares['white'] and squares['black']):
print(False, 4)
exit(0)
# Rule 5: All pawns must be able to get to where they are. I solve
# this here by brute force (simply trying every possible permutation),
# which is exponentially inefficient, but it'll do for this example.
def recursive_assign(taken, choices, i):
if i >= len(choices):
return True
current = choices[i]
for x in current:
if x not in taken:
if recursive_assign(taken + [x], choices, i + 1):
return True
return False
for color in ['white', 'black']:
starting_file = 6 if color == 'white' else 1
choices = []
for y, rank in enumerate(data):
for x, piece in enumerate(rank):
if piece == Piece(color, 'pawn'):
possibilities = range(8)
possibilities = filter(lambda i: abs(i - x) <= abs(y - starting_file), possibilities)
choices.append(list(possibilities))
if not recursive_assign([], choices, 0):
print(False, 5)
exit(0)
print(True)
Proposed Tags
Sandbox Concerns
- I worry Rules 4 and 5 are still not clear enough. I tried to write
them in a way that was as clear as possible while still being
mathematically unambiguous.