5
\$\begingroup\$

I've made a working version of Connect Four in Python. I'm pretty new to Python so it's decently primitive. Please take a look and let me know what you think!

import random

board = [["." for x in range(7)] for x in range(6)]
symbol = ""
winCondition = ""
fullCols = []

def boardLogic(pick, user):
    if user == "user":
        symbol = "0"
    else:
        symbol = "X"
    if board[0][pick] != ".":
        if board[1][pick] != ".":
            if board[2][pick] != ".":
                if board[3][pick] != ".":
                    if board[4][pick] != ".":
                        if board[5][pick] != ".":
                            print("That column is full.")
                            if pick not in fullCols:
                                fullCols.append(pick)
                            fullCols.sort()
                            print fullCols
                        else:
                            board[5][pick] = symbol
                    else:
                        board[4][pick] = symbol
                else:
                    board[3][pick] = symbol
            else:
                board[2][pick] = symbol
        else:
            board[1][pick] = symbol
    else:
        board[0][pick] = symbol

def printBoard():
    for row in board:
        for val in row:
            print '{:4}'.format(val),
        print

def does_square_contain_win(i, j):
    #right_diag:  [[i,j], [i-1,j+1], [i-2,j+2], [i-3,j+3]]
    if i-3 in range(0, 6) and j+3 in range(0,5):
        if board[i][j] == "0" and board[i-1][j+1] == "0" and board[i-2][j+2] == "0" and board[i-3][j+3] == "0":
            print("******************************\nPlayer Wins\n******************************")
            quit()
    if i-3 in range(0, 6) and j+3 in range(0,5):
        if board[i][j] == "X" and board[i-1][j+1] == "X" and board[i-2][j+2] == "X" and board[i-3][j+3] == "X":
            print("******************************\nComputer Wins\n******************************")
            quit()
    #left_diag:  [[i,j], [i-1,j-1], [i-2,j-2], [i-3,j-3]]
    if i-3 in range(0, 6) and j-3 in range(0,5):
        if board[i][j] == "0" and board[i-1][j-1] == "0" and board[i-2][j-2] == "0" and board[i-3][j-3] == "0":
            print("******************************\nPlayer Wins\n******************************")
            quit()
    if i-3 in range(0, 6) and j-3 in range(0,5):
        if board[i][j] == "X" and board[i-1][j-1] == "X" and board[i-2][j-2] == "X" and board[i-3][j-3] == "X":
            print("******************************\nComputer Wins\n******************************")
            quit()
    #right: [[i,j], [i,j+1], [i,j+2], [i,j+3]]
    if j+3 in range(0, 5):
        if board[i][j] == "0" and board[i][j+1] == "0" and board[i][j+2] == "0" and board[i][j+3] == "0":
            print("******************************\nPlayer Wins\n******************************")
            quit()
    if j+3 in range(0, 5):
        if board[i][j] == "X" and board[i][j+1] == "X" and board[i][j+2] == "X" and board[i][j+3] == "X":
            print("******************************\nComputer Wins\n******************************")
            quit()
    #down:  [[i,j], [i-1,j], [i-2,j], [i-3,j]]
    if i-3 in range(0, 6):
        if board[i][j] == "0" and board[i-1][j] == "0" and board[i-2][j] == "0" and board[i-3][j] == "0":
            print("******************************\nPlayer Wins\n******************************")
            quit()
    if i-3 in range(0, 6):
        if board[i][j] == "X" and board[i-1][j] == "X" and board[i-2][j] == "X" and board[i-3][j] == "X":
            print("******************************\nComputer Wins\n******************************")
            quit()

while True:
    printBoard()

    #pick a column
    while True:
        try:
            yourPick = int(raw_input('Pick a column 0 - 6: '))
        except ValueError:
            print 'That\'s not a number!'
        else:
            if 0 <= yourPick < 6 and yourPick not in fullCols:
                break
            else:
                print 'Out of range or column full. Try again.'
    print(yourPick)
    #column logic
    boardLogic(yourPick, "user")

    #opponent randomly picks a column
    while True:
        try:
            theirPick = random.randint(0, 6)
        except ValueError:
            print 'That\'s not a number!'
        else:
            if theirPick not in fullCols:
                break
            else:
                print 'Column full!'
    print(theirPick)
    #column logic
    boardLogic(theirPick, "notUser")

    #check win conditions
    for i in range(0, 6):
        for j in range(0, 5):
            does_square_contain_win(i, j)
