Classes - Attribute error - python

I am currently working through the book Learn Python 3 The Hard Way.
I have reached exercise 43 where a small text adventure game is written to practice Object Oriented programming.
I completed typing in the code for the program and got it working without any errors. Then in the additional study section, the author asked for a simple combat system to be added to the script.
The full script can be seen at this link, I did not want to paste the whole script for ex43 into this question.
https://github.com/ubarredo/LearnPythonTheHardWay/blob/master/ex43.py
I created the following script as a combat system:
import random
import time
class Player:
def __init__(self, hit_points, sides):
self.hit_points = hit_points
self.sides = sides
def take_hit(self):
self.hit_points -= 2
def roll(self):
return random.randint(1, self.sides)
class Combat(Player):
def combat_start():
p = Player(hit_points = 10, sides = 6)
e = Player(hit_points = 8, sides = 6)
battle = 1
while battle != 0:
human = p.roll() + 6
print("Your hit score: ", human)
monster = e.roll() + 6
print("Enemy hit score: ", monster)
if human > monster:
e.take_hit()
print("Your hit points remaining: ", p.hit_points)
print("Enemy hit points remaining:", e.hit_points)
if e.hit_points == 0:
battle = 0
print("The Enemy is Dead!!!")
time.sleep(2)
elif human < monster:
p.take_hit()
print("Your hit points remaining: ", p.hit_points)
print("Enemy points remaining: ", e.hit_points)
if p.hit_points == 0:
battle = 0
print("You died in Battle!!!")
time.sleep(2)
Combat.combat_start()
This works fine on its own and I wanted to use it in the script from the book.
I attempted to call it from the CentralCorridor class. After the player types in "shoot" I called the script I had written by adding:
Combat.combat_start()
What I hoped would happen is the Combat class I had written would start, then when the player won it would continue to the next scene, and if the player lost it would return the Death class.
After much trying and failing, I changed the Combat class to:
class Combat(Scene, Player):
With this added the script ran then breaks out of the loop.
Your hit score: 12
Enemy hit score: 12
Your hit score: 10
Enemy hit score: 11
Your hit points remaining: 8
Enemy points remaining: 4
Your hit score: 10
Enemy hit score: 10
Your hit score: 12
Enemy hit score: 7
Your hit points remaining: 8
Enemy hit points remaining: 2
Your hit score: 7
Enemy hit score: 9
Your hit points remaining: 6
Enemy points remaining: 2
Your hit score: 7
Enemy hit score: 8
Your hit points remaining: 4
Enemy points remaining: 2
Your hit score: 9
Enemy hit score: 10
Your hit points remaining: 2
Enemy points remaining: 2
Your hit score: 10
Enemy hit score: 9
Your hit points remaining: 2
Enemy hit points remaining: 0
The Enemy is Dead!!!
Traceback (most recent call last):
File "ex43.py", line 220, in <module>
a_game.play()
File "ex43.py", line 24, in play
next_scene_name = current_scene.enter()
AttributeError: 'NoneType' object has no attribute 'enter'
If the enemy players score is higher the loop break after one or two rounds.
I'm struggling to find a solution to this and would really appreciate some pointers of where I'm going wrong.
Looking through other answers, I have seen some comment that this is not an ideal way of writing OOP in Python.
In the book it shows:
class Scene(object):
And from what I have read this is the Python 2 style of OOP. I have seen that the Author is more of a Python 2 fan than Python 3.
I do enjoy the exercises he has put into the book and want to continue onto his next one.
Any help as always will be very much appreciated.
----UPDATE----
this is the Play method as requested:
def play(self):
current_scene = self.scene_map.opening_scene()
last_scene = self.scene_map.next_scene('finished')
while current_scene != last_scene:
next_scene_name = current_scene.enter()
current_scene = self.scene_map.next_scene(next_scene_name)
# be sure to print out the last scene
current_scene.enter()

I fixed the issue. After writing,
Combat.combat_start()
I needed to enter:
return 'laser_weapon_armory'
without that the program was crashing as it had no-where to go next.
Also fixed the logic in the Combat class.
Thanks for the help with this.

Related

Telium Adventure Game: how to fix the problem where it prints out any npc's in the room but instead it just prints there is 'None' here

