I want to create several "Players" instances that they will automatically lose hp after some time (e.g. 60 seconds).
Say I have a class:
class Player:
def __init__(self, hp=1000):
self.hp = hp
def lose_hp(self): #not sure if there's a better way to do this than to call this function every 60 seconds
pass
Is there a way to do that without having to call the lose_hp function for each player? (if I have 10000 players it might be difficult).
Save the base HP at object creation and the object creation time, and compute the actual HP based on the current time on the fly:
import time
class Player:
HP_LOSS_INTERVAL = 60
HP_LOSS_PER_INTERVAL = 10
def __init__(self, hp=1000):
self.base_hp = hp
self.birth_time = time.time()
def hp(self):
hp_loss = (time.time() - self.birth_time) // HP_LOSS_INTERVAL * HP_LOSS_PER_INTERVAL
return max(0, self.base_hp - hp_loss)
Here I did it with time.time(), probably in a game you want to use a timer based on your game loop ticks.
Related
How do I set a boolean from a method to create "chance" with a poison status, or use later for dodge and other reusable.
Just now learning Simple Inheritance with Super() and not yet gotten to Complex Inheritance. Not sure if that would make a difference in how I could of coded this but using what I know, here is what I have.
What I am trying to do is create a method inside the Hero Class, and make the hero only be poisoned if a condition is met or a boolean is passed. I keep getting an issue with the logic. Still new to programming.
I've re-written the code multiple times using Idle for speed and it just keeps giving me problems, even when i place the for-loop inside the hero.PoisionedCheck(True) I only get the displayed text and not the forloop activated. Then I will get an error that "amount" parameter is not defined or called before assignment, then when I fix that I will get that scorpion.health and scorpion.energy is not defined. I'm running in circles.
Thank you for reading.
import time
class Monster:
name= 'Monster'
def __init__(self,health, energy):
self.health = health
self.energy = energy
def attack(self, amount):
print('Monster has attacked')
print(f'{amount} damage was delt!')
self.energy -= 20
def move(self,speed):
print(f'{self.name} has moved, at {speed} speed')
class Scorpion(Monster):
name='Scorpion'
def __init__(self, health, energy):
super().__init__(health, energy)
def attack(self, amount):
print('Scorpion has attacked')
print('Poison has take effect!')
# Loops Poison display repeated with time delay.
# for repeater amount of times.
repeater = 5
for poison in range(repeater):
poison = 0
amount = poison + amount
print(f'Poisoned: {amount} damage')
time.sleep(0.5)
poison = repeater
amount *= poison
print(f'Poison did a total damage of {amount} to {hero.name}')
hero.health -= amount
class Hero(Monster):
name = 'Rockwood'
def __init__(self, health, energy) -> None:
self.health = health
self.energy = energy
# How do I use this in a boolean to check Scorpion attack?
# Lets say later adding *import random* later to determine chances of poison.
# def PoisonedCheck(self, posioned):
# self.poisoned = posioned
# if posioned == True:
# print("Testing if become posioned- For loop goes after here to create damage.")
# posioned = True
# else:
# print('Became else.. ')
# posioned = False
monster = Monster(health=100, energy=100)
scorpion = Scorpion(health = 200, energy = 150)
hero = Hero(health=100, energy=100)
scorpion.attack(3)
print()
scorpion.move(110)
hero.move(50)
print(f"{hero.name} has {hero.health} health remaining. ")
print()
To be honest, I am not really sure what you are trying to achieve, hopefully this answers your question or gets you going.
Firstly I would suggest adding a target parameter to Scorpion's attack behaviour and modifying it's behaviour. That way you can define multiple heroes and have them combat the scorpion, for example:
scorpion = Scorpion(health = 200, energy = 150)
hero_one = Hero(health=100, energy=100)
hero_two = Hero(health=200, energy=100)
scorpion.attack(hero_one)
scorpion.attack(hero_two)
Secondly, Scorpion doesn't seem to lose energy after attacking and doesn't deal initial damage. You could fix that by changing Scorpion attack behaviour to first call parent attack function:
class Scorpion(Monster):
...
def attack(self, amount, target):
super().attack(amount, target)
print('Poison has take effect!')
...
But Monster attack behaviour doesn't accept second argument, so my suggestion is to modify Monster's attack function with second parameter to also deal initial damage to target (and correctly subtract target's health with damage amount):
class Monster:
...
def attack(self, amount, target):
print(f'{self.name} has attacked')
print(f'{amount} damage was delt!')
target.health -= amount
self.energy -= 20
To track poisoned status, easiest you can do is to introduce state in Monster class constructor called poisoned and set it to False (initial state is that monster and every child object is not poisoned).
Then you can change target's poisoned state in Scorpion's attack method, like so:
class Scorpion(Monster):
...
def attack(self, amount, target):
...
target.poisoned = True
...
This way you can see if one of Heroes is poisoned, like so:
print(hero_one.poisoned) --> False or True (if attacked by scorpion)
To calculate a chance (lets say 50%) if Scorpion's attack will poison the target or not, extract poison application behaviour into separate private function and modify it like this:
class Scorpion(Monster):
...
def attack(self, amount, target):
super().attack(amount, target)
self.__poison_target(amount, target)
def __poison_target(self, amount, target):
# A coin toss to decide if target will be poisoned or not.
should_apply_poison = random.randint(0, 1) == 1
if should_apply_poison:
print('Poison has take effect!')
target.poisoned = True
# Loops Poison display repeated with time delay.
# for repeater amount of times.
repeater = 5
for poison in range(repeater):
poison = 0
amount = poison + amount
print(f'Poisoned: {amount} damage')
time.sleep(0.5)
poison = repeater
amount *= poison
print(f'Poison did a total damage of {amount} to {target.name}')
target.health -= amount
...
Or you can delegate damage dealing behaviour from Scorpion to Hero by creating abstract class Poisonable which you can use to check if the target is Poisonable (in your case - Hero inherits from Poisonable). This way Scorpion class doesn't care if target is successfuly poisoned or not.
Poisonable class is contract which all classes that inherit from it must abide by (e.g. implement their own version of resistance, damage taking from poison). This way every poisoned creature can react differently.
class Poison:
def __init__(self):
self.damage_per_tick = 5
self.ticks = 3
class Scorpion(Monster):
name = 'Scorpion'
def __init__(self, health, energy):
super().__init__(health, energy)
self.poison = Poison()
def attack(self, amount, target):
super().attack(amount, target)
self.__poison_target(target)
def __poison_target(self, target):
if isinstance(target, Poisonable):
target.suffer_poison_damage(self.poison)
class Poisonable(ABC):
#abc.abstractmethod
def resist_poison(self) -> bool:
pass
#abc.abstractmethod
def suffer_poison_damage(self, poison: Poison) -> None:
pass
class Hero(Monster, Poisonable, ABC):
name = 'Rockwood'
def __init__(self, health, energy) -> None:
super().__init__(health, energy)
def resist_poison(self) -> bool:
# resist poison chance
return random.randint(0, 1) == 1
def suffer_poison_damage(self, poison: Poison) -> None:
if self.resist_poison():
pass
else:
print('Poison has take effect!')
# Loops Poison display repeated with time delay.
# for repeater amount of times.
total_amount = 0
for poison_tick in range(poison.ticks):
total_amount += poison.damage_per_tick
print(f'Poisoned: {poison.damage_per_tick} damage')
time.sleep(0.5)
print(f'Poison did a total damage of {total_amount} to {self.name}')
self.health -= total_amount
I'm working on a simple skeleton for a game, and in an effort to try and be more "pythonic", I'm using objects/classes/dictionaries to try and capture all my actions/behaviors (as methods over functions, etc).
For some reason, every time I execute the method 'act' within the class "Player", the dictionary embedded within act runs all of its values (which are, in turn, methods from within the same instance of the class "Player"). In other words, the player chooses "attack, heal, and flee" every time, all at once, before being prompted.
I'm sure there's a simple explanation, but I've been looking for hours and can't find another example of someone's dictionary auto-running all the methods embedded within. Can you help?
Thanks!
- Jake
from random import randint
### BEGIN ALL CLASSES HERE
# To be used for all game objects (living and non-living)
class gameObject(object):
def __init__(self, name):
self.name = name
# To be used for all characters who can act in some way/be killed/change
class livingThing(gameObject):
def __init__(self, name, HP=1):
self.name = name
self.HP = HP
# The playable character(s)
class Player(livingThing):
def __init__(self,name="The Stranger", HP=4, MP=5, strength=1, intellect=1, spirit=1, luck=5, gil=6):
self.name = name
self.HP = HP
self.MP = MP
self.gil = gil
self.strength = strength
self.intellect = intellect
self.spirit = spirit
self.luck = luck
def act(player, enemy):
actions = {
"attack" : player.attack(enemy),
"heal" : player.heal(enemy),
"flee" : player.flee()
}
#Takes input from the player
decision = input("What would you like to do? ")
if decision.lower() in actions:
actions[decision.lower()]
else:
print("That didn't work! Try again.")
# Prints both player and enemy HP
def printHP(player, enemy):
print("{0}'s' HP: {1} \n{2}'s HP: {3}".format(player.name, player.HP, enemy.name, enemy.HP))
# Allows the player to attack an enemy (currently functional)
def attack(player, enemy):
enemy.HP -= player.strength
print("You strike {0} for {1} damage!".format(enemy.name, player.strength))
player.printHP(enemy)
# Allows the player to heal a certain amount of health based on its "spirit" stat (currently functional)
def heal(player, enemy):
healed = randint(0, player.spirit)
player.HP += healed
print("You've healed for {0}!".format(healed))
player.printHP(enemy)
#Allows the player to attempt to run away
def flee(player):
randluck = randint(0, player.luck)
if randluck > 3:
print("You successfully escaped!")
return player.HP
else:
print("You weren't able to escape!")
# Anything that can act with/against the player
class Actor(livingThing):
def __init__(self, name="Unknown Entity", HP=10, MP=2, gil=3):
self. name = name
self.HP = HP
self.MP = MP
self.gil = gil
### END ALL CLASSES ###
### DICTIONARIES CONTAINING ACTIONS ###
### CHARACTERS ###
fighter = Player()
monster = Actor()
fighter.act(monster)
I see the problem. When you are executing Python code, and you have a dictionary as you do, Python evaluates the dictionary fully. If you wanted your values (in your key:value) pairs to be the results of those methods, this is surely one way to do it.
In your case, what you can do is reference the function itself, and not invoke it. You can do this by getting rid of the parentheses, like this:
player.attack
instead of
player.attack()
Then, to call the function you can do something like
actions[decision.lower()](enemy)
Since one of your functions, flee, doesn't accept any parameters, you could give flee a parameter that you simply don't use in the function. If you were designing many many methods that your player can act with, then one strategy would be to give them all only named parameters, like this:
def f1(enemy=None,something=None,foo=None):
if enemy is None:
raise Exception("enemy cannot be None")
#process_enemy
If however, you also have a very high amount of parameters, then you could do this:
def attack(**kwargs):
#kwargs is a dictionary of parameters provided to the function
enemy = kwargs.get('enemy',None)
if enemy is None:
raise Exception("enemy cannot be None")
def eat(**kwargs):
food = kwargs.get('food',None)
if enemy is None:
raise Exception("food cannot be None")
attack(enemy="someenemyobject")
eat(food="somefoodobject")
attack() # raises Exception
attack(food="somefoodobject") # raises Exception
food(enemy="someenemyobject") # raises Exception
food(food="somefoodobject",enemy="someenemyobject") # does not raise Exception
I am trying to make an elevator simulation because of an interesting problem I saw on CareerCup. My problem is that I want the elevator to "take time" to move from one floor to another. Right now it just instantly moves to the next floor in its "to visit" list. I'm not sure how to program it so that "pickup requests" can be coming in while the elevator is moving. I think this may require threading, and the time.sleep() function. How do I make one thread that makes random requests to the elevator, and another thread that has the elevator trying to meet all of the requests? This is what I have so far:
import time
from random import *
import math
class Elevator:
def __init__(self, num_floors):
self.going_up = False
self.going_down = False
self.docked = True
self.curr_floor = 0
self.num_floors = num_floors
self.floors_to_visit = []
self.people_waiting = []
def print_curr_status(self):
for i in range(self.num_floors):
if i == self.curr_floor:
print('. []')
else:
print('.')
print ("to_visit: ", self.floors_to_visit)
def handle_call_request(self, person):
if not self.going_up and not self.going_down:
self.floors_to_visit = [person.curr_floor] + self.floors_to_visit
self.going_up = True
self.docked = False
self.people_waiting.append(person)
else:
self.floors_to_visit.append(person.curr_floor)
self.people_waiting.append(person)
def handle_input_request(self, floor_num):
self.floors_to_visit.append(floor_num)
def go_to_next(self):
if not self.floors_to_visit:
self.print_curr_status()
return
self.curr_floor = self.floors_to_visit.pop(0)
for i,person in enumerate(self.people_waiting):
if person.curr_floor == self.curr_floor:
person.riding = True
person.press_floor_num()
self.people_waiting.pop(i)
return
class Person:
def __init__(self, assigned_elevator, curr_floor):
self.curr_floor = curr_floor
self.desired_floor = math.floor(random() * 10)
self.assigned_elevator = assigned_elevator
self.riding = False
def print_floor(self):
print(self.desired_floor)
def call_elevator(self):
self.assigned_elevator.handle_call_request(self)
def press_floor_num(self):
self.assigned_elevator.handle_input_request(self.desired_floor)
my_elevator = Elevator(20)
while True:
for i in range(3):
some_person = Person(my_elevator, math.floor(random() * 10))
some_person.call_elevator()
my_elevator.go_to_next()
my_elevator.print_curr_status()
time.sleep(1)
No threding is neccessary. You can introduce 2 new variables: one keeping track on the time the elevator started and one for the time an elevator ride should take. Then just just check when the elevator has run long enough. You can do this calling the function time.time(); it'll return the time in seconds since January 1, 1970 (since you're only interested in the difference it doesn't matter; you just need a function that increment in time). Although, this function often can't give a more accurate time period than 1 second. If you feel it's to inaccurate on your machine then you could use datetime.
class Elevator:
def __init__(self, num_floors):
self.start_time = 0
self.ride_duration = 1
...
def call_elevator(self):
self.start_time = time.time()
self.assigned_elevator.handle_call_request(self)
def go_to_next(self):
if time.time() - self.start_time < self.ride_duration:
return # Do nothing.
else:
...
You'll probably need to refactor the code to suit your needs and add some logic on what to do when the elevator is in use, etc.
I've been putting together an isometric tile-based RPG using Python and the Pyglet library. I've run into the following problem, however:
My player movement is based on positions on the three-dimensional array that consists of tiles. To limit movement speed, I use a global variable: TILE_TO_TILE_DELAY = 200.
TILE_TO_TILE_DELAY is supposed to be the amount of time in milliseconds it takes for the player to move from one tile to another. During this time, they should not be able to make a new movement.
The system I've been using is that I have a timer class, like this:
import time
def GetSystemTimeInMS(): #Return system time in milliseconds
systime = round((time.clock()*1000), 0)
return systime
class Timer:
def __init__(self,time):
#time = time for which the timer runs
self.time = time
self.start_time = 0
self.stop_time = 0
def StartTimer(self):
#start_time = time at which the timer was started
self.start_time = GetSystemTimeInMS()
self.stop_time = self.start_time + self.time
def TimerIsStopped(self):
if GetSystemTimeInMS() >= self.stop_time:
return True
else:
return False
The player class has a Timer object:
self.MoveTimer = Timer(TILE_TO_TILE_DELAY)
When the player presses the W-key, a function is called that checks for player.MoveTimer.TimerIsStopped(). If it returns True, it calls player.MoveTimer.StartTimer() and starts a new movement to the next position.
In the even loop, the update(dt) function is set to happen 30 times a second:
def update(dt):
if player.MoveTimer.TimerIsStopped()
player.UpdatePosition()
pyglet.clock.schedule_interval(update, 1/30)
Now, by my logic, update(dt) should check 30 times a second whether or not enough time has passed to warrant the player a new movement event. However, for some reason the player moves much faster at lower FPS.
When my FPS is around 30, the player moves much faster than in areas where there are less tile sprites, pumping the framerate to 60. In areas where FPS is high, the player indeed by my measurements moves almost twice as slowly.
I just cannot figure it out, nor did I find anything off the internet after a day of searching. Some help would be much appreciated.
Edit: The code that starts the MoveTimer:
def StartMovement(self, new_next_pos):
self.RequestedMovement = False
if self.GetCanMoveAgain():
self.SetNextPos(new_next_pos)
self.SetMoveDirection(self.GetDirection())
#Start the timer for the movement
self.MoveTimer.StartTimer()
self.SetIsMoving(True)
self.SetStartedMoving(True)
self.SetWalking(True)
self.SetNeedUpdate(True)
self.MOVE_EVENT_HANDLER.CreateMoveEvent()
GetCanMoveAgain() returns the value of player.can_move_again, which is set back to True by the UpdatePosition() in update(dt)
Alright, I fixed the problem, how ever I'm still unsure about what caused it. Maybe it was some sort of a rounding error with milliseconds, having to do with more checks being made to the clock when the FPS is higher. Anyways, the solution:
Instead of using the system clock, I decided to use Pyglet's own "dt" argument in their update functions:
The dt parameter gives the number of seconds (due to latency, load and timer inprecision, this might be slightly more or less than the requested interval).
The new timer looks like this:
class dtTimer: #A timer based on the dt-paremeter of the pyglet event loop
def __init__(self,time):
self.time = time
self.time_passed = 0
def StartTimer(self):
self.time_passed = 0
def UpdateTimer(self, dt):
self.time_passed += dt*1000
def GetTime(self):
return self.time
def GetTimePassed(self):
if not self.TimerIsStopped():
return self.time_passed
else:
return 0
def TimerIsStopped(self):
if self.time_passed > self.time:
return True
else:
return False
When the loop attempts to update the player's position, if the TimerIsStopped returns false, dt is simply added to the Timer's self.time_passed. This has fixed the issue: the time it takes for the player to move is now constant.
Thanks for looking at my issue.
I've been trying to make this class called Time with has the attributes of hour,minutes and seconds, that also has accessor functions and mutator functions such as set_hour, increment_hour and so on.
This is my code, I cannot get it to work I get the error Time is not defined or t is not defined when i switch the last lines around. Python 3.2.5 by the way.
class Time:
"""The Time class defines the time with
attributes: hour, minute, second
"""
#Attributes
hour = 12
minutes = 00
seconds = 00
#Functions
def get_hour(self):
return self.hour
def get_minute(self):
return self.minute
def get_second(self):
return self.second
def print_time(self):
print("Hello, the current time is", self.hour,":",self.minute,":",self.second)
def set_hour(self, new_hour):
self.hour = new_hour
t.set_hour("1")
print(t.get_hour())
print(t.print_time())
t = Time()
It seems you are invoking the method set_hour("1") on a variable t before that variable has been initialized by t = Time().
EDIT: and correct the indentation as said in the comments. I'm not a Python programmer so I didn't catch that.