2
\$\begingroup\$

First post, very new to programming. This is my solution for the chapter 5 project of Automate the Boring Stuff and I wondered if I am making any mistakes and is there a way to make it more efficient using what the book has taught thus far.

The project description:

In this chapter, we used the dictionary value {'1h': 'bking', '6c': 'wqueen', '2g': 'bbishop', '5h': 'bqueen', '3e': 'wking'} to represent a chess board. Write a function named isValidChessBoard() that takes a dictionary argument and returns True or False depending on if the board is valid. A valid board will have exactly one black king and exactly one white king. Each player can only have at most 16 pieces, at most 8 pawns, and all pieces must be on a valid space from '1a' to '8h'; that is, a piece can’t be on space '9z'. 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.

My code:

MyBoard = {'a8': 'bR', 'b8': 'bN', 'c8': 'bB', 'd8': 'bK', 'e8': 'bQ', 'f8': 'bB', 'g8': 'bN', 'h8': 'bR',
           'a7': 'bp', 'b7': 'bp', 'c7': 'bp', 'd7': 'bp', 'e7': 'bp', 'f7': 'bp', 'g7': 'bp', 'h7': 'bp',
           'a6': '', 'b6': '', 'c6': '', 'd6': '', 'e6': '', 'f6': '', 'g6': '', 'h6': '',
           'a5': '', 'b5': '', 'c5': '', 'd5': '', 'e5': '', 'f5': '', 'g5': '', 'h5': '',
           'a4': '', 'b4': '', 'c4': '', 'd4': '', 'e4': '', 'f4': '', 'g4': '', 'h4': '',
           'a3': '', 'b3': '', 'c3': '', 'd3': '', 'e3': '', 'f3': '', 'g3': '', 'h3': '',
           'a2': 'wp', 'b2': 'wp', 'c2': 'wp', 'd2': 'wp', 'e2': 'wp', 'f2': 'wp', 'g2': 'wp', 'h2': 'wp',
           'a1': 'wR', 'b1': 'wN', 'c1': 'wB', 'd1': 'wQ', 'e1': 'wK', 'f1': 'wB', 'g1': 'wN', 'h1': 'wR'}



def isValidChessBoard(board):
    valid_count = True
    valid_position = True
    valid_board = True
    piece_count = {}
    black_pieces = 0
    white_pieces = 0
    board_pieces = list(board.values())
    position = list(board.keys())
    position_message = 'Correct position.'
    piececount_message = ''
    message = ''
    
    #Counting blacks and whites
    for i in board_pieces:
        if i == '':
           pass
        elif i[0] == 'b':
            black_pieces += 1
        elif i[0] == 'w':
            white_pieces += 1
        piececount_message = 'There are ' + str(white_pieces) + ' white pieces and ' + str(black_pieces) + ' black pieces.'
            
    #Checking valid position
    for p in position:
        position_str = int(p[1])
        if position_str > 8 or position_str < 1:
            valid_position = False
            position_message = 'Invalid position.'

    #Counting the number of each piece and setting default for empty spaces
    for v in MyBoard.values():
        piece_count.setdefault(v, 0)
        piece_count[v] += 1
    print(piece_count)

    #Checking King numbers
    if piece_count.get('wK', 0) > 1 or piece_count.get('bK', 0) > 1\
    or piece_count.get('wK', 0) < 1 or piece_count.get('bK', 0) < 1:
        valid_count = False
        message = 'King count error.'

    #Checking Queen numbers
    elif piece_count.get('wQ', 0) > 1 or piece_count.get('bQ', 0) > 1\
    or piece_count.get('wQ', 0) < 1 or piece_count.get('bQ', 0) < 1:
        valid_count = False
        message = 'Queen count Error.'

    #Checking Bishop numbers
    elif piece_count.get('wB', 0) > 2 or piece_count.get('bB', 0) > 2\
    or piece_count.get('wB', 0) < 2 or piece_count.get('bB', 0) < 2:
        valid_count = False
        message = 'Bishop count error.'

    #Checking Knight numbers
    elif piece_count.get('wN', 0) > 2 or piece_count.get('bN', 0) > 2\
    or piece_count.get('wN', 0) < 2 or piece_count.get('bN', 0) < 2:
        valid_count = False
        message = 'Knight count error.'

    #Checking Rook numbers
    elif piece_count.get('wR', 0) > 2 or piece_count.get('bR', 0) > 2\
    or piece_count.get('wR', 0) < 2 or piece_count.get('bR', 0) < 2:
        valid_count = False
        message = 'Rook count error.'

    #Checking Pawn numbers
    elif piece_count.get('wp', 0) > 8 or piece_count.get('bp', 0) > 8\
    or piece_count.get('wp', 0) < 8 or piece_count.get('bp', 0) < 8:
        valid_count = False
        message = 'Pawn count error.'

    else:
        message = 'Correct number of pieces on the board.'

    valid_board = valid_count and valid_position
    print(piececount_message)
    print(position_message)
    print(message)
    print('Board Valid: ' + str(valid_board) + '.')
    


