4
\$\begingroup\$

This code is for a board game program, which works by placing the user on a grid which then tasks the user with finding all the treasure chests. Each chest landed on gives 10 gold. However, landing on a bandit means all your gold is taken. There are also different difficulties. The end score is then given once the user has found all the chests. I have managed to added features such as a leaderboard, save system, and tkinter has been implemented.

I want any advice on how to make the code more efficient, presentable or user-friendly. I do also wish to add an SQL database soon as I do want to add the gameplay feature of the program asking a question to the user whenever they land on a chest. Should the user answer correctly, they get the gold in the chest.

from tkinter import ttk
from tkinter import messagebox
import tkinter
import random
import sys
import time
import os
import pickle #module used to serielize objects
import json
from playsound import playsound

window = tkinter.Tk()
window.title("")

boardeasy=[]
boardmed=[]
boardhard=[]
current=[0,0]
treasures = [(random.randint(0,8), random.randint(0,8)) for i in range(12)]
bandits = [(random.randint(0,8), random.randint(0,8)) for i in range(12)]
Coins = 0
Chest = 10
Bandit1 = 5
Bandit2 = 7
Bandit3 = 10

class user():
    def __init__(self, username, userscore, usertime):
        self.username = username
        self.userscore = userscore
        self.usertime = usertime

#For loop prints a new 8*8 grid after every move
boardeasy = [[' ' for j in range(8)] for i in range(8)]
def table_game_easy():
    print("  1   2   3   4   5   6   7   8")
    print("---------------------------------")
    for row in range(8):
        print ('| ' + ' | '.join(boardeasy[row][:8]) + ' | ' + str(row + 1))
        print("---------------------------------")
    treasures_row = [random.randint(0,8) for i in range(10)]
    treasures_col = [random.randint(0,8) for i in range(10)]
    bandits_row = [random.randint(0,8) for i in range(5)]
    bandits_col = [random.randint(0,8) for i in range(5)]

# For loop prints a new 10*10 grid after every move
boardmed = [[' ' for j in range(10)] for i in range(10)]

def table_game_meduim():
    print("  1   2   3   4   5   6   7   8   9   10")
    print("------------------------------------------")
    for row in range(10):
        print ('| ' + ' | '.join(boardmed[row][:10]) + ' | ' + str(row + 1))
        print("------------------------------------------")
    treasures_row = [random.randint(0,10) for i in range(10)]
    treasures_col = [random.randint(0,10) for i in range(10)]
    bandits_row = [random.randint(0,10) for i in range(7)]
    bandits_col = [random.randint(0,10) for i in range(7)]

# For loop prints a new 12*12 grid after every move
boardhard = [[' ' for j in range(12)] for i in range(12)]
def table_game_hard():
    print("  1   2   3   4   5   6   7   8   9   10   11   12")
    print("----------------------------------------------------")
    for row in range(12):
        print ('| ' + ' | '.join(boardhard[row][:12]) + ' | ' + str(row + 1))
        print("----------------------------------------------------")
    treasures_row = [random.randint(0,10) for i in range(10)]
    treasures_col = [random.randint(0,10) for i in range(10)]
    bandits_row = [random.randint(0,10) for i in range(10)]
    bandits_col = [random.randint(0,10) for i in range(10)]

#this function is in charge of downwards movement
def down(num,lev):
    num=(num+current[0])%lev#The % formats this equation
    current[0]=num
#this function is in charge of downwards movement
def right(num,lev):
    num=(num+current[1])%lev #The % formats this equation
    current[1]=num
#this function is in charge of downwards movement
def left(num,lev):
    if current[1]-num>=0:
        current[1]=current[1]-num
    else:
        current[1]=current[1]-num+lev
#this function is in charge of downwards movement
def up(num,lev):
    if current[0]-num>=0:
        current[0]=current[0]-num
    else:
        current[0]=current[0]-num+lev