There is module files as well but they work perfectly, the main problem is when you enter a module and it just printing out There is 'none' in here for every module. Additionally, if I wanted to change the fuel gain from 50 to randomly chose between 20,30,40 or 50.
The rest of the code works well but when the npc in a room is outputted it should say there is a 'workers' in here instead of just none for each module.
#Telium - Game
import random
#Global Variables
num_modules = 17 #Number of modules in the space station
module = 1 #Module of space station we are in
last_module = 0 #Last module we were in
possible_moves = [] #List of possible moves we can make
alive = True #Whether player is alive or not
won = False #Whether player has won
power = 100 #Amount of power the space station has
fuel = 500 #Amount of fuel the player has in flamethrower
locked = 0 #Module that has been locked by the player
queen = 0 #Location of queen alien
vent_shafts = [] #Location of ventilation shaft entrances
info_panels = [] #Location of info panels
workers = [] #Location of worker aliens
#procedure declarations
#This loads the global module
def load_module():
global module, possible_moves
possible_moves = get_modules_from(module)
output_module()
def get_modules_from(module):
moves = []
text_file = open("Charles_Darwin\module" + str(module) + ".txt", "r")
for counter in range(0,4):
move_read = text_file.readline()
move_read = int(move_read.strip())
if move_read != 0:
moves.append(move_read)
text_file.close()
return moves
def output_module():
global module
print()
print("--------------------------------------------------------------
-----------------------")
print()
print("You are in module",module)
print()
npc = spawn_npcs()
print("There is a ", npc ,"here")
def output_moves():
global possible_moves
print()
print("From here you can move to modules: | ",end='')
for move in possible_moves:
print(move,'| ',end='')
print()
def get_action():
global module, last_module, possible_moves, power
valid_action = False
while valid_action == False:
print("What do you want to do next ? (MOVE, SCANNER)")
action = input(">")
if action == "MOVE" or action.lower() == 'move' or action.lower()
== 'm' or action.higher() == 'M':
move = int(input("Enter the module to move to: "))
if move in possible_moves:
valid_action = True
last_module = module
module = move
#power is decreased by 1 for every move
power =- 1
else:
print("The module must be connected to the current
module.")
def spawn_npcs():
global num_modules, queen, vent_shaft, greedy_info_panels, workers
module_set = []
for counter in range(2,num_modules):
module_set.append(counter)
random.shuffle(module_set)
i = 0
queen = module_set[i]
for counter in range(0,3):
i=i+1
vent_shafts.append(module_set[i])
for counter in range(0,2):
i=i+1
info_panels.append(module_set[i])
for counter in range(0,3):
i=i+1
workers.append(module_set[i])
def check_vent_shafts():
global num_modules, module, vent_shafts, fuel
if module in vent_shafts:
print("There is a bank of fuel cells here.")
print("You load one into your flamethrower.")
fuel_gained = 50
print("Fuel was",fuel,"now reading:",fuel+fuel_gained)
fuel = fuel + fuel_gained
print("The doors suddenly lock shut.")
print("What is happening to the station?")
print("Our only escape is to climb into the ventilation shaft.")
print("We have no idea where we are going.")
print("We follow the passages and find ourselves sliding down.")
last_module = module
module = random.randint(1,num_modules)
load_module()
#Main Program starts here
#Menu options
print("ENTER 1 for instructions")
print("ENTER 2 to play")
print("ENTER 3 to quit")
menu = int(input("Please enter a number corresponding to what you want to
do: "))
if menu == 1:
instructions = input("Do you want to read the instructions(Y/N): ")
if instructions == "Y":
print("You, the player are trying to navigate around a space
station named the 'Charles Darwin' which contains many modules")
print("The aim of the game is to find a and trap the queen alien
called 'Telium' who is located somewhere randomly in the station, the
queen will try to escape to connectinhg modules so beware")
print("To win - you have to lock the queen in one of the modules
so she is trapped, you can kill her with a flamethrower, there is also
objects to help on the way so keep a look out")
spawn_npcs()
#Outputs where the queen, shafts, panels and workers are located
print("Queen alien is located in module:",queen)
print("Ventilation shafts are located in modules:",vent_shafts)
print("Information panels are located in modules:",info_panels)
print("Worker aliens are located in modules:",workers)
#when the players is alive, the module will load
while alive and not won:
load_module()
if won == False and alive == True:
output_moves()
get_action()
#if power is 0 then the user will die and the game will end
if power == 0:
print("You ran out of life support, you died")
alive == False
#win message once you have trapped the queen or when you run out of life
support
if won == True:
print("The queen is trapped and you burn it to death with your
flamethrower.")
print("Game over. You win!")
if alive == False:
print("The station has run out of power. Unable to sustain life
support, you die.")
check_vent_shafts()
First the easy part. You can randomly get 20, 30, 40 or 50 by using random.randint like this:
random.randint(2, 5) * 10
The harder part:
As you discussed with #Random Davis in the comments, you want to assign the npcs to random modules and then print which one you encounter.
To append three random (possibly repeating) modules, use
for _ in range(0,3):
vent_shafts.append(random.choice(module_set))
Using global variables (and the global keyword) is generally considered bad practice as it can cause sideeffects due to other parts of the program modifying a variable you wouldn't expect it to / forgot about. Try to use them as function parameters where needed and return the results
queen, vent_shafts, info_panels, workers = spawn_npcs(5)
def spawn_npcs(num_modules):
module_set = []
for i in range(2, num_modules):
module_set.append(i)
for _ in range(0,3):
vent_shafts.append(random.choice(module_set))
for _ in range(0,2):
info_panels.append(random.choice(module_set))
for _ in range(0,3):
workers.append(random.choice(module_set))
queen_module = module_set[0]
return queen_module, vent_shafts, info_panels, workers

Pokemon game in python: classes/objects, battle and deepcopy

I'm new on python and I'm trying to do a Pokemon game (like the gameboy and nds games). I've created the pokemon and trainer classes and I want to implement the battle mechanics. I thought it was working fine, but then I found that if I lose the battle the program enters in an infinite loop.
I tried a lot to found the error and I discovered that when it exits the 'while alive(pokemon1) and alive(pokemon2)' loop, the pokemon1 hp returns to full hp but the pokemon2 remains with the current hp (I've put a print to show it). It makes non sense for me because the code is identical for both trainers. Anyone knows why is it happening? I want it to continue with the current hp of both pokemon at the end of the loops and battle.
Here is a sample of my code:
import copy
import random
class Pokemon(object):
def __init__(self, name, hp):
self.name = name
self.hp = hp
def ataque(self, oponent):
oponent.hp = oponent.hp - 150 #just for a test
class Trainer(object):
def __init__(self, name, *pokemon):
self.name = name
self.pokemon, self.pokemonname = [], []
for pkmn in pokemon:
self.pokemon.append(copy.deepcopy(pkmn))
self.pokemonname.append(pkmn.name)
weavile = Pokemon ('Weavile', 150) #change the hp to more than 150 if you want to win
garchomp = Pokemon ('Garchomp', 250)
roserade = Pokemon ('Roserade', 160)
ambipom = Pokemon ('Ambipom', 160)
me = Trainer('You', weavile) #or choose ambipom to win
cynthia = Trainer('Cynthia', garchomp)
def alive(pokemon): #check if the pokemon is alive
if pokemon.hp>0:
return True
if pokemon.hp<=0:
pokemon.hp=0
return False
def battle(trainer1, trainer2): #battle between two trainers
fight1, fight2 = True, True
while fight1 and fight2:
print ('Choose a pokemon:', trainer1.pokemonname)
pokemon1 = eval(input())
while not alive(pokemon1):
print (pokemon1.name, 'is out of batlle. Choose another pokemon')
pokemon1 = eval(input())
pokemon2 = random.choice(trainer2.pokemon)
while not alive(pokemon2):
pokemon2 = random.choice(trainer2.pokemon)
print (trainer1.name, 'chose', pokemon1.name, '\n', trainer2.name, 'chose', pokemon2.name)
while alive(pokemon1) and alive(pokemon2):
pokemon1.ataque(pokemon2)
if alive(pokemon2):
pokemon2.ataque(pokemon1)
print (pokemon1.name, pokemon1.hp)
print (pokemon2.name, pokemon2.hp)
print (trainer1.pokemon[0].name, trainer1.pokemon[0].hp) #here its returning the original hp
print (trainer2.pokemon[0].name, trainer2.pokemon[0].hp) #here its returning the current hp, as it should
if not any(alive(pokemon) for pokemon in trainer1.pokemon):
fight1 = False
if not any(alive(pokemon) for pokemon in trainer2.pokemon):
fight2 = False
battle(me, cynthia)
If you run the code in the way I wrote here, you need to choose one of your pokemon on the list (there's only one: weavile - to simplify), so just type weavile. After that, weavile will attack and then be attacked by garchomp. Weavile will be defeated (0 hp) in the first hit but after the loop 'while alive(pokemon1)...' it will show that weavile still has 150 hp and it will continue to ask you to choose a pokemon even if it's already dead.
If you want to win: choose ambipom instead of weavile in 'me' object or change the weavile hp to more than 150 in weavile object. If you do this it will run as I want and end the battle.

Why won't my for loop subtract from a variable?

I'm trying to make a super-simple, text-based boxing game as a practice exercise. I want each pass through the loop to throw a punch and inflict a random amount of damage. Each punch will hit a random part of the opponent.
However, the damage never gets subtracted from the original amount.
Where did I go wrong?
Here is my code and output for a couple of rounds.
#starting stats
nose = 100
jaw = 100
face = 100
head = 100
hp = nose + jaw + face + head
moral = 200
import random
#fight
for fight in range(10):
if hp <= 0:
print("DING DING DING\nKNOCK OUT\nYOU ARE THE WINNER!!!!!!")
if hp <= 25:
moral - 25
print("He's losing moral!")
if moral <=0:
hp - 75
print("He's about to KO!!!")
if hp> 0:
print("=========\nMORAL:{0}\nHP:{1}\nNOSE:{2}\nJAW:{3}\nFACE:{4}\nHEAD:{5}\n=========".format(moral, hp, nose, jaw, face, head))
move = input('TYPE "P" TO THOW A PUNCH!!!')
if move == "p" or "P" or "PUNCH":
part = int(random.randint(1,4))
damage = int(random.randint(1, 100))
if part == 1:
print("PUNCH TO THE NOSE!!!")
nose - damage
elif part == 2:
print("PUNCH TO THE JAW!!!")
jaw - damage
elif part == 3:
print("PUNCH TO THE FACE!!!")
face - damage
elif part == 4:
print("PUNCH TO THE HEAD!!!")
head - damage
Output:
=========
MORAL:200
HP:400
NOSE:100
JAW:100
FACE:100
HEAD:100
=========
TYPE "P" TO THOW A PUNCH!!!P
PUNCH TO THE HEAD!!!
=========
MORAL:200
HP:400
NOSE:100
JAW:100
FACE:100
HEAD:100
=========
TYPE "P" TO THOW A PUNCH!!!P
PUNCH TO THE HEAD!!!
=========
MORAL:200
HP:400
NOSE:100
JAW:100
FACE:100
HEAD:100
=========
TYPE "P" TO THOW A PUNCH!!!
Statements like moral - 25 don't do anything at all; they subtract one value from another but then discard the result. You actually need to assign the result back to your variable:
moral = moral - 25
which can be shortened to
moral -= 25
You neglect to save the result. Try this:
hp = hp - 75
Better yet, use the short-hand:
hp -= 75
hp - 75 never resets the variable. With this in mind, hp will always be the global value of 400. Use the -= syntax to accomplish this:
hp -= 75

montyhall simulator gone wrong(python)

#
# Monty Hall Problem Simulation
# Author: Ryan Sturmer
#
import random
def play_round(doors, switch):
# Choose the location of the car
car = random.randint(1, doors)
# Contestant chooses a door
initial_choice = random.randint(1, doors)
# Monty opens ALL the other doors except one
if initial_choice != car:
monty_leaves = car # If the car wasn't chosen, Monty is forced to reveal its location
else:
while True:
monty_leaves = random.randint(1, doors)
if monty_leaves != initial_choice:
break
# monty_leaves is now the door that Monty DIDN'T open
if switch:
final_choice = monty_leaves
else:
final_choice = initial_choice
victory = (final_choice == car)
return victory, initial_choice, final_choice, car
def simulation(iterations, doors=3):
games_won_switch = 0
games_won_noswitch = 0
for i in range(iterations):
won_game, intial_choice, final_choice, car = play_round(doors, False)
if(won_game):
games_won_noswitch += 1
won_game, intial_choice, final_choice, car = play_round(doors, True)
if(won_game):
games_won_switch += 1
print ""
print " Monty Hall Simulation"
print "---------------------------------------------"
print " Iterations: %d" % iterations
print " Games won when switching doors: %d (%g%%)" % (games_won_switch, 100*float(games_won_switch)/float(iterations))
print "Games won when NOT switching doors: %d (%g%%)" % (games_won_noswitch, 100*float(games_won_noswitch)/float(iterations))
===========================================================================
I found this code from github.
When I run this code, the sum of games_won_switch and games_won_noswitch is not equals to iteration.
(For example, if I set iteration 1,000 -> it appears 996, 1,001, 1,008 not exact 1,000)
How can i fix problem this up?
There is no mistake in the code; it runs the simulations twice:
Once when the player chooses to switch doors every time, and another time when the player chooses to never switch doors. It then prints the results of both simulations.
The results are from independent simulations.
for i in range(iterations):
# Sim with player choose to open the door each time
won_game, intial_choice, final_choice, car = play_round(doors, False)
if(won_game):
games_won_noswitch += 1
# Sim with player choose NEVER to open the door
won_game, intial_choice, final_choice, car = play_round(doors, True)
if(won_game):
games_won_switch += 1
In consequence, because sim(open=True) is not (1 - sim(open=False)), but result from two sets of simulations, the addition of results may not add up precisely to the number of trials.

Simple way to decrease values without making a new attribute?

I'm making a program where you're firing a 'blaster', and I have 5 ammo. I'm blasting an alien who has 5 health. At the end I instantiate the player and make him blast 6 times to check that the program works correctly. But the way I've done it makes it so that the amount won't decrease. Is there an easy fix to this, or do I just have to make a new attribute for ammo and health? Here's what I have:
class Player(object):
""" A player in a shooter game. """
def blast(self, enemy, ammo=5):
if ammo>=1:
ammo-=1
print "You have blasted the alien."
print "You have", ammo, "ammunition left."
enemy.die(5)
else:
print "You are out of ammunition!"
class Alien(object):
""" An alien in a shooter game. """
def die(self, health=5):
if health>=1:
health-=1
print "The alien is wounded. He now has", health, "health left."
elif health==0:
health-=1
print "The alien gasps and says, 'Oh, this is it. This is the big one. \n" \
"Yes, it's getting dark now. Tell my 1.6 million larvae that I loved them... \n" \
"Good-bye, cruel universe.'"
else:
print "The alien's corpse sits up momentarily and says, 'No need to blast me, I'm dead already!"
# main
print "\t\tDeath of an Alien\n"
hero = Player()
invader = Alien()
hero.blast(invader)
hero.blast(invader)
hero.blast(invader)
hero.blast(invader)
hero.blast(invader)
hero.blast(invader)
raw_input("\n\nPress the enter key to exit.")
Think about it: the amount of ammunition available is part of a player's state. An object's state is best represented as instance variables of that object. So you should not have ammo as an argument to blast -- it should be self.ammo in that method, initialized to 5 or whatever in the __init__ you forgot to code;-).
It's not a matter of seeking for fancy workarounds to hide and stash that state somewhere else -- it's a matter of doing things in the simplest, most straightforward, most effective way. Why would you ever want anything but such a way?!
You need to keep track of the alien's health. All you're doing now is decrementing the "health" local variable in the Alien.die function.
Here's a little snippet that should help get you going in the right direction:
class Alien(object):
def __init__(self):
self.health = 5
def do_damage(self, amount):
self.health -= amount
Similar tracking required for player's ammo.
I just modify the above program by making 2 attributes named ammo and health.
I think they make program so easy. Try it for various outcomes by changing attributes' initial values.
class Player(object):
""" A player in a shooter game. """
def __init__(self, ammo):
self.ammo = ammo
def blast(self, enemy):
if enemy.health > 0:
if self.ammo > 0:
print "The player has blasted the alien.\n"
print "The player has", self.ammo, "ammunition left."
enemy.die()
elif self.ammo == 0:
print "The player can't blast the alien because he is out of ammunition.\n"
self.ammo -= 1
class Alien(object):
""" An alien in a shooter game. """
def __init__(self, health):
self.health = health
def die(self):
if self.health > 0:
self.health -= 1
print "The alien is wounded. He now has", self.health, "health left.\n"
if self.health == 0:
self.health -= 1
print "The alien gasps and says, 'Oh, this is it. This is the big one. \n"\
"Yes, it's getting dark now. Tell my 1.6 million larvae that I "\
"loved them...\nGood-bye, cruel universe.'\n"
elif self.health < 0:
print "The alien's corpse sits up momentarily and says, 'No need to blast me, I'm dead already!"
print "\t\tDeath of an Alien\n"
hero = Player(6)
invader = Alien(3)
blast = int(raw_input("How many times do you want to blast the alien? "))
for cnt in range(blast - 1):
hero.blast(invader)
Probably you want to do something like this:
class Player:
def __init__(self, ammo=5):
self.ammo=ammo
def blast(self, enemy):
if self.ammo>0:
self.ammo-=1
enemy.die(5)
else:
print "U are out of ammunition!"
You need to use self.health for the alien as well.
In class Player in the method named blast() your references to to ammo are incorrect. You want to refer to self.ammo in all cases within that function/method definition.
Furthermore you should probably (almost certainly) define an __init__ method to set the starting ammunition value to 5 (and perhaps later provide a reload() method to set self.ammo back up to 5 ... and so on.

Categories

Resources