Coding Help With Coding A Tic Tac Toe Game

The following is something I’ve been working on. I know to the experienced folk it will look like a big mess, but I’m self-taught and new, so please be gentle… in line 88, which reads “if player ==2:”, I get an ‘unexpected unindent’ error, but I am not sure why. Everything worked fine for player one, but now it’s giving me an error. Can anyone help please?

col1_row1=" "
col1_row2=" "
col1_row3=" "
col2_row1=" "
col2_row2=" "
col2_row3=" "
col3_row1=" "
col3_row2=" "
col3_row3=" "
player = 1

while True:

#Show current standings
print(col1_row1,"|",col2_row1,"|",col3_row1)
print("------------")
print(col1_row2,"|",col2_row2,"|",col3_row2)
print("------------")
print(col1_row3, "|",col2_row3,"|",col3_row3)

#player 1 turn
if player == 1:
    try:
      xcol = int(input('In which column would you like your X?'))
      xrow = int(input('In which row would you like your X?'))
      if xcol > 0 and xcol < 4 and xrow > 0 and xrow < 4:
    #Check Availability and Claim
          if xcol == 1:
            if xrow == 1:
                if col1_row1 == "  ":
                    col1_row1 = " X"
                    player = 2
                else:
                    print("That spot is already taken.")
            if xrow == 2:
                if col1_row2 == "  ":
                    col1_row2 = " X"
                    player = 2
                else:
                    print("This spot is already taken.")
            if xrow == 3:
                if col1_row3 == "  ":
                    col1_row3 = " X"
                    player = 2
                else:
                    print("This spot is already taken.")
          if xcol == 2:
            if xrow ==1:
                if col2_row1 == "  ":
                    col2_row1 = " X"
                    player = 2
                else:
                    print("This spot is already taken.")
            if xrow == 2:
                if col2_row2 == "  ":
                    col2_row2 = " X"
                    player = 2
                else:
                    print("This spot is already taken.")
            if xrow == 3:
                if col2_row3 =="  ":
                    col2_row3 = " X"
                    player = 2
                else:
                    print("This spot is already taken.")
          if xcol == 3:
              if xrow == 1:
                  if col3_row1 == "  ":
                      col3_row1 = " X"
                      player = 2
                  else:
                      print("This spot is already taken.")
              if xrow == 2:
                  if col3_row2 == "  ":
                      col3_row2 = " X"
                      player = 2
                  else:
                      print("This spot is already taken.")
              if xrow == 3:
                  if col3_row3 == "  ":
                      col3_row3 = " X"
                      player = 2
                  else:
                      print("This spot is already taken.")
      else:
          print("Invalid entry. Please try again.")
#player 2 turn
if player == 2:
    try:
      ocol = int(input('In which column would you like your O?'))
      orow = int(input('In which row would you like your O?'))
      if ocol > 0 and ocol < 4 and orow > 0 and orow < 4:
    #Check Availability and Claim
          if ocol == 1:
            if orow == 1:
                if col1_row1 == "  ":
                    col1_row1 = " O"
                    player = 1'
                else:
                    print("That spot is already taken.")
            if orow == 2:
                if col1_row2 == "  ":
                    col1_row2 = " O"
                    player = 1
                else:
                    print("This spot is already taken.")
            if orow == 3:
                if col1_row3 == "  ":
                    col1_row3 = " O"
                    player = 1
                else:
                    print("This spot is already taken.")
          if ocol == 2:
            if orow ==1:
                if col2_row1 == "  ":
                    col2_row1 = " O"
                    player = 1
                else:
                    print("This spot is already taken.")
            if orow == 2:
                if col2_row2 == "  ":
                    col2_row2 = " O"
                    player = 1
                else:
                    print("This spot is already taken.")
            if orow == 3:
                if col2_row3 =="  ":
                    col2_row3 = " O"
                    player = 1
                else:
                    print("This spot is already taken.")
          if ocol == 3:
              if orow == 1:
                  if col3_row1 == "  ":
                      col3_row1 = " O"
                      player = 1
                  else:
                      print("This spot is already taken.")
              if orow == 2:
                  if col3_row2 == "  ":
                      col3_row2 = " O"
                      player = 1
                  else:
                      print("This spot is already taken.")
              if orow == 3:
                  if col3_row3 == "  ":
                      col3_row3 = " O"
                      player = 1
                  else:
                      print("This spot is already taken.")
      else:
          print("Invalid entry. Please try again.")
    except:
        print("Invalid Entry. Please try again.")
1 Like

Welcome, Brian! We’ll try not to be too hard on you :grinning_face_with_smiling_eyes:

While the error message highlights the beginning of the second player’s block, as is often the case, the true error is on a line well above. In particular, if Python thinks your indent level is wrong, then something above must be missing a block that Python expects. In this case, your player == 1 block has a try block, but is missing the corresponding except block that Python expects (or, else, a finally block), which your player == 2 block has. Copying that over, everything works, with one exception: the very first player = 1' statement under the player == 2 block has a stray single quote character '. Remove that, and everything works.

To note, the code contains a huge amount of repetition, and many layers of nested statements, which makes these errors much easier to make and much hard to spot, as well as your code much harder to understand, fix and improve—just by breaking things up into functions, e.g. one function to print the board, one function to handle the input, one function to process the moves, and a main function to run your loop and call the other functions, your code can be many times shorter and easier to maintain. Combined with that, you can store your cell contents as a nested list, rather than in individual variables, which also simplifies things greatly.