def easy_level(Coins, Chest, Bandit1):
    playsound('audio.mp3', block = False) 
    #This function is for the movement of the game in easy difficulty
    p = 0
    while  True and Chest > 0:
        p = p+1
        if p == 5:
            print("Do you want to save the game?")
            choice = input(">")
            if choice == 'yes':
                with open('file.txt', 'w') as f:
                    json.dump([Coins, Chest, Bandit1], f)          
        oldcurrent=current
        boardeasy[oldcurrent[0]][oldcurrent[1]]='*' 
        table_game_easy()
        boardeasy[oldcurrent[0]][oldcurrent[1]]=' '
        n = input('Enter the direction followed by the number Ex:Up 5 , Number should be < 8 \n')
        n=n.split()
        if n[0].lower() not in ['up','left','down','right']:#Validates input
            print('Wrong command, please input again')
            continue
        elif n[0].lower()=='up':
            up(int(n[1].lower()),8)#Boundary is set to 8 as the 'easy' grid is a 8^8
        elif n[0].lower()=='down':
            down(int(n[1].lower()),8)
        elif n[0].lower()=='left':
            left(int(n[1].lower()),8)
        elif n[0].lower()=='right':
            right(int(n[1].lower()),8)

        print(Chest,"chests left")
        print(Bandit1,"bandits left")
        print("Coins:",Coins)#Acts as a counter, displays the number of coins that the player has
        if (current[0], current[1]) in treasures[:8]:
            Chest = Chest - 1
            print("Hooray! You have found booty! +10 gold")
            Coins = Coins + 10 #Adds an additional 10 points
            print("Coins:",Coins)


        if (current[0], current[1]) in bandits[:8]:
            Bandit1 = Bandit1 - 1
            print("Oh no! You have landed on a bandit...they steal all your coins!")
            Coins = Coins-Coins #Removes all coins
            print("Coins:",Coins)

        boardeasy[current[0]][current[1]]='*'#sets value to players position

    #Label box
    labelOne = ttk.Label(window, text = "Enter your username:")
    labelOne.grid(row = 0, column = 0)
    #Userinput is set to variable
    UserName = tkinter.StringVar()
    #Input box
    userEntry = ttk.Entry(window, width = 26, textvariable = UserName)
    userEntry.grid(row = 0, column = 1)

    def action():
        username1 = UserName.get()
        with open("high_scores.txt","a") as f:
            f.write(str(Coins)+ os.linesep)
            f.write(username1 + os.linesep)
            f.close()

        window.destroy()

    btn = ttk.Button(window, text = "Submit", command = action)
    btn.grid(row = 0, column = 2)

    window.mainloop()

def med_level(Coins, Chest, Bandit2):
    playsound('audio.mp3', block = False) 
#This function is for the movement of the game in medium difficulty
    while  True:
        oldcurrent=current
        boardmed[oldcurrent[0]][oldcurrent[1]]='*'
        table_game_meduim()
        boardmed[oldcurrent[0]][oldcurrent[1]]=' '
        n = input('Enter the direction followed by the number Ex:Up 5 , Number should be < 10 \n')
        n=n.split()
        if n[0].lower() not in ['up','left','down','right']:
            print('wrong command')
            continue
        elif n[0].lower()=='up':
            up(int(n[1].lower()),10)#Boundary is set to 10 as the 'easy' grid is a 10^10
        elif n[0].lower()=='down':
            down(int(n[1].lower()),10)
        elif n[0].lower()=='left':
            left(int(n[1].lower()),10)
        elif n[0].lower()=='right':
            right(int(n[1].lower()),10)
        print(Chest,"chests left")
        print(Bandit2,"bandits left")
        print("Coins:",Coins)#Acts as a counter, displays the number of coins that the player has
        if (current[0], current[1]) in treasures[:10]:
            Chest = Chest - 1
            print("Hooray! You have found booty! +10 gold")
            Coins = Coins+10 #Adds an additional 10 points
            print("Coins:",Coins)

        if (current[0], current[1]) in bandits[:10]:
            Bandit2 = Bandit2 - 1
            print("Oh no! You have landed on a bandit...they steal all your coins!")
            Coins = Coins-Coins #Removes all coins
            print("Coins:",Coins)
        boardmed[current[0]][current[1]]='*'

def hard_level(Coins, Chest, Bandit3):
    playsound('audio.mp3', block = False)
#This function is for the movement of the game in hard difficulty
    while  True:
        oldcurrent=current
        boardhard[oldcurrent[0]][oldcurrent[1]]='*'
        table_game_hard()
        boardhard[oldcurrent[0]][oldcurrent[1]]=' '
        n = input('Enter the direction followed by the number Ex:Up 5 , Number should be < 12 \n')
        n=n.split()
        if n[0].lower() not in ['up','left','down','right']:
            print('wrong command')
            continue
        elif n[0].lower()=='up':
            up(int(n[1].lower()),12)#Boundary is set to 12 as the 'hard' grid is a 12^12
        elif n[0].lower()=='down':
            down(int(n[1].lower()),12)
        elif n[0].lower()=='left':
            left(int(n[1].lower()),12)
        elif n[0].lower()=='right':
            right(int(n[1].lower()),12)

        print(Chest,"chests left")
        print(Bandit3,"bandits left")
        print("Coins:",Coins)#Acts as a counter, displays the number of coins that the player has
        if (current[0], current[1]) in treasures[:12]:
            Chest = Chest - 1
            print("Hooray! You have found booty! +10 gold")
            Coins = Coins+10 #Adds an additional 10 points
            print("Coins:",Coins)

        if (current[0], current[1]) in bandits[:12]:
            Bandit2 = Bandit2 - 1
            print("Oh no! You have landed on a bandit...they steal all your coins!")
            Coins = Coins-Coins #Removes all coins
            print("Coins:",Coins)

        boardhard[current[0]][current[1]]='*'
