5
\$\begingroup\$

This is an exercise in the Automate The Boring Stuff book. I am supposed to create a function that takes a dictionary argument that represents a chess board (ex: {'1h': 'bking', '6c': 'wqueen', '2g': 'bbishop', etc.}) and returns True or False depending on if the board is valid.

A valid board will have (A) exactly one black king and exactly one white king. (B) Each player can only have at most 16 pieces, (C) at most 8 pawns, and (D) all pieces must be on a valid space from '1a' to '8h'; that is, a piece can’t be on space '9z'. (E) The piece names begin with either a 'w' or 'b' to represent white or black, followed by 'pawn', 'knight', 'bishop', 'rook', 'queen', or 'king'. This function should detect when a bug has resulted in an improper chess board.

#Example dictionary of coordinates (keys) and pieces (values)
#my_board = {"1a":"wking", "2a":"bking", "8b":"wpawn", "3a":"wpawn"}

def isValidChessBoard(board):
    #Creating a dictionary of pieces (keys) and the number of pieces (values) 
    num_pieces = {}
    list_pieces = ["bpawn", "bking", "bqueen", "brook", "bbishop", "bknight", "wpawn", "wking", "wqueen", "wrook", "wbishop", "wknight"]
    for v in board.values():
        num_pieces.setdefault(v, 0)
        num_pieces[v] = num_pieces[v] + 1
    for i in list_pieces:
        if i not in num_pieces:
            num_pieces.setdefault(i, 0)
    #Making sure there is exactly one king on each side
    if (num_pieces["wking"] != 1) or (num_pieces["bking"] != 1):
        return False
    #Making sure there are no more than 8 pawns on each side
    if ("bpawn" or "wpawn" in num_pieces) and (num_pieces["bpawn"] or num_pieces["wpawn"]) > 8:
        return False
    #Making sure every piece is apprpriately named (b/w + king/queen/rook/pawn/knight/bishop)
    for j in num_pieces:
        if j not in list_pieces:
            return False
    #Making sure there are no more than 16 pieces on each side
    if num_pieces["bpawn"] + num_pieces["bking"]  + num_pieces["bqueen"] +  num_pieces["brook"] +  num_pieces["bbishop"]  + num_pieces["bknight"] > 16:
        return False
    if num_pieces["wpawn"] + num_pieces["wking"]  + num_pieces["wqueen"] +  num_pieces["wrook"] +  num_pieces["wbishop"]  + num_pieces["wknight"] > 16:
        return False
    #Making sure that the board coordinates are all between (a-h, and 1-8)
    in_range = 0
    possible_coordinates = []
    for i in range(1, 9):
        for j in ["a", "b", "c", "d", "e", "f", "g", "h"]:
            possible_coordinates.append(str(i)+str(j))
    for k in board.keys():
        if k in possible_coordinates:
            in_range = in_range + 1      
    if len(board) != in_range:
        return False
    return True

if isValidChessBoard(my_board) == True:
    print("Valid Board")
else:
    print("Invalid Board")

I know that the code works, but I was wondering if there were any faux-pas that I am implementing, or if there is anything I could obviously do to make it cleaner. Thanks!

\$\endgroup\$
2
  • 5
    \$\begingroup\$ There are much more restrictions. For example, if all 8 pawns are intact, the side cannot have more than 2 Knights, or more than 2 Rooks, etc. \$\endgroup\$
    – vnp
    Commented Dec 19, 2020 at 20:58
  • \$\begingroup\$ forgive me as i'm a noob at this but what does the line for i in list_pieces: if i not in num_pieces: num_pieces.setdefault(i, 0) mean? I understand what everything else means thanks \$\endgroup\$ Commented Feb 4, 2022 at 18:54

3 Answers 3

4
\$\begingroup\$

As you said the code works - there might be additional conditions for a valid board, that you are not checking for which you will be able to expand on using the same structure. I looked up the chapter from the book you mention and your implementation meets the requirements outlined there.

Since this is Python, there are more than one way to implement the requirements, at a minimum I'd recommend refactoring the code to be self documenting instead of relying on comments. This will require only minor refactoring of your code.

Then building on that may be consider taking a functional approach and utilizing python builtins. YMMV - here is my implementation, it is not perfect, but that is not the intent here.

def checkCriteria(pieces, fn, cnt):
    if len(list(filter(fn, pieces))) > cnt:
        return False
    
    return True

def checkValidPositions(input_pos):
    positions = set()
    for col in ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']:
        for row in range(1, 9):
            positions.add(str(row) + col)

    return all(pos in positions for pos in input_pos)

def checkMaxPiecesByColor(pieces):
    return all(checkCriteria(pieces, lambda piece: len(piece) > 0 and piece[0] == ch, 16) for ch in ['w', 'b'])

def checkKingCount(pieces):
    return all(checkCriteria(pieces, lambda piece: piece == king, 1) for king in ['bking', 'wking'])

def checkPawnCount(pieces):
    return all(checkCriteria(pieces, lambda piece: piece == pawn, 8) for pawn in ['bpawn', 'wpawn'])

def checkValidPieces(pieces):
    valid_pieces = set()
    for color in ['w', 'b']:
        for piece in ['pawn', 'knight', 'bishop', 'rook', 'queen', 'king']:
            valid_pieces.add(color + piece)

    return len(pieces - valid_pieces) == 0

def isValidChessBoard(board):
    pieces = board.values()
    positions = board.keys()

    if not checkValidPositions(positions):
        return False
        
    if not checkMaxPiecesByColor(pieces):
        return False
    
    if not checkKingCount(pieces):
        return False
        
    if not checkPawnCount(pieces):
        return False
    
    if not checkValidPieces(set(pieces) - set([''])):
        return False
    
    return True
\$\endgroup\$
4
\$\begingroup\$

This code is not correct

#Making sure there are no more than 8 pawns on each side
if ("bpawn" or "wpawn" in num_pieces) and (num_pieces["bpawn"] or num_pieces["wpawn"]) > 8:
    return False

For example, it fails if num_pieces = {"bpawn":2, "wpawn":11}.

("bpawn" or "wpawn" in num_pieces) evaluates to "bpawn".

(num_pieces["bpawn"] or num_pieces["wpawn"]) evaluates to 2.

"bpawn" and 2 > 8 evaluates to False, so the test passes even though num_pieces["wpawn"] is 11.

It should be something like:

if num_pieces.get("bpawn", 0) > 8 or num_pieces.get("wpawn", 0) > 8:
    return False
\$\endgroup\$
1
\$\begingroup\$

This code will not work if in the board multiple pieces have the same position.

For example, below board contains 2 white pawns in the same position "3a". Even though the board is an invalid board, the program will return True.

my_board = {"1a":"wking", "2a":"bking", "3a":"wpawn", "3a":"wpawn"}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.