Also, some indents use four spaces and some use two, which combined with all the repetition, makes it even harder to follow and easier to not spot when you’ve missed an indent (as happened here); I suggest being consistent and always using four spaces, as is the standard for Python.

Finally, make sure to never use a bare except (unless you know exactly what you’re doing); always use at least except Exception, as otherwise you’ll be catching KeyboardInterrupt and other such non-error exceptions, which makes it impossible for users to quit out of the game

Just for fun, to give you some ideas, here’s one way you could structure your game, that does the same thing as the code above but in less than thirty lines, versus the over 150 above. In addition, it actually supports any size of game board just by changing the SIZE constant, and any number of players with different symbols by modifying the PLAYERS lookup table. Also, it better handles the retry logic, formatting and a few other things.

SIZE = 3  # How big the board is, 3 x 3 for regular tic tac toe
PLAYERS = {1: "X", 2: "O"}  # The players to use and their symbols

def print_board_status(board_status):
    sep = "\n" + "-" * (SIZE * 4 - 3) + "\n"
    print("\n" + sep.join(" | ".join(col) for col in board_status) + "\n")

def get_player_input():
    while True:
        xcol = int(input(f'In which column do you want your {PLAYERS[player]}?'))
        xrow = int(input(f'In which row do you want your {PLAYERS[player]}?'))
        if 0 < xcol < (SIZE + 1) and 0 < xrow < (SIZE + 1):
            return xcol - 1, xrow - 1  # Python uses zero-indexing
        print("Invalid entry. Please try again.")

def play_one_round(board_status, player):
    while True:
        xcol, xrow = get_player_input()
        if not board_status[xrow][xcol].strip():  # If xcol, xrow not occupied
            board_status[xrow][xcol] = PLAYERS[player]
            break
        print("This spot is already taken. Please try again.")

board_status = [[" "] * SIZE for __ in range(SIZE)]
player = 1
while True:
    print_board_status(board_status)
    play_one_round(board_status, player)
    player = player % len(PLAYERS) + 1

There are some improvements you might want to make here. In particular, what if you wanted to check for a winner? For that, you might want to write a function check_winner that is called after each round, and terminates the game with a special message if someone wins, or if all squares are occupied.

What if you want to offer multiple games? Well, you might want to encapsulate the top-level code into another function (perhaps play_game()), and at the top level (or better yet, in a main() function) handles asking the user if they want to play again inside another while loop.

You could even ask the user the size of the game they wanted to play at the beginning, and how many players they have, and set those accordingly or pass those as arguments to play_game().

Have fun!

I have so much to learn! I’m trying to google about half of the commands you put. Thank you for all the help though! Where did you learn so much about Python? I’ve been using the w3 school site

The following is something I’ve been working on. I know to the
experienced folk it will look like a big mess, but I’m self-taught and
new, so please be gentle… in line 88, which reads “if player ==2:”, I
get an ‘unexpected unindent’ error, but I am not sure why.

Let’s look at this bit first. You code goes like this:

 #player 1 turn
 if player == 1:
     try:
    [...]
 #player 2 turn
 if player == 2:

There is no “except” to close the “try” before this if-statement. As
such, the code does not make sense. The parser can go 2 ways here:
assume you left off the “except”, and warn about that, or assume it
hasn’t reached the “except” yet and assume that the if-statement is too
far out (not indented enough).

It has taken the second approach.

Now to other issues:

Others have mentioned not using a bare “except”, but even the “except
Exception” suggestion is too loose.

Almost everything which “goes wrong” in Python is indicated with an
exception. The basic rule is: catch only what you expect may go wrong
and can handle. Let everything else go out. Otherwise you’ll never
find various probems. Even misspelling a variab;e name in Python raises
an exception (a “NameError”), so a bare except hides even mistakes like
that.

Partnered with “catch only what you can accomodate” is to make
try/excepts as narrow as possible. That way you have confidence about
where the error occurred.

So, your except clause does this:

except:
    print("Invalid Entry. Please try again.")

but this will catch anything, from anywhere inside the try/except.

It is better to write something narrow, like this:

try:
    ocol = int(input('In which column would you like your O?'))
    orow = int(input('In which row would you like your O?'))
except ValueError as e:
    print("Invalid entry:", e)
    continue
... the rest of the code here ...

This version:

  • encapsulates only the input calls, since that is all you’re trying to
    handle
  • catches only ValueError, which is what you get if the int() function
    fails to convert the input value to an integer
  • includes the exception in the print() call, just to be clear - also,
    if you get some ValueError you were not handling you’ll see it
  • does a “continue”, which starts the loop again, skipping all the code
    below

That last bit means you don’t need to indent the code below as much.

By keeping the try/except very small you also make it clear what’s being
handled. The reader (yourself included) can see what is does instead of
having to look through a lot of code for the end.

col1_row1=" "
col1_row2=" "
col1_row3=" "
col2_row1=" "
col2_row2=" "
col2_row3=" "
col3_row1=" "
col3_row2=" "
col3_row3=" "

Here you’ve got a variable for each cell in the 3x3 grid. If you were
playing chess this would get pretty tedious pretty fast (8x8 grid).

