Writing a chess game in python [Day 2]
Tags: python, codenewbie
Published: 2020-10-06 08:50:22

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](https://dev.to/rinaarts/writing-a-chess-game-in-python-with-my-12yo-day-1-4pe6) 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!