I'm learning python from a beginners book. Below is an extract of code from that book (Python Programming for the absolute beginner, 3rd edition) for a game.
My question is a fairly simple one. The update() methods appear never to be invoked, yet still function. How does this work?
I've pasted the whole block of code, so nothing is missing.
# Pizza Panic
# Player must catch falling pizzas before they hit the ground
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
self.score.right = games.screen.width - 10
pizza.handle_caught()
class Pizza(games.Sprite):
"""
A pizza which falls to the ground.
"""
image = games.load_image("pizza.bmp")
speed = 1
def __init__(self, x, y = 90):
""" Initialize a Pizza object. """
super(Pizza, self).__init__(image = Pizza.image,
x = x, y = y,
dy = Pizza.speed)
def update(self):
""" Check if bottom edge has reached screen bottom. """
if self.bottom > games.screen.height:
self.end_game()
self.destroy()
def handle_caught(self):
""" Destroy self if caught. """
self.destroy()
def end_game(self):
""" End the game. """
end_message = games.Message(value = "Game Over",
size = 90,
color = color.red,
x = games.screen.width/2,
y = games.screen.height/2,
lifetime = 5 * games.screen.fps,
after_death = games.screen.quit)
games.screen.add(end_message)
class Chef(games.Sprite):
"""
A chef which moves left and right, dropping pizzas.
"""
image = games.load_image("chef.bmp")
def __init__(self, y = 55, speed = 2, odds_change = 200):
""" Initialize the Chef object. """
super(Chef, self).__init__(image = Chef.image,
x = games.screen.width / 2,
y = y,
dx = speed)
self.odds_change = odds_change
self.time_til_drop = 0
def update(self):
""" 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()
The update() methods of each objects are called by the game module you are importing at the top:
from livewires import games, color
The GUI that these modules are handling runs in a loop that manages events and callbacks.
If you are curious, you could open load and read these files, and find out how the code works. You will find that each object is, in turn, calling its own update() method.
You are using a framework livewires which you have imported at the top. That framework takes your objects and then invokes methods on them, instead of you calling the methods.
See this for a high level view of the difference between Frameworks and Libraries: Framework vs. Toolkit vs. Library
Related
I'm having an issue with my snakegame on the collision detection side. What I wrote originally to do so was this:
snakecollisionchecklist = len(snake.snake_coord_list)
snakecollisionchecklistset = len(set(snake.snake_coord_list))
if snakecollisionchecklist != snakecollisionchecklistset:
return
The idea being that if any segment position in the snake was equal to another segment position it would abort the program. The problem I found was that the .position() function I was using to find the position of each segment returned a 10 decimal float value, and for whatever reason this wasn't consistent even if the snake was sharing the same place. Converting it to int doesnt work either as that sometimes will make 60 59 etc. if the variance is high/low. To deal with this I put this together, but I feel like this isn't the most efficient way to handle this:
values_to_convert = segments.position()
xvaluestoconvert = round(values_to_convert[0],2)
yvaluestoconvert = round(values_to_convert[1],2)
self.snake_coord_list[segloc] = (xvaluestoconvert, yvaluestoconvert)
This just takes the individual pieces of the position() and forces it to be rounded. I also had a similar issue with trying to make food not spawn inside the snake, which ended up looking like this:
newloc= positionlist[0]
while newloc in positionlist:
randomx = round((random.randint(-7,7) * 20))
randomy = round((random.randint(-7,7) * 20))
newloc = [randomx, randomy]
self.goto(float(randomx),float(randomy))
print(f"{randomx} and {randomy}")
But then I still get overlap.
Is there a better way to do this? If you're curious about full code it's here:
gamesetup.py
import turtle as t
class FullSnake:
def __init__(self) -> None:
self.snake_part_list = []
self.snake_coord_list = [(-40,0),(-60,0), (-80,0)]
self.moment_prior_coord_list = [(-40,0),(-60,0), (-80,0)]
for nums in range(3):
new_segment = t.Turtle("circle")
new_segment.penup()
new_segment.color("white")
self.snake_part_list.append(new_segment)
new_segment.goto(self.snake_coord_list[nums])
self.snake_head = self.snake_part_list[0]
self.headingverification = 0
def add_segment(self,):
"""This adds a segment to the snake. the segment added should be initialized to be add the end of the list."""
new_segment = t.Turtle("circle")
new_segment.penup()
new_segment.color("white")
self.snake_part_list.append(new_segment)
#self.snake_coord_list.append((self.snake_part_list[0].xcor(),self.snake_part_list[0].ycor()))
#self.snake_coord_list.append(new_segment)
# current_final_seg = self.snake_part_list[-2]
current_final_seg_pos = self.moment_prior_coord_list[-1]
#self.move_snake()
new_segment.goto(current_final_seg_pos[0],current_final_seg_pos[1])
self.snake_coord_list.append(current_final_seg_pos)
def right(self):
if self.headingverification != 180:
self.snake_head.setheading(0)
# time.sleep(0.031)
# self.move_snake()
#ime.sleep(0.05)
def up(self):
if self.headingverification != 270:
self.snake_head.setheading(90)
# time.sleep(0.031)
#self.move_snake()
def left(self):
if self.headingverification != 0:
self.snake_head.setheading(180)
# time.sleep(0.031)
# self.move_snake()
def down(self):
if self.headingverification != 90:
self.snake_head.setheading(270)
#time.sleep(0.031)
# self.move_snake()
def move_snake(self):
"""moves snake. snake moves forward 20 units, and prior units get updated"""
self.moment_prior_coord_list = list(self.snake_coord_list)
for seg_num in range(len(self.snake_part_list)-1,0,-1):
new_x = round(self.snake_part_list[seg_num-1].xcor(),2)
new_y = round(self.snake_part_list[seg_num-1].ycor(),2)
self.snake_part_list[seg_num].goto(new_x, new_y)
self.snake_head.forward(20)
#print(self.snake_head.position())
for segments in self.snake_part_list:
segloc = self.snake_part_list.index(segments)
#for some reason segments.position() a varied float, so this just forces it to be samesies
values_to_convert = segments.position()
xvaluestoconvert = round(values_to_convert[0],2)
yvaluestoconvert = round(values_to_convert[1],2)
self.snake_coord_list[segloc] = (xvaluestoconvert, yvaluestoconvert)
print(self.snake_coord_list)
main.py:
import turtle as t
from gamesetup import FullSnake
import time
import food
import score_board as sb
screen = t.Screen()
screen.setup(food.screensize[0],food.screensize[1])
screen.bgcolor("Black")
screen.title("My Snake Game")
screen.tracer(0)
snakefood = food.Food()
snake = FullSnake()
scoreboard = sb.ScoreBoard()
screen.listen()
screen.onkey(snake.up,"Up")
screen.onkey(snake.down,"Down")
screen.onkey(snake.right,"Right")
screen.onkey(snake.left,"Left")
#game_is_on = True
#while game_is_on:
def snakemovefct():
snake.move_snake()
screen.update()
#what happens when you hit food, add to length of snake, increase score and move pellet to place that snake isnt
if snake.snake_head.distance(snakefood) <5:
snake.add_segment()
snakefood.refresh(snake.snake_coord_list)
scoreboard.score_event()
screen.update()
time.sleep(0.1)
#set gameover if you hit boundary
if snake.snake_head.xcor() > 150 or snake.snake_head.xcor() < -150 or snake.snake_head.ycor() > 150 or snake.snake_head.ycor() < -150:
scoreboard.game_over()
return
#check collision
snakecollisionchecklist = len(snake.snake_coord_list)
snakecollisionchecklistset = len(set(snake.snake_coord_list))
if snakecollisionchecklist != snakecollisionchecklistset:
scoreboard.game_over()
return
#this makes sure you cant press up and left when moving right to go left
snake.headingverification = snake.snake_head.heading()
#keep snake moving in loop, if no recursion it only moves once
screen.ontimer(snakemovefct,150)
screen.update()
screen.ontimer(snakemovefct,150)
screen.mainloop()
food.py
from turtle import Turtle
import random
screensize = (340, 340)
class Food(Turtle):
def __init__(self) -> None:
super().__init__()
self.shape("square")
self.penup()
#self.shapesize(stretch_len=0.5, stretch_wid=0.5)
self.color("red")
self.speed("fastest")
self.refresh([(20,0),(0,0), (-20,0)])
def refresh(self, positionlist):
newloc= positionlist[0]
while newloc in positionlist:
randomx = "{:.2f}".format(random.randint(-7,7) * 20)
randomy = "{:.2f}".format(random.randint(-7,7) * 20)
newloc = [randomx, randomy]
self.goto(float(randomx),float(randomy))
print(f"{randomx} and {randomy}")
scoreboard.py
import turtle as t
class ScoreBoard(t.Turtle):
def __init__(self) -> None:
super().__init__()
self.hideturtle()
self.penup()
self.pencolor("white")
self.speed("fastest")
self.score = 0
self.goto(0,120)
self.write(f"Current score: {self.score}", False, align="center")
def score_event(self):
self.score +=1
self.clear()
self.write(f"Current score: {self.score}", False, align="center")
def game_over(self):
self.goto(0,0)
self.write("GAME OVER", False, align="center")
I have repeatedly encountered the problem of slowing down animation when drawing various objects in Python. The problem is especially acute when the number of objects being drawn increases. I would not say that there are too many objects: > = 200 (r=10) circles are already a slide show.
I think it's not about the library I use (pyglet). Before that I used the p5py analog p5.js the brakes were even stronger (in truth, the creators of p5py know that it is much slower than its counterparts (Issues from Github)). I also tried Tkinter - I thought that a clean canvas would solve my problems. But that didn't happen. And now I run the code in Pyglet - I get a slideshow.
I also noticed that the computer starts to take off on the jet thrust of coolers when the animation is slowing down. At the same time, the CPU load of the Python process never exceeds 20% (very strange). Computer not quite a calculator: Intel i5-8300H 4 cores (8 virtual) 2.30 GHz.
What could be the problem? An animation of 1000 points should not turn into a slideshow, even if it is Python.
PS. I also think that the problem is not in my code. Exactly the same (not about the code below) code on p5.js gives smooth animation in the browser (but the browser uses a video card). But just in case, I put my last count on Pyglet - this is not the top of optimization, but it should not work so slowly:
sketch.py
import pyglet
import random
from firework import Firework
window = pyglet.window.Window(400, 400)
main_batch = pyglet.graphics.Batch()
fireworks = [Firework(main_batch)]
#window.event
def on_draw():
window.clear()
main_batch.draw()
def update(dt):
if random.random() < 0.03:
fireworks.append(Firework(main_batch))
for f in fireworks:
f.update()
if f.done():
fireworks.remove(f)
if __name__ == '__main__':
pyglet.clock.schedule_interval(update, 1 / 120.0)
pyglet.app.run()
particle.py
import p5
import pyglet
class Particle(pyglet.shapes.Circle):
def __init__(self, x, y, color, firework, batch=None):
radius = 2
if firework:
radius = 4
super().__init__(x, y, radius, radius * 5, color, batch)
self.color = color
self.firework = firework
self.opacity = 255
self._done = False
self.acc = p5.Vector(0, 0)
if self.firework:
self.vel = p5.Vector(0, p5.random_uniform(8, 12))
else:
self.vel = p5.Vector.random_2D() * p5.random_uniform(-10, -2)
#property
def batch(self):
return self._batch
def update(self):
self.acc += p5.Vector(0, -0.2)
if not self.firework:
self.vel *= 0.9
if self.opacity > 4:
self.opacity -= 4
else:
self._done = True
self.opacity = 0
self.vel += self.acc
pos = p5.Vector(self.x, self.y) + self.vel
self.x = pos.x
self.y = pos.y
self.acc *= 0
def done(self):
return self._done
firework.py
import p5
import random
from particle import Particle
class Firework:
def __init__(self, batch):
self.color = tuple(random.randint(0, 255) for _ in range(3))
self.firework = Particle(p5.random_uniform(400), 0, self.color, True, batch=batch)
self.bach = batch
self._exploded = False
self.particles = []
def done(self):
if self._exploded and len(self.particles) == 0:
return True
else:
return False
def explode(self):
for _ in range(70):
p = Particle(self.firework.x, self.firework.y, self.color, False, batch=self.firework.batch)
self.particles.append(p)
def update(self):
if not self._exploded:
self.firework.update()
if self.firework.vel.y < 0:
self._exploded = True
self.explode()
self.firework.delete()
for p in self.particles:
p.update()
if p.done():
self.particles.remove(p)
p.delete()
I am writing a game where a rock falls down and you use the mouse to have your chef not get crushed. When the rock falls off the screen, two additional rocks spawn and fall. This continues until your character gets smooshed.
However I got the error:
TypeError: unbound method additonal_drop() must be called with Dropper instance as first argument (got nothing instead)
I'm not sure what I should be putting in the (). Can someone explain this error to me?
In addition, how do I get the Dropper sprite to not be visible?
Here's my code:
from livewires import games, color
import random
games.init(screen_width = 640, screen_height = 480, fps = 50)
class Chef(games.Sprite):
image = games.load_image("chef.bmp")
def __init__(self):
super(Chef, self).__init__(image = Chef.image,
x = games.mouse.x,
bottom = games.screen.height)
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()
def check(self):
""" Check if hit by rocks. """
for rock in self.overlapping_sprites:
rock.end_game()
class Rock(games.Sprite):
"""
A rock which falls to the ground.
"""
image = games.load_image("rock.bmp")
speed = 1
def __init__(self, x = 320, y = 90):
""" Initialize a rock object. """
super(Rock, self).__init__(image = Rock.image,
x = x, y = y,
dy = Rock.speed)
def end_game(self):
""" End the game. """
end_message = games.Message(value = "Game Over",
size = 90,
color = color.red,
x = games.screen.width/2,
y = games.screen.height/2,
lifetime = 2 * games.screen.fps,
after_death = games.screen.quit)
games.screen.add(end_message)
def update(self):
""" Check if bottom edge has reached screen bottom. """
if self.bottom > games.screen.height:
self.destroy()
Dropper.additonal_drop()
class Dropper(games.Sprite):
"""
A invisible sprite that drops the rocks.
"""
image = games.load_image("rock.bmp")
def __init__(self, y = 55, speed = 2, odds_change = 200):
""" Initialize the dropper object. """
super(Dropper, self).__init__(image = Dropper.image,
x = games.screen.width / 2, y = y,
dx = speed)
self.odds_change = odds_change
self.time_til_drop = 0
def update(self):
""" 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
def additonal_drop(self):
new_rock = Rock(x = self.x)
games.screen.add(new_rock)
new_rock = Rock(x = self.x)
games.screen.add(new_rock)
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_rock = Rock()
games.screen.add(the_rock)
the_dropper = Dropper()
games.screen.add(the_dropper)
games.mouse.is_visible = False
games.screen.event_grab = True
games.screen.mainloop()
# start it up!
main()
Try defining the Rock class with an additional parameter dropper:
def __init__(self, x = 320, y = 90, dropper=Dropper()):
dropper will be the Dropper instance. Then create Rock instances from inside Dropper as follows:
Rock(x=self.x, dropper=self)
This will pass the Dropper instance itself to each Rock instance that the Dropper instance creates. In Rock's __init__(), save a reference to the Dropper instance:
self.dropper = dropper
Call additional_drop() with:
self.dropper.additional_drop()
It's still an incomplete program, but for some reason the value of the textbox doesn't increase when it should... Why is this??
When the Pizza sprite overlaps with the Pan sprite, the score in the textbox is supposed to increase in value by 10. Why does this not occur?
Thanks!
'''
Created on Jul 1, 2011
#author: ******* Louis
'''
#Watch me do.
from livewires import games, color
import random
games.init (screen_width = 640, screen_height = 480, fps = 50)
#Pizza Class
class Pizza (games.Sprite):
pizzaimage = games.load_image ("pizza.bmp", transparent = True)
def __init__(self, x = random.randrange(640), y = 90, dy = 4):
super (Pizza, self).__init__(x = x,
y = y,
image = Pizza.pizzaimage,
dy = dy)
def handle_caught (self):
self.destroy()
class Pan (games.Sprite):
panimage = games.load_image ("pan.bmp", transparent = True)
def __init__ (self, x = games.mouse.x, y = games.mouse.y):
super (Pan, self).__init__(x = x,
y = y,
image = Pan.panimage)
self.score = 0
self.textbox = games.Text (value = str(self.score),
size = 20,
color = color.black,
x = 550,
y = 50)
games.screen.add(self.textbox)
def update (self): #WWWWOW There is actually an *update* method
self.x = games.mouse.x
self.y = games.mouse.y
if self.left < 0:
self.left = 0
if self.right >640:
self.right = 640
if self.top < 0:
self.top = 0
if self.bottom > 480:
self.bottom = 480
self.check_collision()
def check_collision (self):
for Pizza in self.overlapping_sprites:
self.score = self.score + 10
Pizza.handle_caught()
#main
def main():
wallbackground = games.load_image ("wall.jpg", transparent = False)
games.screen.background = wallbackground
games.screen.add(Pizza())
games.screen.add(Pan())
games.mouse.is_visible = False
games.screen.event_grab = True
games.screen.mainloop()
main()
The textbox takes a value that is a string. When you create the textbox, you create a string from the current value of score, and set the text to that string. No lasting connection between score and textbox is made.
The textbox probably has a method available to update its text; call that method with the value str(self.score) after you increment the score.
I am attempting to write a game using livewires and pygame where I have a chef (only image I had, haha), avoid rocks that are falling from the sky. The rocks are supposed to fall in random places. I want it to be that 1 rock falls to begin with, then every time you successfully dodge a rock, 2 more rocks fall, until you lose. What I have so far is the chef and 1 rock falling. However, for some reason if the sprites collide, or if the rock touches the bottom of the screen, the game ends, without giving a game over message like I told it to. I'm very confused, and can't see what I did. I know that I did not code it correctly for the 2 rocks part, but I can't even get it to slightly run. Help! Here's what I have now:
from livewires import games, color
import random
games.init(screen_width = 640, screen_height = 480, fps = 50)
class Chef(games.Sprite):
image = games.load_image("chef.bmp")
def __init__(self):
super(Chef, self).__init__(image = Chef.image,
x = games.mouse.x,
bottom = games.screen.height)
def update(self):
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):
for pizza in self.overlapping_sprites:
if not self.bottom>games.screen.height:
self.end_game()
class Rock(games.Sprite):
def update(self):
if self.bottom > games.screen.height:
new_rock=Rock(x=random.randrange(games.screen.width),
y=10,
dy=1)
games.screen.add(new_rock)
def end_game(self):
end_message = games.Message(value = "Game Over",
size = 90,
color = color.red,
x = games.screen.width/2,
y = games.screen.height/2,
lifetime = 5 * games.screen.fps,
after_death = games.screen.quit)
games.screen.add(end_message)
def main():
wall_image = games.load_image("wall.jpg", transparent = False)
games.screen.background = wall_image
the_chef = Chef()
games.screen.add(the_chef)
rock_image=games.load_image("rock.bmp")
the_rock=Rock(image=rock_image,
x=random.randrange(games.screen.width),
y=10,
dy=1)
games.screen.add(the_rock)
games.mouse.is_visible = False
games.screen.event_grab = True
games.screen.mainloop()
main()
You have declared end_game method in Rock class but you are calling it from check_catch method of Chef class.