The standard approach with a grid is a 2 dimensional array. Python does
not have, directly, 2 dimensional arrays, but it does have lists (1
dimensional variable sized arrays, and lists can contain lists for the
second dimension) and dicts (mappings of keys to values).

You could prepare your 2-D array like this:

grid = [ [ "  ", "  ", "  " ],
         [ "  ", "  ", "  " ],
         [ "  ", "  ", "  " ],
       ]

This means 'grid" is a single list, each element of which is itself a
3-element list.

You can access elements of this as: grid[1][2]

Since Python indices start from 0, that is the third column of the
second row. There are 2 index operations happened there:

  • grid[1] accesses the middle list in grid
  • [2] accesses the third element of that inner list
    so this behaves list a 2-dimensional array.

With your “xcol” and “xrow” which count from 1 through 3 you’d access
the cell as:

cell = grid[xrow-1][xcol-1]

to adjust for input values 1-3 to row/column indices of 0-2.

This means that your elaborate if/else chain for each cell can collapse
to a single test:

if cell == "  ":
    grid[xrow-1][xcol-1] = " X"
    player = 2
else:
    print("That spot is already taken.")

See if that helps.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

Mostly just by using it a lot for the past several years :grinning_face_with_smiling_eyes: As in most things, I learned primarily by real-world experience, using Python for my own work (mostly scientific computing) and participating in open source projects like Spyder; I read the basic documentation and tutorial but beyond that, I would mostly learn new things whenever I did something new, look it up in the docs or Stack Overflow, learn from other better programmers’ code, or look for ways to improve my own. Especially working on Spyder exposed me to a ton of code and seeing how things were done, some great and some not as great, but still generally much better than what I could do at the time. Now I’m the one who helps mentor new contributors, but it was pretty overwhelming at first.

W3schools is a decent resource, though the quality is not always the best. I’d definitely avoid GeeksForGeeks though, since anyone can write those articles I’ve found a ton of outright bad info on there. For specific things, Stack Overflow is often best because the community upvotes the highest quality answers and contributes toward improving them, alongside reading the official documentation.

In any case, I rewrote my example to make some parts clearer and to add extensive comments describing everything that is done, to help make it easier to understand both the what and the why. I also added exception handling, which I realize was missing from my original.

# In general ALL_CAPS names are used for module-level constants
# We use SIZE throughout our program to stand in for the size of the board
# So we only have to change it one place for a different size game, or can
# Even make it a variable.
SIZE = 3  # How big the board is, 3 x 3 for regular tic tac toe

# This is a dictionary that we use to map player numbers to symbols
# Its length also tells us how many players we have
# Its better to have a top-level lookup table like this so that you
# don't have to hardcode the numbers and symbols anywhere
# Like with the size, this makes it easier to change later
PLAYERS = {1: "X", 2: "O"}  # The players to use and their symbols

# The def keyword creates a new funtion. A function is just like what you
# might be used to in math. It takes zero or more inputs (arguments)
# and returns zero or more outputs (return values), possibly doing other things
# too (side effects), like printing and modifying (mutating) its inputs
def print_board_status(board_status):
    """Print the current state of the board."""
    # This is the seperator line between board rows. We construct it out of
    # "-" characters, of a length determined by the size (len()) of the board
    # and with newlines (blank lines, "\n") before and after it
    # Here, we use the fact that a string times a number makes a
    # repeating string of that length, i.e. "-" * 3 == "---"
    seperator = "\n" + "-" * (len(board_status) * 4 - 3) + "\n"
    # In this line, we use a list comprehension (essentially a for loop that
    # automatically makes a list) to format each row as a string.
    # Board status is a list of rows, each row in turn being a list of
    # one value for each column. Here, for each row in the board status
    # we use "join" which converts each element to a string and adds a
    # seperator, in this case " | " between them. Each stringified row
    # is then added to a list of rows
    rows = [" | ".join(row) for row in board_status]
    # Finally, we use join again to join our rows, seperated by the
    # seperator we made above, and with blank lines top and bottom
    print("\n" + seperator.join(rows) + "\n")


def get_player_input(player, size=SIZE):
    """Ask the player what row and column they want to claim."""
    while True:
        # Same as in your original code, get the input with a message and
        # conver it to an integer, except we use format strings (f-strings)
        # to inject the symbol of the current player. Format strings have an
        # f in front of the opening quote, and inside {}s you can include
        # variables and Python code that will be evaluated to be part of
        # the string. Here, we get the symbol of the current player from the
        # PLAYERS lookup table (dictionary), and insert that into the string.
        # We use a try-except, as you did, to catch when users enter a non
        # integer value, print a helpful error message and try again
        try:
            ncol = int(input(f'In which column do you want your {PLAYERS[player]}?'))
            nrow = int(input(f'In which row do you want your {PLAYERS[player]}?'))
        except ValueError:
            print("Please enter an integer number")
            continue
        # Here, we can use < as a ternery operator, so we can write an
        # inequality just as we might on paper. If the x or y coordinates
        # given are between 1 and the size of the square, inclusive, then
        # we return them. Otherwise, we print an error and tell the user to
        # try again
        if 1 <= ncol <= size and 1 <= nrow <= size:
            # Here, we return two different variables (actually, a tuple)
            # with commas seperating them. The return statement both
            # returns the values we want and breaks the while loop
            # Also, note that Python uses 0 indexing, so the first position
            # in a list is 0, the second is 1, etc., whereas we humans usually
            # use 1-indexing, where the first row is 1, the second is 2, etc.
            # Therefore, we subtract 1 from the row and column position to
            # account for this.
            return ncol - 1, nrow - 1  # Python uses zero-indexing
        # Here, we're using f-strings again, to insert the size
        print(f"Please enter values between 1 and {size}.")


