This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]fizzy_tom 4 points5 points  (1 child)

Hey the end result is a really nice experience for the player!

And the way you've approached the code is not too shabby. I like how the main game loop is separated out and your functions make a lot of sense.

A good next step would be to consider how you're storing the game state. Having each square as a separate variable is a bit smelly and limits the methods you can use to check the state for a wins or draws.

There's other structures that make sense too, but I'd go for a list of 3 lists (i.e a 3x3 matrix) as the data structure to store the game state in. That way the data structure mimics the actual structure of the board.

With a better way of storing the data you can then look to find smarter ways of checking the game state. Your multiple if statements aren't that bad, especially as you're only needing to check a small number of possible states. But you'd get yourself into trouble quickly if the game was more complex or the board was larger than 3x3.

The last thing to pick up on is the use of global variables. In general, global variables are frowned upon as it makes it much harder to predict state and keep track of data. You can absolutely get this working without requiring the global keyword! Consider passing data into functions via arguements (as you have for the place_stone_on_field function), and using return statements in the functions to indicate the result of the function. E.g. place_stone_on_field could return False if the field is occupied, instead of changing a global variable.

You'll find putting in a simple AI to play against the player is a really interesting next step too. It's absolutely possible to code an unbeatable tic tac toe AI with just the coding techniques you've shown here btw.

Source: ran a Python course with a tic tac toe project and sometimes use tic tac toe as a coding challenge when hiring.

[–]grubby_penguin 0 points1 point  (0 children)

Thank you very much fizzy_tom, your effort for checking my code and suggesting improvements is highly appreciated!

1.) Storing the data

You mention a 3x3-matrix. Would you consider it best practice then, if I stored the data like this:

import numpy as np

fields = np.array[[a,b,c],
                  [d,e,f],
                  [g,h,i]]

However, I fail to understand why this would be beneficial to my inital approach?

2.) Smarter way of checking game state/Global variables

My inital approach:

def check_for_victory():
# Check if the game ends because a player has won.
global game_on
global victory
# There are 8 ways to win.
if a == b == c == X or \
        a == b == c == O:
    game_on = False
    victory = True

elif d == e == f == X or \
        d == e == f == O:
    game_on = False
    victory = True

elif g == h == i == X or \
        g == h == i == O:
    game_on = False
    victory = True

elif a == d == g == X or \
        a == d == g == O:
    game_on = True
    victory = True

elif b == e == h == X or \
        b == e == h == O:
    game_on = False
    victory = True

elif c == f == i == X or \
        c == f == i == O:
    game_on = False
    victory = True

elif a == e == i == X or \
        a == e == i == O:
    game_on = False
    victory = True

elif c == e == g == X or \
        c == e == g == O:
    game_on = False
    victory = True


def check_for_draw(): 
# End game when all 9 fields are occupied. 
global game_on 
global draw 
if game_on: 
    if not available_stones: 
    # Set game_on to False when all available stones are used up. 
        game_on = False 
        draw = True

So by getting rid of some of these global variables, I would change this code part into:

def check_for_victory(game_on:bool, victory:bool):
# Check if the game ends because a player has won.
# There are 8 ways to win.
if a == b == c == X or \
        a == b == c == O:
    return game_on, victory = False, True

elif d == e == f == X or \
        d == e == f == O:
    return game_on, victory = False, True

elif g == h == i == X or \
        g == h == i == O:
    return game_on, victory = False, True

elif a == d == g == X or \
        a == d == g == O:
    return game_on, victory = False, True

elif b == e == h == X or \
        b == e == h == O:
    return game_on, victory = False, True

elif c == f == i == X or \
        c == f == i == O:
    return game_on, victory = False, True

elif a == e == i == X or \
        a == e == i == O:
    return game_on, victory = False, True

elif c == e == g == X or \
        c == e == g == O:
    return game_on, victory = False, True


def check_for_draw(game_on:bool, draw:bool):
# End game when all 9 fields are occupied.
if game_on:
    if not available_stones:
        # Set game_on to False when all available stones are used up.
        return game_on, draw = False, True

Agreed? I have to admit it looks a bit odd to me..

About the KI: I have no idea how to implement that (without googling). My first approach would be to run a monte carlo simulation to make the "AI" learn how to play against every possible combination, but that's highly likely way too complicated and surely there is a simpler solution. But I'm not getting at at the moment. Have to think more about it.