# Automate the Boring Stuff Chapter 5 Project - Chess Dictionary Validator

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)


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 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

Otherwise, consider:

• 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()

• For the piece names I purposefully broke that to make it closer to the algebraic notation that chess uses. For the rest, thank you! Apr 28, 2022 at 19:36