def instruct():
    difficulty = input("""Before the game starts, please consider what difficulty
would you like to play in, easy, medium or (if you're brave) hard.
""")
    if difficulty == "easy":
        print("That's great! Lets continue.")
        time.sleep(1)#Allows the user time to get ready
        print("initiating game in...")
        time.sleep(1)
        print()
        print("3")
        time.sleep(1)
        print("2")
        time.sleep(1)
        print("1")
        time.sleep(1)
        for i in range(3):
            print("")
        easy_level(Coins, Chest, Bandit1)

    elif difficulty == "medium":
        print("That's great! Lets continue.")
        time.sleep(1)#Allows the user time to get ready
        print("initiating game in...")
        time.sleep(1)
        print()
        print("3")
        time.sleep(1)
        print("2")
        time.sleep(1)
        print("1")
        time.sleep(1)
        for i in range(3):
            print("")
        med_level(Coins, Chest, Bandit2)

    elif difficulty == "hard":
        print("That's great! Lets continue.")
        time.sleep(1)#Allows the user time to get ready
        print("initiating game in...")
        time.sleep(1)
        print()
        print("3")
        time.sleep(1)
        print("2")
        time.sleep(1)
        print("1")
        time.sleep(1)
        for i in range(3):
            print("")
        hard_level(Coins, Chest, Bandit3)
    else:
       print("Sorry, that is an invalid answer. Please restart the programme")

def menu():
    #This function lets the user quit the application or progress to playing.
    print("")
    print ("Are you sure you wish to play this game? Please answer either yes or no.")
    choice1 = input() # Sets variable to user input
    if choice1.lower().startswith('y'): 
       print("Okay lets continue then!")
       print("")
       print("")
       print("""~~~~~~~~~~MENU~~~~~~~~~~
             1). Load a previous game
             2). Display the high score table
             3). Continue

             """)
       choice2 = input(">")
       while choice2 != '3':
           print("")
           print("")
           print("""~~~~~~~~~~MENU~~~~~~~~~~
             1). Load a previous game
             2). Display the high score table
             3). Continue

             """)
           choice2 = input(">")
           if choice2 == "1":
               with open('file.txt') as f:
                   coins, chests, bandits = json.load(f)

           elif choice2 == "2":
               with open("high_scores.txt") as f:
                   for line in f:
                       print(line)
                       print("")

    elif choice1.lower().startswith('n'):
        print("Thank you, I hope you will play next time!")
        print("")
        quit("Thank you for playing!")#Terminates the programme
    else:
        print("Sorry, that is an invalid answer. Please restart the programme")
        print("")
        quit()

    instruct()

def showInstructions():
    #hides main window
    window.withdraw()
    #message box display
    messagebox.showinfo("Instructions","""You are a treasure hunter, your goal is to collect atleast 100 gold by the end
of the game from treasure chests randomly scattered across the grid. There are
10 chests within a grid (this can be changed based on difficulty) and  each
treasure chest is worth 10 gold but can only be reclaimed 3 times before it is
replaced by a bandit. Landing on a bandit will cause you to lose all of your
gold and if all the chests have been replaced by bandits and you have less then
100 gold this means you lose!
Press enter to continue...""")

    messagebox.showinfo("Instructions","""At the start of the game, you always begin at the top right of the grid.
Below is a representation of the game:

 * 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
Press enter to continue...""")

    messagebox.showinfo("Instructions","""When deciding where to move, you should input the direct co-ordinates of your
desired location. For instance:
Enter the direction followed by the number Ex: Up 5 , Number should be < 8
right 3
 0 0 0 * 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
Unlucky move! You have found nothing!
If nothing on the grid changes , this means that your move was a bust! Landing
on nothing isn't displayed on the grid.
Press enter to continue...""")

    messagebox.showinfo("Instructions","""
Enter the direction followed by the number Ex: Up 5 , Number should be < 8
down 4
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 * 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0
Hooray! You have found booty! +10 gold
Here you can see that the use has landed on a chest!
As you land on chest, they get replaced by bandits. Be sure to remember the
previous locations so you don't accidently land on a bandit and lose all
your gold!
Press enter to continue...""")

