We're still in lockdown due to COVID-19, and I'm using the time to implement a textual chess game with my 12yo. In this series I'll be sharing what we did and how the project evolved.
We left off in Day 1 with a method that prints a two dimensional board.
We talked a bit about what would be the next step - do we want to get input from the users and parse it, or start implementing the logic of the moves. 12yo opted for implementing the moves. According to what he said we defined how this method should work:
- User provides the name of the piece to move and where it should go (target square)
- We determine if the move is valid and find the matching piece
- If the move is valid, we move the matching piece to the target square.
At this point 12yo pointed out that in chess rows are called rank and columns are file, so I should start defining the variable names accordingly. Well, sorrrrry :eye-roll:. So this is the interface we defined:
def is_valid_move(piece, target_rank, target_file):
pass
First thing to check is if the target square is in the bounds of the board:
def is_valid_move(piece, target_rank, target_file):
# out of bounds
if 0 > target_rank > 7:
return False
if 0 > target_file > 7:
return False
OK, cool. What's next?
We need to check that the target square doesn't already have a piece of the same color, by checking if the color prefix is the same:
...
# piece with same color is in the target cell
if board[target_rank][target_file][0] == piece[0]:
return False
Now what? 12yo suggested we start with checking if the move is valid for a rook, since it can only move in straight lines and that should be relatively easy to code:
...
if piece in ("WR", "BR"):
pass
How are we going to do this?
First thing we need to check if there is a rook on the same rank or on the same file as the target square.
...
found = False
for i in range(8):
if (board[target_rank][i] == piece):
found = True
break
for i in range(8):
if (board[i][target_file] == piece):
found = True
break
return found
Remember that we're doing a naive solution and we'll deal with optimizations later, there are definitely better ways to write this code!
Wait, but what if the piece is already at the target square?
Actually... that's already covered by the condition that checked if there was a piece with the same color in the target square. OK, let's move on.
Next thing we need to check is if there's another piece blocking our way to the target square. But our previous implementation didn't give us enough information to figure this out. So let's change our implementation to include the indexes of where we found the piece. We'll actually need this later when we get to performing the moves, but no spoilers!
...
found_rank = -1
found_file = -1
for i in range(8):
if board[target_rank][i] == piece:
found_rank = target_rank
found_file = i
break
# TODO: deal with the case where there are two matching pieces
if found_rank > -1:
for i in range(8):
if board[i][target_file] == piece:
found_rank = i
found_file = target_file
break
if found_rank < 0 or found_file < 0:
return False
Now let's check if there are any pieces between the piece we found and the target cell:
...
if found_rank == target_rank:
if found_file > target_file:
for i in range(target_file+1, found_file):
if board[target_rank][i] != " ":
return False
else: # found_file < target_file
for i in range(found_file+1, target_file):
if board[target_rank][i] != " ":
return False
else: # found_file == target_file
if found_rank > target_rank:
for i in range(target_rank+1, found_rank):
if board[i][target_file] != " ":
return False
else: # found_rank < target_rank
for i in range(found_rank+1, target_rank):
if board[i][target_file] != " ":
return False
return True
I don't like that string comparison so we changed it to a constant to make it clearer:
EMPTY = " "
Let's pack all that logic into a separate method, or our code will be impossible to manage. We also realized that although white is at the bottom, it's indexes are 0-7, so we probably want to reverse the way we define the board and only print it bottom-up.
Here's the final code for today:
EMPTY = " "
def print_board(board):
row_number = 8
print(" ", end="")
print(" ----"*8)
for row in reversed(board):
print(row_number, end=" ")
row_number -= 1
for cell in row:
print("| {} ".format(cell), end="")
print("|")
print(" ", end="")
print(" ----"*8)
print(" ", end="")
for letter in ['a','b','c','d','e','f','g','h']:
print(" {} ".format(letter), end="")
print("")
board = [[EMPTY]*8]*8
board[0] = ["WR","WN","WB","WQ","WK","WB","WN","WR"]
board[1] = ["WP","WP","WP","WP","WP","WP","WP","WP"]
board[6] = ["BP","BP","BP","BP","BP","BP","BP","BP"]
board[7] = ["BR","BN","BB","BQ","BK","BB","BN","BR"]
def is_valid_move(piece, target_rank, target_file):
# out of bounds
if 0 > target_rank > 7:
return False
if 0 > target_file > 7:
return False
# piece with same color is in the target cell
if board[target_rank][target_file][0] == piece[0]:
return False
if piece in ("WR", "BR"):
return is_valid_rook_move(piece, target_rank, target_file)
def is_valid_rook_move(piece, target_rank, target_file):
found_rank = -1
found_file = -1
for i in range(8):
if board[target_rank][i] == piece:
found_rank = target_rank
found_file = i
break
if found_rank > -1:
for i in range(8):
if board[i][target_file] == piece:
found_rank = i
found_file = target_file
break
if found_rank < 0 or found_file < 0:
return False
if found_rank == target_rank:
if found_file > target_file:
for i in range(target_file+1, found_file):
if board[target_rank][i] != EMPTY:
return False
else: # found_file < target_file
for i in range(found_file+1, target_file):
if board[target_rank][i] != EMPTY:
return False
else: # found_file == target_file
if found_rank > target_rank:
for i in range(target_rank+1, found_rank):
if board[i][target_file] != EMPTY:
return False
else: # found_rank < target_rank
for i in range(found_rank+1, target_rank):
if board[i][target_file] != EMPTY:
return False
return True
I have no idea if we got this right, but 12yo was tired so we stopped here. Join us tomorrow for testing!
Top comments (0)