Connect Four

Cleaning up code is an everyday activity of any prorammer. Clean programs are shorter, easier to understand and easier to change. The process of cleaning up is also called refactoring. More specifically, refactoring means improving the structure of a program without changing its functionality.

In this chapter you will:

area

topic

🚀

clean up a connect four game

🔧

read code

🔧

apply refactoring: extract a function

🔧

apply refactoring: extract a loop

🐞

remove redundant code

Exercise 1

In connect_four.py you find a working implementation of a simple “Connect Four” game. Writing such a program for the first time is a huge success!

Run the game and see how it works.

Exercise 2: Code review

The first implementation often requires clean-up. Read the code in connect_four.py

Do you see any problems in the code?

Exercise 3: Extract a function

In the code, there is a paragraph commented with "print the game board. This code paragraph has a well-defined functionality. Move it into a separate function.

Define a function at the beginning of the file:

def print_board(board):
    ...

Move the code paragraph into that function. Add a function call where you took the code from:

while True:

    print_board(board):

# first player moves
...

Run the game and make sure it still works.

Exercise 4: Redundant code

The code for the print_board() function appears a second time further below.

Replace the code by the same function call.

Because you already defined the function, you can use it multiple times. Your program should become shorter by a few lines.

Run the game and make sure it still works.

Exercise 5: A function with a parameter

The next paragraph from "first player moves" to "end: player moves" can also be moved to a function. This time, we introduce an extra function parameter: the symbol for the player.

Create a new function with the definition:

def player_move(board, symbol):
    ...

Replace the "X" in the code by the new variable symbol. Use the function call:

player_move(board, "X")

Run the game and make sure it still works.

Then, see if there is a similar code paragraph that you could replace by a function call.

Exercise 6: More functions

See if there are any other redundant blocks of code. Move them into functions as well.

Then, run the game and make sure it still works.

Exercise 7: Extract a loop

Consider the following section of the code:

if board[0][x - 1] == ".":
    y = 0
if board[1][x - 1] == ".":
    y = 1
if board[2][x - 1] == ".":
    y = 2
if board[3][x - 1] == ".":
    y = 3
if board[4][x - 1] == ".":
    y = 4

Here, there is redundant code on a smaller scale. Each pair of lines differs only by a single number. This code can be made shorter by using a for loop that counts from 0 to 4.

What needs to be inserted into the two gaps?

for i in range(5):
    if board[___][x - 1] == ".":
        y = ___

Run the game and make sure it still works.

The Code

Here is the complete (unclean) code:

import sys

# create empty game board
board = [
         [".", ".", ".", ".", ".", ".", "."],
         [".", ".", ".", ".", ".", ".", "."],
         [".", ".", ".", ".", ".", ".", "."],
         [".", ".", ".", ".", ".", ".", "."],
         [".", ".", ".", ".", ".", ".", "."],
]

while True:

    # print the game board
    print()
    print("1 2 3 4 5 6 7")
    for row in board:
        line = ""
        for cell in row:
            line += cell + " "
        print(line)
    print()

    # first player moves
    x = input("enter a column (1-7) ")
    x = int(x)

    # find empty cell closest to bottom
    if board[0][x - 1] == ".":
        y = 0
    if board[1][x - 1] == ".":
        y = 1
    if board[2][x - 1] == ".":
        y = 2
    if board[3][x - 1] == ".":
        y = 3
    if board[4][x - 1] == ".":
        y = 4

    board[y][x - 1] = "X"
    # end: player moves

    # check rows
    for row in board:
        for i in range(4):
            if (
                row[i] == "X"
                and row[i + 1] == "X"
                and row[i + 2] == "X"
                and row[i + 3] == "X"
            ):
                print("Player 1 (X) wins!")
                sys.exit(0)

    # check columns
    for i in range(7):
        if board[0][i] == "X" and board[1][i] == "X" and board[2][i] == "X" and board[3][i] == "X":
            print("Player 1 (X) wins!")
            sys.exit(0)
        elif board[1][i] == "X" and board[2][i] == "X" and board[3][i] == "X" and board[4][i] == "X":
            print("Player 1 (X) wins!")
            sys.exit(0)

    # check diagonals
    for y in range(2):
        for x in range(4):
            if (
                board[y][x] == "X"
                and board[y + 1][x + 1] == "X"
                and board[y + 2][x + 2] == "X"
                and board[y + 3][x + 3] == "X"
            ):
                print("Player 1 (X) wins!")
                sys.exit(0)
    for y in range(2):
        for x in range(3, 7):
            if (
                board[y][x] == "X"
                and board[y + 1][x - 1] == "X"
                and board[y + 2][x - 2] == "X"
                and board[y + 3][x - 3] == "X"
            ):
                print("Player 1 (X) wins!")
                sys.exit(0)

    # print the game board
    print()
    print("1 2 3 4 5 6 7")
    for row in board:
        line = ""
        for cell in row:
            line += cell + " "
        print(line)

    print()

    # second player moves
    x = input("enter a column (1-7) ")
    x = int(x)

    # find empty cell closest to bottom
    if board[0][x - 1] == ".":
        y = 0
    if board[1][x - 1] == ".":
        y = 1
    if board[2][x - 1] == ".":
        y = 2
    if board[3][x - 1] == ".":
        y = 3
    if board[4][x - 1] == ".":
        y = 4

    board[y][x - 1] = "O"

    # check rows
    for row in board:
        for i in range(4):
            if (
                row[i] == "O"
                and row[i + 1] == "O"
                and row[i + 2] == "O"
                and row[i + 3] == "O"
            ):
                print("Player 2 (O) wins!")
                sys.exit(0)

    # check columns
    for i in range(7):
        if board[0][i] == "O" and board[1][i] == "O" and board[2][i] == "O" and board[3][i] == "O":
            print("Player 2 (O) wins!")
            sys.exit(0)
        elif board[1][i] == "O" and board[2][i] == "O" and board[3][i] == "O" and board[4][i] == "O":
            print("Player 2 (O) wins!")
            sys.exit(0)

    # check diagonals
    for y in range(2):
        for x in range(4):
            if (
                board[y][x] == "O"
                and board[y + 1][x + 1] == "O"
                and board[y + 2][x + 2] == "O"
                and board[y + 3][x + 3] == "O"
            ):
                print("Player 2 (O) wins!")
                sys.exit(0)
    for y in range(2):
        for x in range(3, 7):
            if (
                board[y][x] == "O"
                and board[y + 1][x - 1] == "O"
                and board[y + 2][x - 2] == "O"
                and board[y + 3][x - 3] == "O"
            ):
                print("Player 2 (O) wins!")
                sys.exit(0)