#Introduces the user
print('Welcome to the Treasure hunt!')
time.sleep(0.3)
print()
time.sleep(0.3)
print('Would you like to see the instructions? (yes/no)')
if input().lower().startswith('y'):#If function checks for the first letter
    showInstructions()
elif input == 'no' or 'No':
    print("Lets continue then!")#Calls the function which displays instructions
else:
   print("Please check your input and try again.")

menu()
\$\endgroup\$
2
\$\begingroup\$
  1. Move your startup/init code into a main routine, and replace it with the more usual Python idiom:

    if __name__ == '__main__':
        main()
    
  2. As a pet peeve, get rid of the delay timers. I don't think they add any value, and they do make it irritating when debugging.

  3. Move your data away from your code. For example, you have a bunch of literal text in your showInstructions function. Instead, create a list of tuples:

    INSTRUCTIONS = [ # (title, text)
        ("Instructions", """You are a treasure hunter, your goal is to collect at least 
         100 gold by the end of the game from treasure chests randomly ..."""),
        # ... etc ...
    ]
    

    Then use the tuples in a simple loop inside showInstructions:

    def showInstructions():
        """Hide main window, then show a series of message boxes with instructions in them."""
    
        window.withdraw()
        for title, text in INSTRUCTIONS:
            messageBox(title, text)
    

    Also, since your message boxes always end with "Press enter to continue..." go ahead and define a messageBox function that appends that text.

    def messageBox(title, text):
        message = text.rstrip() + '\n\nPress enter to continue...'
        messagebox.showinfo(title, message)
    
  4. Decide how you want to handle input, and write a function to do that. Checking a Y/N response for a 'y' and an 'n' is annoying. Users prefer there to be a default behavior that requires only one answer. For example, if the default is N, then the user can type 'Y' or just hit enter, defaulting to N. This will reduce the amount of code you have to write, as well.

    Writing a function for this should be easy and obvious. You can return a boolean true for yes, false for no, and use that inside an if statement:

    if ask_yorn("Are you sure you with to play this game?"):
       print("Okay lets continue then!")
       print("")
       print("")
       print("""~~~~~~~~~~MENU~~~~~~~~~~""")
       # ...
    else:
       quit("Thank you for playing!")
    
  5. A same suggestion applies to numeric text entry for selecting from a menu. Write a function that takes a maximum value, a prompt, and keeps looping until a valid input is found. Call it get_number(3, "> "). Let the return value be the integer result.

  6. Collapse your various xxx_easy/med/hard functions into code as much as you can. Your only change seems to be the board size, so why not just create a size variable, or use the len(board) directly to know how many rows/columns there are?

  7. Write a function to handle getting a board command, checking it against the valid list of commands, and returning a valid result. It should just loop until something valid happens, either up, down, etc or an exit.

  8. Finally, stop using current[0] and current[1]. You are storing values into the list, then unpacking the elements of the list each time. You should consider some of these possibilities:

    • Declare global position (instead of current) and stop passing arguments.
    • Create a namedtuple and use current.x, current.y.
    • Create a tuple and pass it directly.
    • Create global X and global Y and pass them directly.
\$\endgroup\$
0
\$\begingroup\$

Minor notes:

  • this:

    boardeasy = [[' ' for j in range(8)] for i in range(8)]
    

    can skip the loops and be:

    boardeasy = [ [ ' ' ] * 8 ] * 8 ]
    
  • Instead of table_game_easy, table_game_medium and table_game_hard that print out boards stored in global variables, make a generic print_board routine, and also make a more general routine table_game(size) that can generate a square board of any size. Then your 'easy', 'medium' and 'hard' can be just calls to set board = table_game(size) where size is 8, 10, or 12 depending on the difficulty.

  • code like

    current[1]=current[1]-num
    

    should be written like:

    current[1] -= num
    

    as it's both shorter and less error-prone

  • this

    while  True and Chest > 0:
    

    is the exact same as:

    while Chest > 0:
    
\$\endgroup\$
  • 2
    \$\begingroup\$ Your first bullet point is bad advice as now boardeasy[0] is the same object as boardeasy[1] and so on. So setting boardeasy[x][y] does not have the expected effect but instead set 8 cells at a time. \$\endgroup\$ – 409_Conflict Jan 16 at 7:10

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.