If Statement Error (Tic Tac Toe)

Discussion in 'Python' started by ale.of.ginger, Nov 1, 2005.

  1. Greetings! I am trying to make a multiplayer (no AI, 2 person) game of
    tic tac toe in Python. So far it has been pretty simple. My only
    concern is with the win checking to see if a person has won. At first
    it looked like it was working, but now it sometimes assigns a win when
    you enter an X or O (doesn't matter) on certain tiles (row 1, column 1
    won't be an error, but row 2, column 3 will be...). If you can find
    the problem, I'd be very thankful! Here's the code:

    # TIC TAC TOE
    # Started: 10/31/05
    # Ended: still in progress

    loop = 1

    while loop == 1:
    print "TIC TAC TOE"
    print "1 - Play Multiplayer"
    print "2 - Quit"
    option = input("> ")

    if option == 2:
    # QUIT
    loop = 0

    if option == 1:
    # MAIN GAME LOOP
    print "Rules: You will alternate turns."
    print "On your turn, you can place your letter (O = Player 1 or
    X = Player 2)",
    print "in any unoccupied square."
    print "The first to get 3 in a row wins. Good luck!"

    gameboard = [' ',' ',' ',' ',' ',' ',' ',' ',' ']

    win = 0
    turnnumber = 0

    while win != 1:
    if turnnumber % 2 == 0:
    print " "
    print "Player 1"
    print " "
    print
    "[",gameboard[0],"]","[",gameboard[1],"]","[",gameboard[2],"]"
    print
    "[",gameboard[3],"]","[",gameboard[4],"]","[",gameboard[5],"]"
    print
    "[",gameboard[6],"]","[",gameboard[7],"]","[",gameboard[8],"]"
    print "What row?"
    row = input("> ")
    print "What column?"
    column = input("> ")

    if (row > 3 or row < 1) or (column > 3 or column < 1):
    print "Exceeeded limits."
    turnnumber = turnnumber - 1

    if row == 1 and column == 1:
    if gameboard[0] != ('O' or 'X'):
    gameboard[0] = ('O')
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 2 and column == 1:
    if gameboard[3] != ('O' or 'X'):
    gameboard[3] = ('O')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1
    if row == 3 and column == 1:
    if gameboard[6] != ('O' or 'X'):
    gameboard[6] = ('O')
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 1 and column == 2:
    if gameboard[1] != ('O' or 'X'):
    gameboard[1] = ('O')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 2 and column == 2:
    if gameboard[4] != ('O' or 'X'):
    gameboard[4] = ('O')
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 3 and column == 2:
    if gameboard[7] != ('O' or 'X'):
    gameboard[7] = ('O')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 1 and column == 3:
    if gameboard[2] != ('O' or 'X'):
    gameboard[2] = ('O')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 2 and column == 3:
    if gameboard[5] != ('O' or 'X'):
    gameboard[5] = ('O')
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 3 and column == 3:
    if gameboard[8] != ('O' or 'X'):
    gameboard[8] = ('O')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    turnnumber = turnnumber + 1

    if (((gameboard[0] and gameboard[1] and gameboard[2]) ==
    'O') or ((gameboard[3] and gameboard[4] and gameboard[5]) == 'O') or
    ((gameboard[6] and gameboard[7] and gameboard[8]) == 'O')\
    or ((gameboard[0] and gameboard[3] and gameboard[6]) ==
    'O') or ((gameboard[1] and gameboard[4] and gameboard[7]) == 'O') or
    ((gameboard[2] and gameboard[5] and gameboard[8]) == 'O')\
    or ((gameboard[0] and gameboard[4] and gameboard[8]) ==
    'O') or ((gameboard[2] and gameboard[4] and gameboard[6]) == 'O')):
    print "Player 1 wins!"
    win = 1

    if ((gameboard[0:9]) == ('O' or 'X')) and (win == 0):
    print "Tie."
    win = 1

    if turnnumber % 2 == 1 and win != 1:
    print " "
    print "Player 2"
    print " "
    print
    "[",gameboard[0],"]","[",gameboard[1],"]","[",gameboard[2],"]"
    print
    "[",gameboard[3],"]","[",gameboard[4],"]","[",gameboard[5],"]"
    print
    "[",gameboard[6],"]","[",gameboard[7],"]","[",gameboard[8],"]"
    print "What row?"
    row = input("> ")
    print "What column?"
    column = input("> ")

    if (row > 3 or row < 1) or (column > 3 or column < 1):
    print "Exceeeded limits."
    turnnumber = turnnumber - 1
    if row == 1 and column == 1:
    if gameboard[0] != ('O' or 'X'):
    gameboard[0] = ('X')
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 2 and column == 1:
    if gameboard[3] != ('O' or 'X'):
    gameboard[3] = ('X')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 3 and column == 1:
    if gameboard[6] != ('O' or 'X'):
    gameboard[6] = ('X')
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 1 and column == 2:
    if gameboard[1] != ('O' or 'X'):
    gameboard[1] = ('X')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 2 and column == 2:
    if gameboard[4] != ('O' or 'X'):
    gameboard[4] = ('X')
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 3 and column == 2:
    if gameboard[7] != ('O' or 'X'):
    gameboard[7] = ('X')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 1 and column == 3:
    if gameboard[2] != ('O' or 'X'):
    gameboard[2] = ('X')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 2 and column == 3:
    if gameboard[5] != ('O' or 'X'):
    gameboard[5] = ('X')
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    if row == 3 and column == 3:
    if gameboard[8] != ('O' or 'X'):
    gameboard[8] = ('X')

    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    turnnumber = turnnumber + 1

    if (((gameboard[0] and gameboard[1] and gameboard[2]) ==
    'X') or ((gameboard[3] and gameboard[4] and gameboard[5]) == 'X') or
    ((gameboard[6] and gameboard[7] and gameboard[8]) == 'X')\
    or ((gameboard[0] and gameboard[3] and gameboard[6]) ==
    'X') or ((gameboard[1] and gameboard[4] and gameboard[7]) == 'X') or
    ((gameboard[2] and gameboard[5] and gameboard[8]) == 'X')\
    or ((gameboard[0] and gameboard[4] and gameboard[8]) ==
    'X') or ((gameboard[2] and gameboard[4] and gameboard[6]) == 'X')):
    print "Player 2 wins!"
    win = 1
     
    ale.of.ginger, Nov 1, 2005
    #1
    1. Advertisements

  2. ale.of.ginger

    Mike Meyer Guest

    You've got two problems. One of them causes your problem. The other
    caues you to duplicate a *lot* of code.

    The first problem is that logical and equality tests don't
    interoperate the way you think they do:

    (gameboard[0] and gameboard[1] and gameboard[2]) == 'X')

    does *not* verify that each of gameboard[0], gameboard[1] and
    gameboard[2] are all equal to 'X'. It tests whether or not the value
    (gameboard[0] and gamerboard[1] and gameboard[2]) is equal to 'X'. The
    former is almost certainly the value of gameboard[2], unless
    gameboard[0] or gameboard[1] evaluates to a false value, in which case
    that's the value you're comparing to 'X'. You'll have to break this
    one out into a set of three tests.

    Similar comments apply to tests like "gameboard[0] != ('O' or 'X')". The
    rhs of that is a constant - 'X'. This one you can rewrite as
    "gameboard[0] not in '0X'".

    The second problem is that you're using code where you should be using
    data. Whenver you see a long stretch of very similar code repeated
    over and over, you should consider replacing the code with data. For
    instance, all those things that look like:

    if row == X and column == Y:
    if gameboard[V] not in 'OX':
    gammeboard[V] = 'O'
    else:
    print "This cell is already filled."
    turnnumber -= 1
    [repeate multiple times. And while I'm at it, the second and further
    tests in this string should be elif, not if.]


    Can be replaced by a dictionary (or a list of lists):

    boardmap = {(1, 1): 0, (2, 1): 3, (3, 1): 6,
    (1, 2): 1, (2, 2): 4, (3, 2): 7,
    (1, 3): 2, (2, 3): 5, (3, 3): 8
    }

    cell = boardmap[row, column]
    if gameboard[cell] not in 'OX':
    gameboard[cell] = 'O'
    else:
    print "This cell is already filled."
    turnnumber -= 1


    The representation as a list of lists is left as an exercise for the
    reader.

    <mike
     
    Mike Meyer, Nov 1, 2005
    #2
    1. Advertisements

  3. Thank you. It seems I didn't understand logic statements as much as I
    thought I did!

    The one remaining question I have deals with this:

    if gameboard[cell] not in 'OX':
    gameboard[cell] = 'O'
    else:
    print "This cell is already filled."
    turnnumber -= 1

    (gameboard[cell] not in 'OX' is the gameboard[cell] != 'OX' text I sort
    of used in my code, right?)

    I am confused about "OX": what exactly *is* it? After that, I plan to
    rewrite all the code... :)
     
    ale.of.ginger, Nov 1, 2005
    #3
  4. ale.of.ginger

    Mike Meyer Guest

    "OX" is a string. Saying "shortstring in longstring" is one way of
    asking if shortstring is contained in longstring. In your case,
    shortstring is one character long, but that's a string to
    Python. Python doesn't have a distinct character data type.

    While I'm at it, don't be confused by single quotes vs. double
    quotes. Unlike other languages, in Python they have the same
    meaning. The only difference is what you have to do to get a single
    (double) quote in the string.

    <mike
     
    Mike Meyer, Nov 1, 2005
    #4
  5. So is there a way I have to set up the string OX in the beginning? Is
    this where it houses the location of the Xs and Os to determine whether
    or not a letter is already there?
     
    ale.of.ginger, Nov 1, 2005
    #5
  6. Nevermind my previous reply: I've been fixing up the code and
    understand it. I'm at a nifty 80 lines where I am at now rather than
    209 lines in the previous version! The changes are immense!

    The only problem I have is whenever player two goes, it says the cell
    is filled. But the code:

    if gameboard[cell] not in 'OX':
    gameboard[cell] = 'X'
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    is not any different than the code I am using for player one. Anything
    I have to change regarding 'OX' or something else?
     
    ale.of.ginger, Nov 1, 2005
    #6
  7. ale.of.ginger

    Mike Meyer Guest

    Make sure you're setting cell properly - you didn't show that.

    Also, the above code is a syntax error; I assume your running code has
    the correct indentation.

    <mike
     
    Mike Meyer, Nov 2, 2005
    #7
  8. ale.of.ginger

    Jim Segrave Guest

    I see one of the reponses directs you to a web site about how to ask
    questions and expect answers - there's some very good advice
    there. Nonetheless, I'll give you the benefit of the doubt and assume
    that you are _very_ new to programming. I hope I haven't redone your
    homework for you, but if I have, I would suggest you don't submit my
    answer, I think it might be recognised as not being your own.

    The biggest problem with asking someone for help with a program like
    this is that it's very large (not in and of itself a problem) and,
    even to the most rapid glance, the program contains huge swathes of
    essentially the same code repeated over and over with only tiny
    changes. When I see that, all the warning bells start ringing.

    My first hint would be that anytime you find yourself typing exactly
    the same (or almost exactly the same thing) over and over, you are
    probably doing something wrong. In your original code, you have a
    whole section for handling player X, then you have virtually identical
    code, for player O.

    You should almost never need to do this. There are a couple of easy
    ways to save retyping the same code:

    1) create a function. The function body will have all the identical
    code, the function arguments will indicate what's different between
    the two sections. I think this is a hint from Kernighan and Pike
    from a book dating back to the late 1960's - subroutines are used
    to show what code has in common, the arguments are used to show
    what's different.

    or

    2) put the duplicated code inside a loop and let the loop variable be
    used to make the small changes. That's what I did in the hasty
    rework of your program below.

    Then, when we look at the code for a single player, you have 9
    separate tests to see if a cell is occupied:

    if row == 2 and column == 1:
    if gameboard[3] != ('O' or 'X'):
    gameboard[3] = ('O')
    else:
    print "This cell is already filled."
    turnnumber = turnnumber - 1

    For each row and column, you know which cell to look at, so why have
    tests for every possible input, when only one test will apply for any
    given user input?

    The cell you want is:
    gameboard[3 * (row - 1 ) + (column - 1)]
    so one test can replace 9. In reworking this, I calculate this
    location once and save it as 'loc', since I'll be needing the location
    later.

    Then there's the question of the test if the cell is occupied:

    if gameboard[3] != ('O' or 'X'):

    This doesn't test if the cell contains an 'O' or an 'X', what it
    actually tests is whether the cell contains an 'O'. The reason for
    this is that Python sees the 'or' operator and tests if the left hand
    side is True, which, since it is a string, tests if the string is
    empty, which it isn't. Python, when given EXPR or EXPR returns the
    left hand EXPR if it is True, otherwise it returns the right hand
    expression. Also, since you know that unoccupied cells contain a
    space, why not test for the cell containing a space, instead of
    testing that it doesn't contain an 'O' and it doesn't contain an 'X'?,
    no 'or' operator required? If you must use 'or', then you'd have to
    write:

    if gameboard[3] == 'O' or gameboard[3] == 'X':
    print "This cell is already filled."

    The next point is the entire user input section. You have a while loop
    waiting until the game is won, which gets the user input at the top
    and goes through the entire rest of the code on every user input. It's
    clearer to handle the user input within a smaller loop which runs
    forever (while 1:) prompting for input and which uses the 'break'
    statement to drop out of the loop when the input is valid. If I were
    writing this myself, I'd probably extract it into a function, simply
    to separate it from the game playing code.

    You display the current board with a very lengthy print statement,
    naming each cell to be printed and surrounding it with individual
    ']''s. But this is the sort of job computers do well. What you
    actually want to do is print three lines, one for board[0] through
    board[2], another for board[3] through board[5] and the last for
    board[6] through board[8].

    print "[%s][%s][%s]" % (board[0], board[1], board[2]) etc.

    would work in three clearer lines, but we may as well let the computer
    do the calculation of the indices using a for loop. A final pair of
    improvements is to not type the [%s] 3 times, nor the name of the
    board 3 times. We can get the format string using the "" * n notation:

    '[%s]' * 3

    We can get the cells to print as a string slice:

    tuple(board[r : r + 3])

    I leave it to you to find out why the tuple() call is needed.

    When getting the input, you use the function input(), which sounds
    reasonable, given the name, but in fact is not at all what you
    want. This function gets a line of input from the user and evaluates
    it, returning the result. You could enter '1+2' for the row and Python
    would return 3. If you type an invalid expression or a letter, Python
    will terminate your program, which seems a bit drastic. Instead, I
    used raw_input, which simply gets a string, then checked if the string
    was one of '1', '2', or '3', using the 'in' operator on the sequence
    of valid values. While not the sort of validation you'd use in may
    places, when the number of accptable inputs is this small, the
    resulting code becomes instantly understandable. You can now type
    almost anything except control-C and it will not stop the program.

    After validating the input, you check that the cell is free, but,
    since you don't have a loop to get the input, you have to reset the
    player and drop to the bottom of the program just to go back to the
    main while loop. Keeping all the validation in a single loop you can't
    leave until you come up with a good location.

    The final part (and the one you originally asked about) was the pair
    of if statements you wrote to check if the game had been one. Once
    again, you misunderstood the logical operators, 'and' in this
    case. The result of the expression

    (gameboard[3] and gameboard[4] and gameboard[5]) == 'O'

    is not a test of whether the three cells all contain 'O', it is a test
    of whether the first two cells contain a non-empty string _and_ the
    last cell contains 'O'.

    Naming the 8 possible ways to win at tictactoe in a single if
    statement seems wrong. The statment borders on the unreadable.

    Instead, consider what winning means: you have three identical cells
    not containing spaces either horizontally ( in board, board[i + 1]
    and board[i +2] for values of i of 0, 3, or 6. Another way of winning
    is to have three identical non-space cells in a column = board,
    board[i + 3], board[i + 6] for values of i of 0, 1, or 2. The third
    way to win is to have a diagonal set of three identical non-space
    cells = board[0], board[4], board[8] or board[2], board[4], board[6]

    The first two types of wins cry out for a for loop to try the
    horizontal or vertical wins, with a little simple work, the same for
    loop over values of 0, 1, 2 can do both types. I'm too lazy to think
    up a clever way of doing the two diagonals.

    The actual test looks like:

    board == board[i + 1] == board[i + 2] and board != ' '

    taking advantage of the way Python handles multiple '=='s strung out
    together ( in effect a == b == c is treated as a == b and b == c ).

    The resulting rework of the program follows - it's by no means optimal
    but it's 1/3 the length and, in my opinion, much more readable.

    It still suffers from having too many while loops depending on break
    statements, but changing that would require me writing it over from
    scratch.

    # TIC TAC TOE
    # Started: 10/31/05
    # Ended: still in progress

    # There's no need for the variable 'loop'
    while 1:
    print "TIC TAC TOE"
    print "1 - Play Multiplayer"
    print "2 - Quit"
    option = raw_input("> ")

    if option == '2':
    break

    if option != '1':
    continue

    # MAIN GAME LOOP
    print "Rules: You will alternate turns."
    print "On your turn, you can place your letter (O = Player 1 or X = Player 2)",
    print "in any unoccupied square."
    print "The first to get 3 in a row wins. Good luck!"

    board = [' ',' ',' ',' ',' ',' ',' ',' ',' ']

    turns = 0
    player = 0
    win = 0

    # loop until the board is full
    while turns < 9:
    # loop until we have valid input
    while 1 :
    print " "
    print "Player %d" % (player + 1)
    print " "

    for r in range(0, 9, 3):
    print "[%s]" * 3 % tuple(board[r : r + 3])

    row = raw_input("Row: ")
    column = raw_input("Column: ")

    if row not in ['1', '2', '3' ] or \
    column not in ['1', '2', '3']:
    print "Invalid selection, please try again"
    continue

    # convert row and column to array index
    loc = 3 * (int(row) - 1) + int(column) - 1
    if board[loc] == ' ':
    break

    print "This cell is already filled."

    # now we have a valid location, fill it in
    board[loc] = ['X', 'O'][player]

    for i in range(3):
    # check for horizontal win
    if board[3 * i] == board[ 3 * i + 1] == board[3 * i + 2] and \
    board[3 * i] != ' ':
    win = 1
    break
    if board == board[i + 3] == board[i + 6] and \
    board != ' ':
    win = 1
    break
    else:
    # no horizontal or vertical win yet
    # test the diagonals
    if (board[0] == board[4] == board[8] or
    board[2] == board[4] == board[6]) and board[4] != ' ':
    win = 1

    if win:
    break

    # if we reach here, the game isn't over, flip players
    turns += 1
    player = (player + 1) % 2

    # the game has finished
    if win:
    print "\nPlayer %d wins!" % player
    for r in range(0, 9, 3):
    print "[%s]" * 3 % tuple (board[r : r + 3])
    print
    else:
    print "Tie"
     
    Jim Segrave, Nov 2, 2005
    #8
  9. The code's indentation was fine - I forgot to declare cell in player
    two's section and not just in player one.

    The code (including the win check, for once!) is working. The last
    obstacle is my tie checker; it doesn't seem to like what I have:

    if ((gameboard[0:9] is 'X' or 'O') and (win == 0)):
    print "Tie."
    win = 2

    Will the [0:9] range not work in this? Should I write out each option?
    Or is there some way to check if all things in the list are NOT ' '?

    I tried

    if ((gameboard[0:9] is not ' '))

    but it didn't like that - once again maybe the range is the culprit.
    Hopefully this will be the last help I need, thanks once again.
     
    ale.of.ginger, Nov 2, 2005
    #9
  10. ale.of.ginger

    Mike Meyer Guest

    Yes, those tests are not doing what you want them to do. On the other
    hand, you can still test this in one swell foop with "in":

    if ' ' not in gameboard and win == 0:
    print "Tie."
    win = 2

    That does assume that gameboard and gameboard[0:9] are the same
    thing. But they should be. For that matter, the defaults for the slice
    should be 0 and 9, so that gameboard[:] is the same thing yet
    again. But since you're not changing the board but reading it, there's
    no need to make a copy, which is what the sliced versions do.

    <mike
     
    Mike Meyer, Nov 2, 2005
    #10
  11. adding a

    print gameboard[cell]

    just before that if-statement is a quick way to check that the cell really
    contains the right thing.

    </F>
     
    Fredrik Lundh, Nov 2, 2005
    #11
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.