def play_one_round(board_status, player, size=SIZE):
    """Play one round of the tic tac toe game."""
    while True:
        # Here, we call our get_player_input() function and "unpack" its
        # result into the two different variables we are expecting
        ncol, nrow = get_player_input(player=player, size=size)
        # Here, we do a few things all at once: We get the current contents
        # of the cell at the specified row and column, and use strip to
        # remove whitespace (i.e. if it is an empty cell). Then, we
        # test whether it is true or false; an empty string is false while
        # a string that has a value, like "X" or "O", evaluates true
        # Finally, we invert that with not, so our if statement evaluates
        # true and runs the next line if the selected cell of the board
        # does _not_ already contain a character. Otherwise, prints an error
        # and asks the player for input again
        if not board_status[nrow][ncol].strip():  # If xcol, xrow not occupied
            # Inserts the current player's symbol (looked up in the PLAYERS
            # dictionary) into the list element at the specified row and
            # column
            board_status[nrow][ncol] = PLAYERS[player]
            # Breaks out of the while loop
            break
        print("This spot is already taken. Please try again.")


def play_game(size=SIZE):
    """Set up and play one tic tac toe game."""
    # Set up our board. There's a lot going on here in this one line.
    # Multiplying a list duplicates its elements, so [" "] * 3 creates a
    # list [" ", " ", " "]. We use this inside a list comprehension to
    # create a list containing three (our size) tested lists for our rows,
    # which each contain three blank spaces, one for each column.
    # We use range as you did, with __ as a dummy variable, since the
    # index doesn't actually get used.
    board_status = [[" "] * size for __ in range(size)]
    # Start with the first player
    player = 1
    while True:
        print_board_status(board_status=board_status)
        play_one_round(board_status=board_status, player=player, size=size)
        # Here, we increment the player number, wrapping it around if it
        # exceeds the number of players using the mod (%) operator.
        # We add one, since the first player has number 1 instead of zero
        player = player % len(PLAYERS) + 1
        # You probably want to call another function here to check if one
        # player has one, or the board is all filled up, and end the game
        # with an appropriate message.


def main():
    """Start playing tic tac toe."""
    # Our main function, which we execute when the file is run as a script
    # Right now, this just calls the play_game() function with the default
    # size, but you could add an input() to ask the player what size game they
    # want to play, and once you add a check for one player winning,
    # you might want to put this in another while look to ask the player
    # whether they want to play again once the game is over
    # Note that the name main is just convention for a module's main function,
    # i.e. what gets called if the module is run as a script.
    play_game()


# This checks if the current module is being run as a script, e.g.
# python tictactoe.py, rather than being imported by another module,
# e.g. import tictactoe. That way, you can use the functions of this module
# in another module, while also being able to automatically start a game
# if you run this module as a script
if __name__ == "__main__":
    # You can call anything here, it doesn't have to be main(), but it is
    # good to follow convention.
    # You could also put all your logic right inside this block rather than
    # in a function, but it is still useful to keep things organized,
    # avoid errors and allow you to run it from another module.
    main()
1 Like

Good point of course; I didn’t want to overwhelm @Kikari with detail, and wanted to stress that it is most critical to never, ever use a bare except (particularly in this case, as it leaves users trapped), and ended up forgoing exception handling entirely in my simplified example, which I corrected in my later expanded one just now.

Just to be clear, for all those not reading on email, this translates to:

while True:
    try:
        ocol = int(input('In which column would you like your O?'))
        orow = int(input('In which row would you like your O?'))
    except ValueError as e:
        print('Invalid entry:', e)
        continue
    # … the rest of the code here …

Thanks, I manked the formatting. Forgot to leave a blank line before the
indented code. I’ve edited the post for fix the formatting. - Cameron

1 Like

Hey, I made a different program to help me get familiar with functions (defining/using/etc). Would you mind looking over it and telling me how I did?

I can post the code here if you are willing

I can’t speak for Cameron but I can give it a shot. However, if you do so, I’d suggest describing specifically what you’re trying to illustrate, the issues/questions/uncertainties you had and the particular areas you’re looking for comment on, so my feedback will be the most helpful and tailored to what you’re looking for, and to help avoid a firehose of general code review commentary, hehe.

Well, the main thing I’m hoping to get from you is feedback on how I did with consolidation of the code compared to the tic tac toe game as well as pointers on of there is anything you feel I could have coded better. As I said in the beginning, I am self taught so I’m struggling to improve, but I’ll get there. The last feedback you gave me was so awesome I saved the program you sent just so I could use your code as a way to figure out how it works and your comments helped me out a lot too.

1 Like

import random
SIZE = 10
DOOR = 1
ifDead = list()
LOOP = True
DIF_MULT = 0

def intro():
print("\n Welcome to:","\n"," Doors of Death")
print("\n \n In this game your goal is simple. Survive. The first floor will hold as many doors as you decide. Out of that amount, a specific percentage of them (Depending on which difficulty you select) will contain death doors, which will kill you upon opening. Every time you select a safe door you will proceed to the next floor which will have half of the doors the previous floor had. Your mission is to choose the safe door on the final floor which contains only two doors. Good Luck! \n\n")