isValidChessBoard(MyBoard)
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Your implementation has broken the spec. Pieces aren't supposed to have a single letter to identify them; they're supposed to have the full word.

Your message and print code is non-ideal because:

  • it complicates your implementation;
  • it wasn't asked for in the spec; and
  • it forces all callers to produce console output whether they wanted it or not.

So it should go away.

Your # Checking ... numbers code is repetitive and can be simplified by use of a lookup dictionary for expected counts of each piece.

Another way that you've broken the spec - re-read this section:

returns True or False depending on if the board is valid

Your function does not return.

Otherwise, consider:

  • Add type hints
  • Make a simple object-oriented interface for a Piece

Suggested

from typing import NamedTuple, Iterator, Literal


class Piece(NamedTuple):
    row: Literal['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
    col: Literal['1', '2', '3', '4', '5', '6', '7', '8']
    player: Literal['b', 'w']
    piece: Literal['pawn', 'knight', 'bishop', 'rook', 'queen', 'king']

    @classmethod
    def from_pair(cls, pos: str, piece: str) -> 'Piece':
        row, col = pos
        return cls(row=row, col=col, player=piece[0], piece=piece[1:])

    @property
    def is_valid(self) -> bool:
        return (
            # all pieces must be on a valid space from '1a' to '8h'; that is, a piece can’t be on space '9z'.
            self.row in {'a','b','c','d','e','f','g','h'}
            and self.col in {'1','2','3','4','5','6','7','8'}
            # The piece names begin with either a 'w' or 'b' to represent white or black
            and self.player in {'b','w'}
            # followed by 'pawn', 'knight', 'bishop', 'rook', 'queen', or 'king'
            and self.piece in {'pawn', 'knight', 'bishop', 'rook', 'queen', 'king'}
        )


def parse_pieces(board: dict[str, str]) -> Iterator[Piece]:
    for pos, player_piece in board.items():
        if player_piece != '':
            yield Piece.from_pair(pos, player_piece)


def is_valid_chess_board(board: dict[str, str]) -> bool:
    try:
        pieces = list(parse_pieces(board))
    except IndexError:
        return False

    if not all(piece.is_valid for piece in pieces):
        return False

    counts = {
        'pawn': 8, 'knight': 2, 'bishop': 2, 'rook': 2, 'queen': 1, 'king': 1,
    }

    for player in 'bw':
        for piece_name, piece_count in counts.items():
            count = sum(
                piece.player == player and piece.piece == piece_name
                for piece in pieces
            )
            if count != piece_count:
                return False

    return True


def main() -> None:
    my_board = {
        'a8': 'brook', 'b8': 'bknight', 'c8': 'bbishop', 'd8':  'bking', 'e8': 'bqueen', 'f8': 'bbishop', 'g8': 'bknight', 'h8': 'brook',
        'a7': 'bpawn', 'b7':   'bpawn', 'c7':   'bpawn', 'd7':  'bpawn', 'e7':  'bpawn', 'f7':   'bpawn', 'g7':   'bpawn', 'h7': 'bpawn',
        'a6':      '', 'b6':        '', 'c6':        '', 'd6':       '', 'e6':       '', 'f6':        '', 'g6':        '', 'h6':      '',
        'a5':      '', 'b5':        '', 'c5':        '', 'd5':       '', 'e5':       '', 'f5':        '', 'g5':        '', 'h5':      '',
        'a4':      '', 'b4':        '', 'c4':        '', 'd4':       '', 'e4':       '', 'f4':        '', 'g4':        '', 'h4':      '',
        'a3':      '', 'b3':        '', 'c3':        '', 'd3':       '', 'e3':       '', 'f3':        '', 'g3':        '', 'h3':      '',
        'a2': 'wpawn', 'b2':   'wpawn', 'c2':   'wpawn', 'd2':  'wpawn', 'e2':  'wpawn', 'f2':   'wpawn', 'g2':   'wpawn', 'h2': 'wpawn',
        'a1': 'wrook', 'b1': 'wknight', 'c1': 'wbishop', 'd1': 'wqueen', 'e1':  'wking', 'f1': 'wbishop', 'g1': 'wknight', 'h1': 'wrook',
    }

    print(is_valid_chess_board(my_board))


if __name__ == '__main__':
    main()
\$\endgroup\$
1
  • \$\begingroup\$ For the piece names I purposefully broke that to make it closer to the algebraic notation that chess uses. For the rest, thank you! \$\endgroup\$
    – Javana
    Apr 28, 2022 at 19:36

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.