I am doing a pygame program from a book where a chef drops pizzas and you have to catch them with a pan. The chef and pan sprites are only created once, while the pizza sprites obviously keep getting created. I am trying to make it so that as the score gets higher, the chef starts to move around faster (only moves in the x). I think I am running into trouble with class attributes vs instance attributes. I have been unable to find a way to access the score from the chef class, even though I tried making score a global variable or even just assigning it to a dummy global variable (This would allow me to make changes to the chefs speed within the update method). Alternatively, I tried accessing the chefs dx within the pan class since that is where the score is. I have also been unable to access that, even with the getattr method. Would greatly appreciate any suggestions on this one. Here is the code for the pan and chef class. I have commented out parts of stuff that I tried and didn't work.
from livewires import games, color
import random
games.init(screen_width = 640, screen_height = 480, fps = 50)
class Pan(games.Sprite):
"""
A pan controlled by player to catch falling pizzas.
"""
image = games.load_image("pan.bmp")
def __init__(self):
""" Initialize Pan object and create Text object for score. """
super(Pan, self).__init__(image = Pan.image,
x = games.mouse.x,
bottom = games.screen.height)
self.score = games.Text(value = 0, size = 25, color = color.black,
top = 5, right = games.screen.width - 10)
games.screen.add(self.score)
def update(self):
""" Move to mouse x position. """
self.x = games.mouse.x
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
self.check_catch()
def check_catch(self):
""" Check if catch pizzas. """
for pizza in self.overlapping_sprites:
self.score.value += 10
#increase the speed of the pizza
# if self.score.value % 100 == 0:
# Pizza.speed += 0.1
# print(Pizza.speed)
#increase the speed of the chef
# if self.score.value % 100 == 0:
# print(Chef.dx)
# print(x)
# y = int(x)*2
# print(y)
self.score.right = games.screen.width - 10
pizza.handle_caught()
class Chef(games.Sprite):
"""
A chef which moves left and right, dropping pizzas.
"""
image = games.load_image("chef.bmp")
speed = 2
def __init__(self, y = 55, odds_change = 200):
""" Initialize the Chef object. """
super(Chef, self).__init__(image = Chef.image,
x = games.screen.width / 2,
y = y,
dx = Chef.speed)
self.odds_change = odds_change
self.time_til_drop = 0
def update(self):
#x = getattr(Pan,"score")
#print(x)
""" Determine if direction needs to be reversed. """
if self.left < 0 or self.right > games.screen.width:
self.dx = -self.dx
elif random.randrange(self.odds_change) == 0:
self.dx = -self.dx
self.check_drop()
def check_drop(self):
""" Decrease countdown or drop pizza and reset countdown. """
if self.time_til_drop > 0:
self.time_til_drop -= 1
else:
new_pizza = Pizza(x = self.x)
games.screen.add(new_pizza)
# set buffer to approx 30% of pizza height, regardless of pizza speed
self.time_til_drop = int(new_pizza.height * 1.3 / Pizza.speed) + 1
def main():
""" Play the game. """
wall_image = games.load_image("wall.jpg", transparent = False)
games.screen.background = wall_image
the_chef = Chef()
games.screen.add(the_chef)
the_pan = Pan()
games.screen.add(the_pan)
games.mouse.is_visible = False
games.screen.event_grab = True
games.screen.mainloop()
# start it up!
main()
As the chef speed is a class variable you need to add the class name to access it, as in: Chef.speed. Where is the pizza class? I don't know Livewire so can't explain why you can't access the score, but surely you can set it as a number somehow and use it that way?
Thanks for your time. I was able to solve it by creating an attribute flag within the Pan class to check the score and then accessing that attribute flag in the Chef's update method, to then be able to change dx. Chef.speed is only the initial chef speed so changing it doesn't update the chef's dx.
Related
I'm trying to create an agent-based model using the mesa python framework. I want my agents to reproduce only on certain days of the year. Specifically, on days 203 through 209. Here is my model so far:
import mesa
import random
#set up class for agent
class Oyster(mesa.Agent):
"""An agent with randomly assigned initial energy & age."""
#define init values
def __init__(self, unique_id, model):
super().__init__(unique_id, model)
self.age = random.randint(0, 3649)
self.energy = random.randint(1,10)
#define what happens at each step
def step(self):
living = True
self.age += 1
self.energy += random.randint(-5, 5)
# Death
if self.energy < 0 or self.age > 3650:
self.model.grid.remove_agent(self)
self.model.schedule.remove(self)
living = False
# Repoduction
if living & self.energy >= 1.5 and self.age > 365:
for i in range(random.randint(1,6)):
babyOyster = Oyster(
self.model.next_id(), self.model, self.energy, self.age
)
self.model.grid.place_agent(babyOyster, self.pos)
self.model.schedule.add(babyOyster)
#set up class for model
class OysterModel(mesa.Model):
"""A model with some number of agents."""
#define init parameters
def __init__(self, N, width, height):
self.num_agents = N
self.grid = mesa.space.MultiGrid(width, height, True)
self.schedule = mesa.time.RandomActivation(self)
self.running = True
# Create agents
for i in range(self.num_agents):
a = Oyster(i, self)
self.schedule.add(a)
# Add the agent to a random grid cell
x = self.random.randrange(self.grid.width)
y = self.random.randrange(self.grid.height)
self.grid.place_agent(a, (x, y))
#define step
def step(self):
"""Advance the model by one step."""
self.schedule.step()
I feel like the code should be something like "reproduce when the step number is divisible by 203-209" but I'm very new to python so I don't know how to get this. I also don't know how the agent can access the step number?
So this is my current solution!
First, I added a new attribute to the Model class called step_count and I initialized it to be equal to 0. Then under the step function in the Model class, I added 1 to step-count.
In the Agent class created an if statement where reproduction only happens if step_count is divisible by my desired interval.
There was also an issue with next_id which was preventing my reproduction code from working. I would get an error saying AttributeError: 'Model' object has no attribute 'current_id'. I fixed that by setting current_id = 0 when I initialized the model. See code below:
#this tutorial uses the mesa package
import mesa
import random
#set up class for agent
class Oyster(mesa.Agent):
"""An agent with randomly assigned initial energy & age."""
#define init values
def __init__(self, unique_id, model, age = 0):
super().__init__(unique_id, model)
self.energy = random.randint(1,10)
self.age = age
#define what happens at each step
def step(self):
living = True
self.age += 1
self.energy += random.randint(-5, 5)
# Death
if (self.energy < 0) or (self.age > 3650):
self.model.grid.remove_agent(self)
self.model.schedule.remove(self)
living = False
#reproduction
if living & (self.age > 365) and (self.energy > 2) and self.model.step_count%50 == 0 :
for i in range(3):
babyOyster = Oyster(
self.model.next_id(), self.model
)
x = self.random.randrange(self.model.grid.width)
y = self.random.randrange(self.model.grid.height)
self.model.grid.place_agent(babyOyster, (x, y))
self.model.schedule.add(babyOyster)
#set up class for model
class OysterModel(mesa.Model):
"""A model with some number of agents."""
#define init parameters
def __init__(self, N, width, height):
self.num_agents = N
self.grid = mesa.space.MultiGrid(width, height, True)
self.schedule = mesa.time.RandomActivation(self)
self.running = True
self.step_count = 0
self.current_id = 0
# Create agents
for i in range(self.num_agents):
x = self.random.randrange(self.grid.width)
y = self.random.randrange(self.grid.height)
age = random.randint(1, 3649)
oyster = Oyster(self.next_id(), self, age)
self.grid.place_agent(oyster, (x, y))
self.schedule.add(oyster)
#definte step
def step(self):
"""Advance the model by one step."""
self.schedule.step()
self.step_count += 1
I also changed a few things to make sure baby wasn't staying in the same cell as the parent and the baby's age starts at 0. Hopefully this helps someone else, and if anyone finds a better solution let me know!
First of all, my point is making 3 different bullets with different stats and different colors. Shoot them with Space key, selecting them with 1,2 and 3 keys. I'm not putting whole code here but if you need more info feel free to ask.
LaserDict = {
"redLaser": (RED_LASER,4,1), #Bad Velocity #Bad Firerate
"greenLaser": (GREEN_LASER,10,3), #Excellent Velocity #Good Firerate
"blueLaser": (BLUE_LASER,5,2) #Medium Velocity #Good Firerate
}
class Lasers():
def __init__(self,x,y,laserIndex):
self.x = x
self.y = y
self.laserNames = ["redLaser","greenLaser","blueLaser"]
self.laserIndex = laserIndex
self.laserImg, self.laserVel, self.laserCooldown = LaserDict[self.laserNames[self.laserIndex]]
def draw(self,window):
window.blit(self.laserImg,(self.x,self.y))
def move(self,p):
self.y += p
def offScreen(self,height):
return not (self.y>0 and self.y<=height) #if not in screen return True
class Player():
fpsCooldown = 30
def __init__(self,x,y,health,laserIndex):
blah blah...
self.cooldownReady = 0
self.lasers = []
self.laserIndex = laserIndex
def cooldown(self):
if self.cooldownReady >= self.fpsCooldown:
self.cooldownReady = 0
elif self.cooldownReady > 0:
self.cooldownReady += Lasers(self.x,self.y,self.laserIndex).laserCooldown
def shoot(self):
if self.cooldownReady == 0:
laser = Lasers(self.x+int(self.getWidth()/8) ,self.y, self.laserIndex)
self.lasers.append(laser)
self.cooldownReady = 1
def moveLasers(self):
self.cooldown()
for laser in self.lasers:
p = Lasers(self.x,self.y,self.laserIndex).laserVel
laser.move(-p)
if laser.offScreen(height):
self.lasers.remove(laser)
def main():
blah blah...
if keys[pygame.K_SPACE]:
player.shoot()
if keys[pygame.K_1]:
player.laserIndex = 0
if keys[pygame.K_2]:
player.laserIndex = 1
if keys[pygame.K_3]:
player.laserIndex = 2
Code is working fine with one bug. When you change your laser type by
pressing 1,2 or 3 it changes all of your Laser's stats exist in
window. For example if you shoot "Red Lasers" 5 times and if they are
not off the screen yet, and if you change your laser type to "GreenLasers" all of your "Red Lasers" remains red but moving just like
Green ones.
In moveLasers you calculate how much to move each laser with the variable p. This value p is calculated by creating a new Lasers with the current player.laserIndex, meaning that all lasers will move this much. You're better off changing Lasers.move to:
def move(self):
self.y += self.laserVel
rather than re-finding p everytime, which is only the laserVel for the current mode anyway
Create an object for your bullet, with its own stats.
Here, when you modify the bullet stats on the player, it affects all the bullets because you update your bullets inside the Player.
You could also keep a track of every bullet inside the player but it is overwhelming.
In you Bullet Object, just add it a direction, a speed and a color. The update every frame, just check if it reaches the end of the screen to erase it (to not lose memory).
Was this helpful?
Lasers.move() changed to:
def move(self):
self.y -= self.laserVel
def moveEnemyLasers(self,p):
self.y += p
Player.moveLasers() changed to:
def moveLasers(self):
self.cooldown()
for laser in self.lasers:
laser.move()
if laser.offScreen(height):
self.lasers.remove(laser)
OK in my 2d game, I have several 'cell' object instances (sprite objects) that have an empty self.target = [] attribute upon initialization.
They will then find a valid "plant" target object nearby and that will become the cells' "self.target."
The 'cell' will then navigate toward the plant to eventually collide with and eat it, and the plant is then set to ".kill()" and another is respawned at a random new coordinate. There are several plant instances (part of spr_plant_group) and several cells (belonging to spr_cell_group).
Each plant 'lives' for X game ticks (200 I think), and if not eaten by then, is .kill()-ed and a new one spawns at a random coordinate.
The problem is this: If a cell's target plant happens to despawn from age, or is eaten by something, the cell's self.target info is STILL pointing to the old "plant" object's data. This means the cell is chasing a phantom object, still apparently having a valid x and y coordinate.
Question: How do I tell the cell that it's target is dead, gone, invalid? If I can do this, I think it would fix the 'phantom' target object.
Pieces of relevant code:
class Agent(sprite.Sprite):
def __init__(self, sense_range, size, food, maxspeed):
sprite.Sprite.__init__(self)
self.x = randint(0,450)
self.y = randint(0,450)
self.ang = randint(0,359)
self.turn_rate = 2
self.dx = 0
self.dy = 0
self.speed = 1
self.maxspeed = maxspeed
self.food = int(food)
self.max_food = food*1.5
self.target = []
self.sense_range = sense_range
# Snip------This part below is supposed to find a new target, but it only
# works for the first one when the cell spawns, then I can't seem to
# get it to become 'empty' so that the "if self.target == []" can do its
# thing....
def seek_food(self):
if (self.target == []):
#find cell a target within "sense_range" distance (say 200 pixels)
dist = self.sense_range
targ = []
for t in spr_plant_group:
t_dist = abs(self.x - t.x)
if t_dist <= dist:
targ = t
dist = t_dist
self.target = targ
print ("Found target...",dist, self.target)
else:
#already have a target, so move cell toward target
dx = self.target.x - self.x
dy = self.target.y - self.y
rads = atan2(dy,dx)
rads %= 2*pi
degs = degrees(rads)
direction = degs - self.ang
if direction > 0:
self.ang = self.ang + self.turn_rate
elif direction < 0:
self.ang = self.ang - self.turn_rate
# Correct for angle being out of 0-360* range
if self.ang > 360:
self.ang -= 360
elif self.ang < 0:
self.ang += 360
#---This is just a piece of the Plant class for your reference
class Plant (sprite.Sprite):
def __init__(self):
sprite.Sprite.__init__(self)
self.x = randint(0,450)
self.y = randint(0,450)
self.age = 1 + randint(0,50)
For reference, below. Plants age increases until 200 ticks, then they're killed, and a new one respawns...
def update_plants():
for shrub in spr_plant_group:
shrub.age += 1
# Respawn a new plant in a different place
if shrub.age >= 200:
shrub.kill()
plant.append (Plant())
EDIT: Actually, it's a lot simpler. Pygame's Sprite class supports an alive method, so just do this:
def seek_food(self):
if (self.target == [] or not self.target.alive()):
# Find a new target that isn't dead...
else:
# Move to the (alive) target...
You can use some variant of the Observer design pattern. Extend your Plant class with an extra property which keeps track of any Cell instances that are targeting the plant. When a cell targets a plant, it will add itself to the plant's list. Before the plant dies, it will notify all of its cells.
New property in the Plant class:
class Plant (sprite.Sprite):
def __init__(self):
# ...
self.followers = []
# ...
Cells subscribe to the plant's list of followers:
def seek_food(self):
if (self.target == []):
# ...
self.target = targ
self.target.followers.append(self)
# ...
else:
# ...
The Plant class overrides its parent class' kill function so that it notifies its followers before dying:
(Here, the cell class is directly modifying each plant's target, but you could encapsulate the behaviour differently if you want)
def kill(self):
for follower in self.followers:
follower.target = []
self.followers = []
super().kill()
I need to create a class for a car that moves on a horizontal line. The constructor must only have one argument and this is where it is throwing me off. I can only have one argument to initialize the initial position of the bug. It should default to a value of 0 and the initial direction should always be one. But i'm not sure i can do this without 2 arguments in the constructor. I also need two mutator methods for moving and turning the car as well as a accessor method that will display the location of the car.
Example: position 5 direction right: .....>; position 2 direction left: ..<
class Bug:
def __init__(self, iPosition=0):
self.position = iPosition
def move(self):
pos = self.postion
def turn(self):
direction = self.position
def display(self):
if direction < 0:
x = '<'
elif direction > 0:
x = '>'
for i in range(pos):
y = '.' + y
return (y,x)
Your code in the display() function should give you the answer. It has the movement being to the left for negative direction. Hence, a negative value in the constructor's parameter could represent movement to the left.
This might be more like what you need:
class Bug:
def __init__(self, iPosition=0):
self.position = iPosition
self.direction = 1
def move(self, distance=1):
self.position += self.direction * distance
def turn(self):
self.direction = -self.direction
def display(self):
if self.direction < 0:
x = '<'
elif self.direction > 0:
x = '>'
y = ''
for i in range(self.position): # this is not right if position is negative; maybe abs()?
y = '.' + y
return (y,x)
I am trying to make a turn based RPG and I am currently working on the turn system.
To keep it simple for now I am trying to order each character/enemy by their speed and then highest speed goes first. Below is the start of my code for the turn system.
An example of what I want to do is read Order[0] (which will be the fasted character/enemy) and find out which character/enemy that relates to. Speed[0] to Speed[3] gives the speed of my 4 characters and enemySpeed[0] to enemySpeed[4] gives the speed of the 5 enemies.
def Turn():
Turn = [int(Speed[0]), int(Speed[1]), int(Speed[2]), int(Speed[3]), int(enemySpeed[0]), int(enemySpeed[1]),int(enemySpeed[2]), int(enemySpeed[3]), int(enemySpeed[4])]
Order = sorted(Turn, key = int, reverse = True)
Edit: Here is some information regarding player stats.
In main():
name = ['Ben','Ellis','Curt','Jen']
HP = [100,100,100,100]
exp = [0,0,0,0]
lvl = [1,1,1,1]
player1 = [name[0],HP[0],exp[0],lvl[0]]
player2 = [name[1],HP[1],exp[1],lvl[1]]
player3 = [name[2],HP[2],exp[2],lvl[2]]
player4 = [name[3],HP[3],exp[3],lvl[3]]
PLAYERS = [player1, player2, player3, player4]
REGION = 'start'
POS = '[x, y]'
ITEMS = None
SIGEVENTS = False
gameData = [PLAYERS, REGION, POS, ITEMS, SIGEVENTS]
Out of main():
def playerStats():
global Attack, Defense, Speed, MaxHP
Attack = [0,0,0,0]
Defense = [0,0,0,0]
Speed = [0,0,0,0]
MaxHP = [0,0,0,0]
for i in range(0,4):
Attack[i] = lvl[i] * 1
Defense[i] = lvl[i] * 2
Speed[i] = lvl[i] * 3
MaxHP[i] = lvl[i] * 4
return Attack, Defense, Speed, MaxHP
Instead of looking at just the speeds, look at your players and enemies, and devise a key to determine what speed goes with what player.
You didn't share any details on how you define players and speeds; I'll use a number here for now:
players_and_enemies = range(8)
fastest = max(players_and_enemies, key=lambda p: int(Speed[i] if i < 4 else enemySpeed[i - 4]))
It may well be that you can simplify this based on your data structures; if you have objects per player and enemy that have a speed attribute, for example, you can access that attribute in the key function instead.
max() returns the highest value by key; no need to sort all speeds if all you need is the fastest driver.
Martijn Pieters already pointed out the most important issues. Try to use objects. Try to remodel the objects of your game and don't manage their attributes seperately.
I will elaborate a bit on this and maybe I can give you so some ideas.
A Character, be it a NPC or a PC, is one object of a class that could look something like this:
class Character:
def __init__ (self, name, xp = 0, level = 1, hp = 4, speed = 3, atk = 1, def_ = 2):
self.name = name
self.xp = xp
self.level = level
self.hp = self.curHp = hp
self.speed = speed
self.atk = atk
self.def_ = def_
self.inventory = []
Now using this class you can instantiate your PCs and NPCs:
npcs = [Character (name) for name in ['Baldur', 'Lord Never', 'Ashyra'] ]
Adding methods to your class lets you interact with it, for example:
class Character:
[....]
def takeDamage (self, dmg):
self.curHp -= min (0, dmg - self.def_)
def gainXP (self, xp):
self.xp += xp
#check for level up
def takeItem (self, item):
self.inventory.append (item)
def useItem (self, item):
item.activate ()
self.inventory.remove (item)
Or in order to get all your characters ordered by their speed, you can use:
charactersBySpeed = sorted (characters, key = -character.speed)
And so further and so on. The idea is really to use object oriented programming for modelling the reality (even the made-up reality of your game) in your code.