#Lets the player select difficulty
def difficulty():
print(" Easy: 25% of doors kill you","\n",“Medium: 50% of doors kill you”,"\n",“Hard: 75% of doors kill you”)
global DIF_MULT
difficulty = input("\n Which difficulty would you like? “)
while difficulty.lower() != “easy” and difficulty.lower() != “medium” and difficulty.lower() != “hard”:
error()
difficulty = input(”\n Which difficulty would you like? ")
if difficulty.lower() == “easy”:
DIF_MULT = .25
if difficulty.lower() == “medium”:
DIF_MULT = .5
if difficulty.lower() == “hard”:
DIF_MULT = .75

def error():
print("\n",“Invalid Entry. Please try again.”)

#set all doors as safe
def remaining_doors(door,size):
while door <= size:
ifDead.insert(door - 1, 1)
door = door + 1

#determine death doors
def death_doors(door,size):
global DIF_MULT
death = int(size*DIF_MULT)
while door <= death:
deathDoor = random.randint(1,size)
if ifDead[deathDoor - 1] == 1:
ifDead[deathDoor - 1] = 2
door = door + 1

#Player chooses a door
def player_turn(size):
global LOOP
global RESULT
while True:
try:
choice = int(input(f’\nPlease choose a door between 1 and {size}: ')) - 1
if 0 <= choice <= size:
if ifDead[choice] == 1:
global SIZE
SIZE = int(SIZE * .5)
if SIZE == 1:
LOOP = False
RESULT = “CONGRATULATIONS! YOU SURVIVED!”
else:
print("\n",“You made it past this floor. The doors have been cut in half.”)
break
else:
print(“I’m so very sorry, but you just died…”)
RESULT = “YOU LOSE”
LOOP = False
break
else:
error()
except:
error()

#Program execution begins
intro()
difficulty()

#Player chooses the initial amount of doors.
while True:
try:
SIZE = int(input(’\n How many doors would you like to start with? '))
if SIZE >= 2:
break
else:
error()
except:
error()

#Repeats until loss or victory
while LOOP == True:
remaining_doors(DOOR,SIZE)
death_doors(DOOR, SIZE)
player_turn(SIZE)
ifDead = list()
print(RESULT)

#I know everything the user sees is a bit rough still, but I was focusing more on the coding side. I feel I’m understanding functions a lot more at least.

That’s fantastic, really glad to hear! Normally we try to avoid providing full code samples of how to do things, rather than giving insight, examples, snippets and explanation, to help users actually learn rather than just copying and pasting, but based on what you shared above, you seemed like someone who was likely to to take it as a learning opportunity to see how you could improve (especially since that was the purpose of your work, rather than completing a school assignment).

As for your code, its really hard for those of us not on plain-text email to see what it actually looks like, since unless you format it as code (select it and click the </> button in the toolbar, or put triple backticks above and below, like this:

```python
YOUR CODE HERE
```

the indents and other formatting doesn’t get preserved, which as you know is critical in Python and makes it impossible to understand what you mean without guessing. You can see if it looks like my examples in the preview. Thanks!

import random
SIZE = 10
DOOR = 1
ifDead = list()
LOOP = True
DIF_MULT = 0

def intro():
    print("\n Welcome to:","\n","        Doors of Death")
    print("\n \n In this game your goal is simple. Survive. The first floor will hold as many doors as you decide. Out of that amount, a specific percentage of them (Depending on which difficulty you select) will contain death doors, which will kill you upon opening. Every time you select a safe door you will proceed to the next floor which will have half of the doors the previous floor had. Your mission is to choose the safe door on the final floor which contains only two doors. Good Luck! \n\n")

#Lets the player select difficulty
def difficulty():
    print(" Easy: 25% of doors kill you","\n","Medium: 50% of doors kill you","\n","Hard: 75% of doors kill you")
    global DIF_MULT
    difficulty = input("\n Which difficulty would you like?   ")
    while difficulty.lower() != "easy" and difficulty.lower() != "medium" and difficulty.lower() != "hard":
        error()
        difficulty = input("\n Which difficulty would you like?   ")
    if difficulty.lower() == "easy":
        DIF_MULT = .25
    if difficulty.lower() == "medium":
        DIF_MULT = .5
    if difficulty.lower() == "hard":
        DIF_MULT = .75

def error():
    print("\n","Invalid Entry. Please try again.")

#set all doors as safe
def remaining_doors(door,size):
    while door <= size:
        ifDead.insert(door - 1, 1)
        door = door + 1

#determine death doors
def death_doors(door,size):
    global DIF_MULT
    death = int(size*DIF_MULT)
    while door <= death:
        deathDoor = random.randint(1,size)
        if ifDead[deathDoor - 1] == 1:
                ifDead[deathDoor - 1] = 2
                door = door + 1
                
#Player chooses a door
def player_turn(size):
    global LOOP
    global RESULT
    while True:
        try:    
            choice = int(input(f'\nPlease choose a door between 1 and {size}:   ')) - 1
            if 0 <= choice <= size:
                if ifDead[choice] == 1:
                    global SIZE
                    SIZE = int(SIZE * .5)
                    if SIZE == 1:
                        LOOP = False
                        RESULT = "CONGRATULATIONS! YOU SURVIVED!"
                    else:
                        print("\n","You made it past this floor. The doors have been cut in half.")
                    break
                else:
                    print("I'm so very sorry, but you just died..")
                    RESULT = "YOU LOSE"
                    LOOP = False
                    break
            else:
                    error()
        except:
                error()                

#Program execution begins
intro()
difficulty()

#Player chooses the initial amount of doors.
while True:                
    try:
        SIZE = int(input('\n How many doors would you like to start with?   '))
        if SIZE >= 2:
            break
        else:
            error()
    except:
        error()  

#Repeats until loss or victory     
while LOOP == True:
    remaining_doors(DOOR,SIZE)
    death_doors(DOOR, SIZE)
    player_turn(SIZE)
    ifDead = list()
print(RESULT)

#I know everything the user sees is a bit rough still, but I was focusing more on the coding side. I feel I'm understanding functions a lot more at least.

In this one, is there a way to simplify the class and enemy type sections at all, or is that the nature of the beast? It felt repetitive, but it still seemed too different to try to clump together.

import random
import subprocess
Class = 0
Enemy = 0
#Lists are in the order-
#MinHp,MaxHp,MinMp,MaxMp,MinStr,MaxStr,MinWis,MaxWis,MinDef,MaxDef,MinDex,MaxDex
RogueStats = [200,225,10,20,5,10,1,5,10,15,20,25]
WizardStats = [200,225,40,60,1,5,10,20,1,5,10,15]
FighterStats = [300,350,10,20,15,20,1,5,20,25,15,20]
BarbarianStats = [400,450,5,10,30,40,0,3,30,40,10,15]
ClericStats = [250,275,30,50,1,5,10,15,1,5,15,20]
IllusionistStats = [200,225,40,60,1,5,10,20,1,5,10,15]
GoblinStats = [150,175,0,0,15,18,3,7,4,8,10,15]
TrollStats = [250,275,0,0,25,28,13,17,14,18,5,10]
GiantStats = [450,500,0,0,35,38,18,23,24,28,10,15]

#Provide Stat points for given entity
class StatsAssignment:
    def __init__(self,minhp,maxhp,minmp,maxmp,minstr,maxstr,minwis,maxwis,mindef,maxdef,mindex,maxdex):
        self.Hp = random.randint(minhp,maxhp)
        self.Mp = random.randint(minmp,maxmp)
        self.Str = random.randint(minstr,maxstr)
        self.Wis = random.randint(minwis,maxwis)
        self.Def = random.randint(mindef,maxdef)
        self.Dex = random.randint(mindex,maxdex)

#Simple Pause  
def Pause():
    input('To continue, press [Enter]') 
     
#Class Selection
def SelectClass():
    global Class
    global ClassStats
    while Class <=0 or Class >= 7:
            try:    
                Class = int(input('Please choose your class:\n (Enter the corresponding number)\n1) Rogue\n2) Wizard\n3) Fighter\n4) Barbarian\n5) Cleric\n6) Illusionist\n\n'))
                if Class == 1:
                    ClassStats = RogueStats
                    Class = "Rogue"
                    break
                    #Class skills eventually
                elif Class == 2:
                    ClassStats = WizardStats
                    Class = "Wizard"
                    break
                    #Class skills eventually
                elif Class == 3:
                    ClassStats = FighterStats
                    Class = "Fighter"
                    break
                    #Class skills eventually
                elif Class == 4:
                    ClassStats = BarbarianStats
                    Class = "Barbarian"
                    break
                    #Class skills eventually
                elif Class == 5:
                    ClassStats = ClericStats
                    Class = "Cleric"
                    break
                    #Class skills eventually
                elif Class == 6:
                    ClassStats = IllusionistStats
                    Class = "Illusionist"
                    break
                    #Class skills eventually
                else:
                    subprocess.call(["clear"])
                    print("Invalid entry. Please try again.")
            except:
               subprocess.call(["clear"])
               print("Invalid Entry. Please try again.")
        
#Generate player's base stats based on selected class
def GetPlayerStats():
    global PlayerDex
    global PlayerDef
    global PlayerWis
    global PlayerStr
    global PlayerHp
    global PlayerMp
    PlayerStats = StatsAssignment(ClassStats[0],ClassStats[1],ClassStats[2],ClassStats[3],ClassStats[4],ClassStats[5],ClassStats[6],ClassStats[7],ClassStats[8],ClassStats[9],ClassStats[10],ClassStats[11])
    PlayerHp = PlayerStats.Hp
    PlayerMp = PlayerStats.Mp
    PlayerStr = PlayerStats.Str
    PlayerWis = PlayerStats.Wis
    PlayerDef = PlayerStats.Def
    PlayerDex = PlayerStats.Dex

#Show User the stats of their character
def ShowPlayer():
    print(f'\nClass: {Class}\nHealth: {PlayerHp}\nMana: {PlayerMp}\nStrength: {PlayerStr}\nWisdom: {PlayerWis}\nDefense: {PlayerDef}\nDexterity: {PlayerDex}\n')

#Select the Enemy type
def SelectEnemy():
    global Enemy
    global EnemyTypeStats
    while Enemy <= 0 or Enemy >= 4:
        try:
            Enemy = int(input("\nSelect the enemy type:\n(Select the number next to your choice)\n\n1) Goblin\n2) Troll\n3) Giant\n\n"))
            if Enemy == 1:
                EnemyTypeStats = GoblinStats
                Enemy = "Goblin"
                print(EnemyTypeStats)
                break
            elif Enemy == 2:
                EnemyTypeStats = TrollStats
                Enemy = "Troll"
                break
            elif Enemy == 3:
                EnemyTypeStats = GiantStats
                Enemy = "Giant"
                break
            else:
                subprocess.call(["clear"])
                print("Invalid Entry. Please Try Again.")
        except:
            subprocess.call(["clear"])
            print("Invalid Entry. Please Try Again.")

#Determine enemy stats
def GetEnemyStats():
    global EnemyDex
    global EnemyDef
    global EnemyWis
    global EnemyStr
    global EnemyHp
    global EnemyMp
    EnemyStats = StatsAssignment(EnemyTypeStats[0],EnemyTypeStats[1],EnemyTypeStats[2],EnemyTypeStats[3],EnemyTypeStats[4],EnemyTypeStats[5],EnemyTypeStats[6],EnemyTypeStats[7],EnemyTypeStats[8],EnemyTypeStats[9],EnemyTypeStats[10],EnemyTypeStats[11])
    EnemyHp = EnemyStats.Hp
    EnemyMp = EnemyStats.Mp
    EnemyStr = EnemyStats.Str
    EnemyWis = EnemyStats.Wis
    EnemyDef = EnemyStats.Def
    EnemyDex = EnemyStats.Dex

#Show User Stats of Enemy
def ShowEnemy():
    print(f'\nEnemy Type: {Enemy}\nHealth: {EnemyHp}\nMana: {EnemyMp}\nStrength: {EnemyStr}\nWisdom: {EnemyWis}\nDefense: {EnemyDef}\nDexterity: {EnemyDex}\n')        

#Run Program
SelectClass()
GetPlayerStats()
SelectEnemy()
GetEnemyStats()
subprocess.call(["clear"])
ShowPlayer()
ShowEnemy()
Pause()

I’m sorry if bouncing code off of you ever gets old. You and the W3 schools website has taught me nearly everything I know and I am dying to know more! Thank you for helping a man with his hobby!

I posted a reply earlier suggesting you make a new topic for your new
programme. But it never seemed to show. Anyway, we’re here now…

In this one, is there a way to simplify the class and enemy type
sections at all, or is that the nature of the beast? It felt
repetitive, but it still seemed too different to try to clump together.

It is repetitive, and can be cleaned up a lot. Let’s look at a few
things as they show up…

Class = 0
Enemy = 0

I know these are standard terms and therefor hard to avoid, but the
lowercase “class” is a Python keyword. Since (by convention) we name
ordinary variables with lower case (well, snake_case) names and class
names with CapitalisedNames this is slightly confusing.

#Lists are in the order-
#MinHp,MaxHp,MinMp,MaxMp,MinStr,MaxStr,MinWis,MaxWis,MinDef,MaxDef,MinDex,MaxDex
RogueStats = [200,225,10,20,5,10,1,5,10,15,20,25]

Here is a classis situation where you could do with a namedtuple:

A namedtuple is a fixed length tuple whose elements can be accessed as
attributes. This means you don’t need to use index access (which is
fragile, because it is easy to make mistakes). So:

from collections import namedtuple
......
ClassStats = namedtuple('ClassStats', 'MinHp MaxHp MinMp MaxMp MinStr MaxStr MinWis MaxWis MinDef MaxDef MinDex MaxDex')

WizardStats = ClassStats(200,225,40,60,1,5,10,20,1,5,10,15)
FighterStats = ClassStats(300,350,10,20,15,20,1,5,20,25,15,20)
BarbarianStats = ClassStats(400,450,5,10,30,40,0,3,30,40,10,15)
ClericStats = ClassStats(250,275,30,50,1,5,10,15,1,5,15,20)
IllusionistStats = ClassStats(200,225,40,60,1,5,10,20,1,5,10,15)
GoblinStats = ClassStats(150,175,0,0,15,18,3,7,4,8,10,15)
TrollStats = ClassStats(250,275,0,0,25,28,13,17,14,18,5,10)
GiantStats = ClassStats(450,500,0,0,35,38,18,23,24,28,10,15)

Now you have the same fixed lists (well, tuples, immutable) of numbers,
but now they have names. You can write “BarbarianStats.MinMp” and so
forth. Note; They are tuples, so you can’t change the values. But for
things like the above which are fixed definitions they work well. And
they’re stored as tuples, which are more compact than normal
any-attribute-you-like objects, which use a dict for the
attribute->value mapping. Not that that is an issue here.

#Provide Stat points for given entity
class StatsAssignment:
    def __init__(self,minhp,maxhp,minmp,maxmp,minstr,maxstr,minwis,maxwis,mindef,maxdef,mindex,maxdex):
        self.Hp = random.randint(minhp,maxhp)
        self.Mp = random.randint(minmp,maxmp)
        self.Str = random.randint(minstr,maxstr)
        self.Wis = random.randint(minwis,maxwis)
        self.Def = random.randint(mindef,maxdef)
        self.Dex = random.randint(mindex,maxdex)

And here we have more repetition. First up, I’d be inclined to pass in a
single ClassStats instances instead of all these parameters (again,
fragile - a bunch of numbers in a very specific order) since the fields
seem to match up with the ClassStats field names. So:

    def __init__(self, class_stats):
        self.Hp = random.randint(class_stats.MinHp,class_stats.MaxHp)

and so forth.

But I’d go further: you could actually make your namedtuple a class
which subtypes namedtuple and provides the above as a method:

StatsAssignment = namedtuple('StatsAssignment', 'Hp Mp Str Wis Def Dex')

class ClassStats(namedtuple('ClassStats', 'MinHp MaxHp MinMp MaxMp MinStr MaxStr MinWis MaxWis MinDef MaxDef MinDex MaxDex'))

    def assign_stats(self):
        return StatsAssignment(
            Hp=random.randint(self.MinHp,self.MaxHp),
            Mp=random.randint(self.MinMp,self.MaxMp),
            .....
        )

which you use like this:

stats_assignment = WizardStats.assign_stats()

to get a new random assignment for a Wizard, via the WizardStats
specification.

Notice we’re defining the new StatsAssignment differently. namedtuples
can be set up with a straight string of values as we did for the various
classes, which is nice and compact like an other tuple. But they also
support keyword based assignment, which is handy when you want to be
clear and you’re “away” from the position order.

#Simple Pause
def Pause():
    input('To continue, press [Enter]')

Again, and throughout, I recommend snake case names. So:

def pause():

And:

#Class Selection
def select_class():

This function fiddles a could of global variables. Normally we try to
avoid having functions do that. Instead, why not use local variables and
return them? Eg:

while True:
    klass = int(input('Please choose your class:\n (Enter the corresponding number)\n1) Rogue\n2) Wizard\n3) Fighter\n4) Barbarian\n5) Cleric\n6) Illusionist\n\n'))
    if klass == 1:
        return "Rogue", RogueStats
    if klass == 2:
        return "Wizard", WizardStats
    ... etc etc ...
    print("Invalid entry, expected an integer from 1 though 7.")

Then as the calling end:

class_name, class_definition = select_class()

No globals involved inside the function.

Also, you’ve does a wide and bare try/except again. Don’t do that! be
precise. Also, you clear the display a lot. In particular, that’s likely
to erase any complain you might make about the input. Years of watching
idiot PC BIOS boots which repeatedly clear the screen after various
phases makes me almost prone to rage when that happens :slight_smile:

And this again suggests a further improvement. Why return a name string
and the definition. Why not name the definition itself know its own
name? Like this:

ClassStats = namedtuple('ClassStats', 'Name MinHp MaxHp MinMp MaxMp MinStr MaxStr MinWis MaxWis MinDef MaxDef MinDex MaxDex')

WizardStats = ClassStats('Wizard',200,225,40,60,1,5,10,20,1,5,10,15)

Then you don’t need magic names - they’re in the definition. Which means
your selection function can just “return WizardStats” etc.

But wait! There’s more!

Why have an elaborate if-else chain (or “if…return, if…return”) at
all? Why not put all your definitions in a dictionary?

class_definitions = {
    1: RogueStats,
    2: WizardStats,
    ........
}

Then you can just try to access the dict in the select function:

try:
    definition = class_definitions[klass]
except KeyError:
    print("Invalid entry, expected one of", sorted(class_definitions.keys()))
else:
    return definitions

But wait! You could have the prompt print out the list of available
choices by getting them from the class_definitions dict!

#Generate player's base stats based on selected class
def GetPlayerStats():
    global PlayerDex
    global PlayerDef
    global PlayerWis
    global PlayerStr
    global PlayerHp
    global PlayerMp

Really, you don’t need clobal variables here.

    PlayerStats = StatsAssignment(ClassStats[0],ClassStats[1],ClassStats[2],ClassStats[3],ClassStats[4],ClassStats[5],ClassStats[6],ClassStats[7],ClassStats[8],ClassStats[9],ClassStats[10],ClassStats[11])

See how fragile this is? The slightest mistake with those indices and it
is all wrong. You’ve got stats assignments - just keep one to hand as a
single variable.

    PlayerHp = PlayerStats.Hp
    PlayerMp = PlayerStats.Mp
    ...

And I wouldn’t do this either. Just get them straight from the stats
assignment as fields, exactly as you have on the right hand side of the
assignments. SO you could discard all these assignments completely.

#Show User the stats of their character
def ShowPlayer():
    print(f'\nClass: {Class}\nHealth: {PlayerHp}\nMana: {PlayerMp}\nStrength: {PlayerStr}\nWisdom: {PlayerWis}\nDefense: {PlayerDef}\nDexterity: {PlayerDex}\n')

You could make this the __str__ method of the StatsAssignment cass.
Which means you could just go:

print(stats_assignment)

and the __str__ method will do the rest for you.

I’d stick the “subprocess.call()” of clear in a function, just like you
did with the pause() function.

Cheers,
Cameron Simpson cs@cskk.id.au

Thanks for all the feedback! After reading it, I do have a few questions.

  1. Is there a website or library somewhere that shows me the built-in importable functions? The ones I know of now are only because I googled how to do specific things, but it would be cool to be able to know what my options are before hand.

  2. Can you explain, and provide simple examples if possible, the instances in which situations the snake case, camel case, and pascal case? Initially I thought they were interchangable, which technically they are, but I want to make my code easy for others to follow.

  3. Can you help me understand how the return function works?

  4. In that assign_stats function, I notice you formatted it in a way that took up several lines. Can you explain how the syntax for that works? It’s something I don’t recall seeing anywhere before and would love to learn more about it.