\$\endgroup\$
  • \$\begingroup\$ the nested if-else statements was already covered, but also you check the condition, if i-3 in range(0, 6) and j-3 in range(0,5): , multiple times - that definitely can be simplified. \$\endgroup\$ – mugabits Feb 19 '16 at 20:41
3
\$\begingroup\$

Raise the abstraction

if board[0][pick] != ".":
    if board[1][pick] != ".":
        if board[2][pick] != ".":
            if board[3][pick] != ".":
                if board[4][pick] != ".":
                    if board[5][pick] != ".":
                        print("That column is full.")
                        if pick not in fullCols:
                            fullCols.append(pick)
                        fullCols.sort()
                        print fullCols
                    else:
                        board[5][pick] = symbol
                else:
                    board[4][pick] = symbol
            else:
                board[3][pick] = symbol
        else:
            board[2][pick] = symbol
    else:
        board[1][pick] = symbol
else:
    board[0][pick] = symbol

So if the first is empty you assign the first, if the second is empty you assign the second ...

In other words you assign the first empty, let me write some pseudocode:

def boardLogic(pick, user):
    # ... Logic before (this should maybe be moved elsewhere)

    try:
        first_empty = first(lambda i: board[i][pick] == EMPTY, range(5+1))
        board[first_empty][pick] = symbol
    except StopIteration:
         print("That column is full.")
         # ... No empty space

Where

EMPTY = '.'

And first can very easily be found on StackOverflow


In high level languages you should try to write code that resembles the high level description of the problem, and avoid a lot of chained conditionals.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Caridorc made a very good point which I think is the key thing to look at, but there's other stuff here I wanna note.

There's no need to instantiate symbol or winCondition. For one, it doesn't seem like you use winCondition. But also with symbol it's perfectly fine to just define it inside a function rather than in the global namespace. You only need it in one place? Just use it there.

Still on symbol. There's no real need to pass user to boardLogic. You could save yourself time by passing symbol values directly, since user just gets passed as a literal anyway:

def boardLogic(pick, symbol):

boardLogic(yourPick, "0")

There's tons of repetition in does_square_contain_win. You should really only have two print lines, one for computer win and one for player win. Your if statements should be then used to narrow down to one of those 2 conditions. Even an overly long if statement is better than many many little if blocks.

When getting user input, instead of using else on a try except, you should continue after the ValueError:

while True:
    try:
        yourPick = int(raw_input('Pick a column 0 - 6: '))
    except ValueError:
        print 'That\'s not a number!'
        continue

    if 0 <= yourPick < 6 and yourPick not in fullCols:
        break
    else:
        print 'Out of range or column full. Try again.'

It's easier to read... and I had forgotten about else blocks being on excepts. Also why are you treating the computer's choices like human input? They can't possibly raise that ValueError, and the player doesn't need to know that the computer has tried to pick fullCols. Leave out prints here, just loop until theirPick isn't in fullCols.

Now, if you wanted a better method you could get a list of numbers from 0 to 6 that are not in fullCols and randomly pick from there. That way you know a move is valid straight away:

theirPick = random.choice([x for x in range(7) if x not in fullCols])

random.choice will randomly choose one value from a collection. The list here is a list comprehension of all values from 0 to 6 if they're not in fullCols. This means you're always going to get a valid number.

Now that I'm at the end, the way you're testing win conditions is very odd. Why are you looping over every single square to check for a win? Surely you only need to check the user and computers' picks? It's definitely very inefficient, and hard to read. You should either just have a function that reads the whole board at once (which I think would be more readable) or specifically just run tests on the most recently added positions. Doing every position is just going to confuse people.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.