How to get rid of the global variables - python

I don't want to use the global-variable, but how do I make this without them?
(I have more functions and so on, so this isn't all)
Players turn. I use the global-variable to make sure the turns are switching, tried some other sulotion however the turns didn't switch...
#Check if the shot hit any computerships
def player_hit_check():
global players_turn
shot = player_shot()
check_if_hit = set(computer_ships) & set(player_turn_shot)
if(check_if_hit == set()):
print("You missed! Switching turns...")
time.sleep(3)
players_turn = 1
else:
print(f"YOU MADE A BIG BANG ON ",player_turn_shot,"!")
computer_ships.remove(shot)
all_player_hits.append(shot)
Computers turn
#Check if the computers shot hit any player ships
def computer_hit_check():
global computers_turn
computer_random_shot = computer_shot()
check_if_hit = set(player_ships) & set(computer_turn_shot)
if(check_if_hit == set()):
print("Computer missed! It's now your turn.")
time.sleep(3)
computers_turn = 1
else:
print(f"COMPUTER MADE A BIG BANG ON ",computer_turn_shot,"!")
player_ships.remove(computer_random_shot)
all_computer_hits.append(computer_random_shot)
The game it self
#Runs the game
while True:
#Players turn
players_turn = 0
while players_turn < 1:
print('\n'.join('\t'.join(row) for row in board.values()))
print("Playerships:",player_ships)
print("Computerships:",computer_ships)
print("You have shot on",all_player_shots,"and has knocked out these ships:",all_player_hits)
print("The computer has shot at these coordinates",all_computer_shots,"and has knocked out these ships:",all_computer_hits)
player_hit_check()
#Computers turn
computers_turn = 0
while computers_turn < 1:
computer_hit_check()

In your case you don't need global variables at all. Your program is not using them to carry information back and forth.
In your case you just need to get information back from your functions. So in the case of player_hit_check():
def player_hit_check():
shot = player_shot()
check_if_hit = set(computer_ships) & set(player_turn_shot)
if(check_if_hit == set()):
print("You missed! Switching turns...")
time.sleep(3)
return 1
else:
print(f"YOU MADE A BIG BANG ON ",player_turn_shot,"!")
computer_ships.remove(shot)
all_player_hits.append(shot)
return 0
And the call site would be like this:
while True:
#Players turn
players_turn = 0
while players_turn < 1:
# prints elided ...
players_turn = player_hit_check()

The problem with global variables is, that they fly around in an unstructured manner.
There are several approaches to solve global variables
One first step to get rid of global variables is to use singletons (https://en.wikipedia.org/wiki/Singleton_pattern). This will bundle similar values into one class. So the values are more structured and easier to track.
Use classes. If the global value is used in an environment that can be associated to a single responsibility, it can be productive to bundle all functions in a class and make the global variable to a class variable or property of the class
Pass it through. In many programs there is the need of settings class or state class. Put everything in there that defines the current state or starting state of the program and pass it through your code. The downside of passing is, that this classes tend to become almighty classes that appear in every constructor.
There are certainly more approaches how to get rid of global variables, but this are some common strategies.

Related

Can I make a print statement run before a module I imported?

I am a beginner to python and coding in general and I was wondering how I can make a print statement run before a module I imported. I am making a number guessing game and in my main file where I combine all the modules, I have a general function for running all the code together. It would be best if I show my code so you guys will have a better understanding of what I am dealing with:
import random
import lvl1
import time
level1 = lvl1.Level_1_activated()
# This is the main file combining everything together to make this game playable
introduction = """
Hello and welcome to NumGuess by Sava. Here is a little bit about the game:
The game works by having 3 levels, each where you must pick a number between a range of
1-10 (level 1), 1-20 (level 2), and 1-50 (level 3).
You are given 5 attempts in the first level, 10 in the second level, and 20 in the final one.
You can also access a hint by typing ‘hint’. You win the game by picking the right number in each level.
You lose the game when you run out of tries. You can get a free bonus with 5 extra tries if you type ‘hlp’.
"""
def start_game(time, lvl1):
print(introduction)
level1
start_game(time, lvl1)
This is just the code for the main module, I have the code for lvl1 (which is the first level of my 'game'), and I have a class which has all the functions which then take part in the while loop. I will also show that file:
import random
import time
# I will fist make variables for the time breaks. S is for short, M is for medium and L is for long
S = 0.2
M = 0.7
L = 1.1
class Level_1_activated():
def get_name(self):
# This function simply asks the name of the player
name = input("Before we start, what is your name? ")
time.sleep(S)
print("You said your name was: " + name)
def try_again(self):
# This asks the player if they want to try again, and shows the progress of the level
answer = (input("Do you want to try again? "))
time.sleep(M)
if answer == "yes":
print("Alright!, well I am going to guess that you want to play again")
time.sleep(M)
print("You have used up: " + str(tries) + " Of your tries. Remember, when you use 5 tries without getting the correct number, the game ends")
# Return statement for if the player wants to play again
return True
else:
print("Thank you for playing the game, I hope you have better luck next time")
# This is the return statement that stops the while loop
return False
def find_rand_num(self, random):
# This is the core of the level, where the player just chooses numbers between 1 and 10
time.sleep(S)
print("The computer is choosing a random number between 1 and 10... beep beep boop")
time.sleep(L)
# The list of numbers for the level that the player is on at the moment
num_list = [1,10]
number = random.choice(num_list)
ques = (input("guess your number, since this is the first level you need to choose a number between 1 and 10 "))
print(ques)
if ques == str(number):
time.sleep(S)
print("Congratulations! You got the number correct!")
# Yet another return statement for the while loop
return "Found"
elif input != number:
time.sleep(M)
print("Oops, you got the number wrong")
# This variable is fairly self-explanatory; it is what controls how many itterations there are in the while loop
tries = 1
while tries < 6:
if tries < 2:
Level_1_activated().get_name()
res = Level_1_activated().find_rand_num(random)
if res == "Found":
break
checker = Level_1_activated().try_again()
if checker is False:
break
tries += 1
If you go back to this function in the main file:
def start_game(time, lvl1):
print(introduction)
level1
I intentionally put the print statement before the module to make it run first, and I have tried different approaches to this and still can't seem to get a grasp on what I'm doing wrong here. Thank you for taking the time to read the code and I would be very grateful if any of you have a possible solution to this.
there are number of thing you can do, one is encapsulate your code into functions that only run when you ask for it
lvl1
... #all the previous code
def run_game():
tries = 1
while tries < 6:
...
tries += 1
you can also make a distinction between being executed directly vs being imported, to do that is simple, you include the following check (usually at the end of the file)
if __name__ == "__main__":
#if true it mean that you're executing this module directly, otherwise it was imported
#and you include here whatever you want that happens when you execute the module directly but not when is imported, like for example running a game
run_game()
__name__ is a special variable and python will assigned the value "__main__" if executed directly, otherwise it will be the name of the file, like "lvl1" for example
And in your main you can import it and do stuff like
import lvl1
...
def start_game():
print(introduction)
lvl1.run_game()

Is there a way to create a function that will run this code but without passing so many arguments?

I am trying to make code from my project more efficient, shorter but not hard to read/understand.
I see that there are some actions that repeat themselves a couple of times in the code. It is a game so it is required to repeat those.
I thought that maybe I should create a function to call every time instead, but it does not seem friendly to read, because, following a condition, I need to change a couple of variables so I need to pass all of them to the function.
Here is an example:
if nxtTurn == 'player1':
card2, player1 = EgyptionWarHelper.pull_out_card(player1, nxtTurn)
storage.append(card2)
nxtTurn = 'player2'
else:
card2, player2 = EgyptionWarHelper.pull_out_card(player2, nxtTurn)
storage.append(card2)
nxtTurn = 'player1'
I wanted to create a function that does this, but then realized I will need to pass all of the variables to it and then return a tuple of 2 variables at the end of it. I did hear about global variables but I never really used them and I don't know if they are the solution for this.
EDIT: I found out that I didn't have to pass some of the argument so after I edit the function and used temporary variables as well, the code is much more readable.
Additionally, as commented, I didn't have to return player and player2 because python passes lists by reference and not by value.
why not using a temporary variable ?
if nxtTurn == 'player1':
player = player1
nxtTurn = 'player2'
else:
player = player2
nxtTurn = 'player1'
card2, player1 = EgyptionWarHelper.pull_out_card(player, nxtTurn)
storage.append(card2)
it's easily readable and maintainable.

Modifying a dictionary inside of an if statement

I've been trying to make a basic text game in Python, and I'm using dictionaries to contain the player's information. I want to make it so that when a player's health reaches 0, the code will stop running. I've had trouble making that happen. My dictionary, which is at the beginning of my code, looks like this:
playerAtt = {}
playerAtt["Weapon"] = "Baseball bat"
playerAtt["Party"] = "Empty"
playerAtt["Health"] = 15
playerAtt["Cash"] = "$100"
print(playerAtt)
if playerAtt['Health'] <= 0:
exit()
The bottom section is what I wrote to try and make the code stop running when the player's health reached zero, but it doesn't seem to work. In one path of my game, your health gets set to zero and the game is supposed to end, but the program continues to run:
townChoice = raw_input("You met a traveler in the town. 'Yo. I'm Bob. Let's be friends.' Will you invite him to your party? Y/N\n")
if townChoice == 'y' or townChoice == 'Y':
print("That kind traveler was not such a kind traveler. He stabbed you with a machete. RIP " + Name + '.')
playerAtt['Health'] == 0
When you reach this part of the game, all it does is print the message, and moves on to the next decision. In this situation, I could just manually end the program by doing exit() under the print command, but there are circumstances where the player only loses a fraction of their health, and they would eventually reach zero. Sorry if this is a stupid question, I've only been working on Python for a few days.
You have two "=" when you set the player's health to 0
I had put 2 == instead of 1 = when defining
playerAtt["Health"].
Also, I needed to make sure it was constantly checking if the player's health was zero, so I used a while loop. I used
while playerAtt["Health"] = 0:
deathmsg()
exit()
to fix it. deathMsg was a function I made to display a random death message, for more information.

I/O python: Handling output and variable

Good day :)
So during the day, I decided to make a gambling simulation. I'm testing a fail gambling strategy (So mine you if you try to tried my method)
Let me show my code, then the whole thing what happened.
from random import randint
winningNumber=0
bankroll=5000
testCase=1
betLevel=0
bettingLevel=[1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987]
town=[]
bet=0
#----------------------------
my_file= open("output.txt","w")
my_file.write(" # Bet Number Outcome bankroll "+"\n")
def startTheSimulation():
print "OK"
for i in range(100):
if bankroll==0:
break
global betLevel
if bankroll < bettingLevel[betLevel]:
betLevel=0
bet= bettingLevel[betLevel]
print "betlevel",betLevel
print "bet",bet
winningNumber= randint(0,36)
print "winningnumber",winningNumber
if winningNumber== 4:
win(bet)
else:
lose(bet)
def win(inbox):
global bankroll
cow= inbox*35
bankroll+=cow
print "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"
print "bankroll",bankroll
town=[testCase,bet,winningNumber,"WIN",bankroll]
print town
betLevel=0
writing()
def lose(inbox):
global bankroll
global betLevel
wow= inbox
bankroll-=wow
town=[testCase,bet,winningNumber,"LOSE",bankroll]
betLevel+=1
if betLevel==16:
betLevel=15
writing()
def writing():
global testCase
testCase+=1
print "Hey!"
my_file.write(" ".join(town)+"\n")
startTheSimulation()
my_file.write("On all betting, player bet single bet on one number, which is number 4. How money money bet on number for is indicated.")
my_file.close()
My betting system is a weird one. It works like martingale betting system, but instead of doubling my bet, my next bet is based on Fibonacci sequence.
The parameter betLevel is used to decide how many should I bet. The bettingLevel shows the list of the Fibonnaci sequence.
Here comes trouble
Trouble #1:
My output contains blank line
The desired output file is this
& Bet Number Outcome bankroll
# 100 lines of information
On all betting, player bet single bet on one number, which is number 4. How money money bet on number for is indicated.
However, I in turn got this
& Bet Number Outcome bankroll
# 100 BLANK LINES
On all betting, player bet single bet on one number, which is number 4. How money money bet on number for is indicated.
My debugging process:
I actually print the list town. The list if filled (not empty). No other improvement.
Trouble #2: (Solved by using function with arguments.)
My bank roll doesn't update.
My debugging process:
I figured out the problem.
Notice the win function. When I print (int(35)*int(bet)). It turns out to return 0, causing the bankroll not moving.
HOWEVER
When I print "bet",bet in the startTheSimulation() function, it prints the right number. I'm stucked here.
That's my 2 biggest problem. Any help is appreciated.
PS: I use global to avoid UnBoundLocalError
PPS: I use Python 2.7.6
Your logic seems quite convoluted for a fairly simple process. Also, you write things like int(35), that tell me you just came to Python from another language (IDL, perhaps?).
If you are using this as an exercise to learn, I can give you a few hints on how to solve it:
First of all, global variables are almost always a bad idea. If you need to use one, you are probably doing something wrong. The proper way of sharing this information is creating a class. Something like this (very incomplete)
class Simulation(object):
def __init__(self, bankroll):
self.betlevel = 0
self.betting = [1, 1, 2, 3, 5] # You should actually generate this on the fly
self.bankroll = bankroll
self.outputfile = open('filename.txt', 'w')
def do_bet(self):
self.bet = self.betting[self.betlevel]
luckynumber = random.randint()
mynumber = random.randint()
if mynumber == luckynumber:
self.win()
def win(self):
self.bankroll -= self.bet
self.outputfile.write('I won:' + str(self.bet))
The idea is that the class methods have access to the class attributes, so you totally avoid global variables, and reduce the possibility of mistake.
Try to complete the implementation. Once you have it, you can post it again and we can see if there are improvements.
Trouble #1:
you didn't set town as global, so this will print correctly:
town=[testCase,bet,winningNumber,"WIN",bankroll]
print town
, but in writing() method town is empty, and that's why you get 100 empty lines
here is example:
#global town #uncomment if you want to change "town" in "one()"
town = ["not", "changed", "town"]
def one():
#global town #uncomment if you want to change "town" here
town = [1,"local", "town", True]
print "local -> ", town
def two():
# you don't need "global town" here because you do not change it, you only use it
print town
one()
two()
this is output:
local -> [1, 'local', 'town', True]
['not', 'changed', 'town']
Trouble #2:
similar as trouble #1, in startTheSimulation(): you need to write global bet , otherwise bet is local variable and never gets changed, that's why it's 0 in your win() method.
Those are solutions for your problems , but consider creating a class, like #davidmh said...global variables are almost always a bad idea

Python - Passing variables between a class function incorrectly?

The program is rather self-explanatory. I've begun playing around with the basics of Python, and am really lost on this one. I am used to C++ and the wonderful ability to pass things by reference. But, in this, the class variable I'm trying to change (Fighter.statHEALTH) won't change, and I've read that it's because integers are immutable and it just creates a new object locally. So how in the world can I make the change apply to the original variable? I've Googled and Googled, but to no avail. I don't want to perform some ugly manuever like making a list and passing it if I don't have to.
#python 3.2.2
# Create a small test project to have combat between two entities. #
# Combat should include 3 different stats: statATK, statDEF, and statHEALTH. #
# The two entities should be of the same class. #
class Fighter:
def __init__(self):
self.statHEALTH = 10
self.statATK = 3
self.statDEF = 3
def attack(self, enemyhealth):
enemyhealth = (enemyhealth - self.statATK)
return enemyhealth
def defend(self):
statDEF += 1
def main():
James = Fighter()
Keaton = Fighter()
while James.statHEALTH > 0:
print("Do you wish to attack or defend?")
print("1. Attack")
print("2. Defend")
choice = input()
if choice == "1":
James.attack(Keaton.statHEALTH)
print("You did", James.statATK, "damage!")
Keaton.attack(James.statHEALTH)
print("Keaton has", Keaton.statHEALTH, "health left.")
print("Keaton did", Keaton.statATK, "damage!")
print("You have", James.statHEALTH, "health left.")
#elif choice == "2":
#James.defend()
#Keaton.attack(James.statHEALTH)
main()
def attack(self, enemyhealth):
enemyhealth = (enemyhealth - self.statATK)
return enemyhealth
This would actually work if you changed your call to be
Keaton.statHEALTH = James.attack(Keaton.statHEALTH)
.. since you return the damage from the attack. Obviously this is ugly; repeating yourself is not good. Instead you can make attack look like:
def attack(self, other):
other.statHEALTH -= self.statATK
And then just do
James.attack(Keaton)
when you call it.
Maybe you could think of it differently. In your example, Fighter.attack() just returns the value of the enemies health after the attack. So really, it should be a method call on the enemy's object. You could add a method that decreases the fighter's health when they get attacked:
def attack(self, enemy):
enemy.getAttacked(self.statATK)
def getAttacked(self, ATK):
self.statHEALTH -= ATK
Try doing:
while James.statHEALTH > 0:
#print statements
if choice == "1":
the_attack = James.attack(Keaton)
Then define your classes as:
class Fighter(object):
def __init__(self):
self.statHEALTH = 10
self.statATK = 3
self.statDEF = 3
def attack(self,target):
attack_details = target.takedamage(self.statATK)
return attack_details
def takedamage(self,dmg):
modified_dmg = dmg-self.statDEF
self.statHEALTH -= modified_dmg
return modified_dmg
This has the added benefit of being easily expandable, e.g. you can add a hit table (for i in random.randint(1,100) if i < 20: #miss; elif 20 <= i < 80: #hit; elif 80<= i: #crit) or resistances for certain elements or add a flag that allows your defender to counterattack in their takedamage function (perhaps calling a new function getcountered to prevent infinite looping).
The problem isn't really that you can't pass things by references in Python. In fact, you always pass values by reference; Python never copies values unless you ask it to.
Or, more accurately, that whole C-based terminology is misleading in Python.
Anyway, when you do this:
James.attack(Keaton.statHEALTH)
This doesn't make a copy of the value in Keaton.statHEALTH, it passes a reference to the exact same value. So, when attack starts, your enemyhealth variable is a name for that value.
And then you do this:
enemyhealth = (enemyhealth - self.statATK)
… the enemyhealth - self.statATK returns a new value, and then you bind that new value to the enemyhealth name. This has no effect on any other names for the old value.
There are two ways to solve this.
First, you don't actually need to mutate anything here. You're already doing return enemyhealth at the end. That means the caller can get the new value that you calculated. So why not just use that?
Keaton.statHEALTH = James.attack(Keaton.statHEALTH)
And then everything works.
Of course in C++, you can mutate integer values. This seems a little silly when you think about it—turning the number 24 into the number 19 would break a lot of math, and probably make the universe stop working, and Python just isn't that powerful.
But you can easily build a type that can be sensibly mutated. In fact, you've already built one: a Fighter holds an integer health value, but can be changed to hold a different one instead. So, you could write this:
def attack(self, enemy):
enemy.health = enemy.health - self.statATK
And then:
James.attack(Keaton)
Just as calling the old method with Keaton.health made enemyhealth into another reference to the same number, calling the new method with Keaton makes enemy into a reference to the same Fighter. If you just reassigned a new value to enemy, that wouldn't have any effect on the old value, the one that Keaton refers to. But if you change the value in-place, obviously Keaton is still referring to that now-changed value.

Categories

Resources