So there must be something wrong with the code which detects wether it should be alive or not in Cell.update(), but the glider i hardcoded in is not working as intended. The first and second generations work as intended, but on the third it dies out. Anyone know what the problem is? I know the code is a bit messy, but I'm quite new to python so it is expected. Thanks in advance!
Here is the code:
import pygame
"""
rules:
1. Any live cell with two or three live neighbours survives.
2. Any dead cell with three live neighbours becomes a live cell.
3. All other live cells die in the next generation. Similarly, all other dead cells stay dead.
number cells on screen = 32x18
cell size = 30x30
"""
# variables
WIDTH = 960
HEIGHT = 540
TITLE = "Conway's Game Of Life"
GRID_COLOUR = (200, 200, 200)
BG_COLOUR = (255, 255, 255)
grid = [[False] * 32] * 18
cells = []
live_queue = []
die_queue = []
# window
wn = pygame.display.set_mode((WIDTH, HEIGHT), vsync=1)
pygame.display.set_caption(TITLE)
# classes
class Cell:
def __init__(self, x, y, alive, index_x, index_y):
self.x = x
self.y = y
self.alive = alive
self.indexX = index_x
self.indexY = index_y
def die(self):
self.alive = False
def live(self):
self.alive = True
def get_index(self):
return self.indexX, self.indexY
def update(self):
grid_temp = grid[self.indexY]
grid_temp.pop(self.indexY)
grid_temp.insert(self.indexY, self.alive)
grid.pop(self.indexY)
grid.insert(self.indexX, grid_temp)
adjacent_alive = 0
for i in cells:
if i.x == self.x - 30 and i.y == self.y and i.alive:
adjacent_alive += 1
elif i.x == self.x + 30 and i.y == self.y and i.alive:
adjacent_alive += 1
elif i.x == self.x and i.y == self.y - 30 and i.alive:
adjacent_alive += 1
elif i.x == self.x and i.y == self.y + 30 and i.alive:
adjacent_alive += 1
elif i.x == self.x - 30 and i.y == self.y - 30 and i.alive:
adjacent_alive += 1
elif i.x == self.x - 30 and i.y == self.y + 30 and i.alive:
adjacent_alive += 1
elif i.x == self.x + 30 and i.y == self.y - 30 and i.alive:
adjacent_alive += 1
elif i.x == self.x + 30 and i.y == self.y + 30 and i.alive:
adjacent_alive += 1
if self.alive:
if adjacent_alive < 2:
return False
elif adjacent_alive > 3:
return False
else:
return True
if not self.alive:
if adjacent_alive == 3:
return True
else:
return False
def render(self):
if self.alive:
pygame.draw.rect(wn, (0, 0, 0), (self.x, self.y, 30, 30))
# functions
def render_grid():
for y in range(0, HEIGHT, 30):
pygame.draw.line(wn, GRID_COLOUR, (0, y), (WIDTH, y))
for x in range(0, WIDTH, 30):
pygame.draw.line(wn, GRID_COLOUR, (x, 0), (x, HEIGHT))
def parse_to_x_y(x, y):
return x * 30, y * 30
def parse_to_index(x, y):
return int(x / 30), int(y / 30)
indexX = 0
indexY = 0
x_pos = 0
y_pos = 0
for y_ in range(18):
for x_ in range(32):
cells.append(Cell(x_pos, y_pos, False, indexX, indexY))
indexX += 1
x_pos += 30
y_pos += 30
x_pos = 0
indexY += 1
cells[2].live()
cells[35].live()
cells[65].live()
cells[66].live()
cells[67].live()
# main loop
fps = 1
clock = pygame.time.Clock()
while True:
# start_time = time.time()
wn.fill(BG_COLOUR)
for item in cells:
item.render()
render_grid()
# events loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
for item in cells:
if item.update():
live_queue.append(item)
else:
die_queue.append(item)
for i in live_queue:
i.live()
for i in die_queue:
i.die()
pygame.display.update()
clock.tick(fps)
# end_time = time.time()
# print(round(1 / (end_time - start_time)), "fps")
The problem is that you don't reset your queues in the main loop.
So add this before adding to the queues:
live_queue = [] # <----
die_queue = [] # <----
for item in cells:
if item.update():
live_queue.append(item)
else:
die_queue.append(item)
Some other remarks
You never use grid or grid_temp in a useful way. Even the operations you make on them are strange. Any way, you can just remove all references to them.
You never use the indexX or indexY attributes, nor the method around it, nor the corresponding arguments to the constructor. All that can go.
You should avoid scanning all the cells just to find the (up to) 8 neighbors of one cell: this has a bad impact on performance.
I agree with commenter Rabbid76, in that you need to update the entire grid at once, not cell by cell. This is usually done by using two separate grids, an "previous state" grid and a "new state grid". Loop through each position in the "new state" grid and calculate its number of live neighbors using the "previous state" grid. After the entire "new state" grid is calculated, you can copy to "new state" grid to the "old state" grid.
Another fatal flaw in your algorithm is grid = [[False] * 32] * 18. This will not work as expected in Python. With this code, each row is a reference to the same array. For instance, the expression grid[0] is grid[1] will evaluate to True. If you set a certain cell in the grid to true, the entire column will be set to true. You can fix this via the code:
grid=[]
for r in range(18):
row=[]
for c in range(32):
row.append(False)
grid.append(row)
Though it isn't directly related to the bug, I suggest a bit of a redesign of your algorithm. It is not really needed to encapsulate each cell in a class; doing so creates a redundancy between the grid and list of cells. This also leads you to identify cells by their pixel position (hence the i.x == self.x - 30 clause), which can easily lead to bugs. I suggest checking adjacent indices in the grid variable instead. Try looking into https://www.geeksforgeeks.org/conways-game-life-python-implementation/ for some inspiration.
I decided to add the attack images into the walking ones to see what happened and I got them to load up.
I have been attempting to break it down and piece it back together to try and find what I have been doing wrong and it may be an issue of my experience.
I have butchered my code trying to figure this out after someone gave me an explanation about my classes. I thought maybe I should try and set it up as global, and then I realize there's something I'm not doing right. I don't want someone to just write the code for me I should mention. But an explanation for why I am screwing up would be nice.
Some tutorials are helping me but not as much as when someone tells me where specifically where I went wrong. I think I made more progress there.
##a simple punch without the whole animation atm
class attacks(object):
attack = pygame.image.load("atk3.png")
def __init__(self, x, y, width, height, attack):
self.x = x
self.y = y
self.width = width
self.height = height
self.attackCount = 0
def draw(self, win):
## is it my redraw method?
if (self.attack):
if self.attackCount + 1 >= 9:
win.blit(attack, self.attackCount//3(self.x, self.y))
self.attackCount += 1
So with the class set up here im sure that something is wrong, ive been diving through forums and I know something is not right. I dont know what though.
def redrawGameWindow():
global walkCount
global attackCount
win.blit(bg, (0, 0))
if walkCount +1 >= 27:
walkCount = 0
bob.draw(win)
jerk.draw(win)
for attack in attacks:
attack.draw(win)
pygame.display.update()
#While true
#This calls the classes created
bob = player(100, 200, 128, 128)
jerk = enemy(500, 200, 164, 100, 600)
attacks = []
run = True
while run:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
#attempting to make this into a bool that changes the value
if not(bob.attacking):
if keys[pygame.K_SPACE]:
bob.attacking = True
bob.standing = False
bob.left = False
bob.right = False
if keys[pygame.K_LEFT] and bob.x > bob.vel:
bob.x -= bob.vel
bob.left = True
bob.right = False
bob.standing = False
elif keys[pygame.K_RIGHT] and bob.x < 800 - bob.width - bob.vel:
bob.x += bob.vel
bob.right = True
bob.left = False
bob.standing = False
else:
bob.standing = True
bob.walkCount = 0
if not(bob.isJump):
if keys[pygame.K_UP]:
bob.isJump = True
bob.right = False
bob.left = False
bob.walkCount = 0
else:
if bob.jumpCount >= -10:
neg = 1
if bob.jumpCount < 0:
neg = -1
bob.y -= (bob.jumpCount ** 2) * 0.5 * neg
bob.jumpCount -= 1
else:
bob.isJump = False
bob.jumpCount = 10
redrawGameWindow()
pygame.quit()
Im almost sure my True loop has nothing to do with it now since im able to run walk and jump. And when I press space it loads a frame thats not an attack one so i know for sure something is happening when I hit space
one of the errors I have found is that you are telling pygame to load multiple images however, you have only loaded one
class attacks(object):
attack = pygame.image.load("atk3.png")#< ----- you are only loading one
def __init__(self, x, y, width, height, attack):
#[...]
and you are telling it to go in a cycle to blit different images in a group
def draw(self, win):
## is it my redraw method?
if (self.attack):
if self.attackCount + 1 >= 9:
win.blit(attack, self.attackCount//3(self.x, self.y))# you only have one image yet you are trying to blit multiple
self.attackCount += 1
another error is your attack varible. to know when you luanch an attack you do it like this
class attacks(object):
attack = pygame.image.load("atk3.png")
def __init__(self, x, y, width, height,#attack <----- this attack shoudnt be here):
self.x = x
self.y = y
self.width = width
self.height = height
self.attackCount = 0
self.punchisluacnhed = false
def draw(self, win):
if (self.punchisluacnhed):
if self.attackCount + 1 >= 9:
win.blit(self.attack, self.attackCount//3(self.x, self.y))
self.attackCount += 1
#B.T.W the attack image in the draw function should have self before it since you called it in a class not in the initilization i.e import pygame etc
If you want the character to punch when a button is pressed you would put it in a function like this
if keys[pygame.K_SPACE]:
bob.punchisluacnhed = True
bob.standing = False
bob.left = False
bob.right = False
to tell it to stop you put an else stateent an set every thing to false except from the standing value
I'm trying to write a game in pygame, involving a moving object with a "turret" that swivels to follow a mouse. As of now, I'm mostly trying to expand upon examples, so the code's not entirely mine (credit to Sean J. McKiernan for his sample programs); however, this portion is. Below is my code; I use the center of rect (the "base" shape and the point around which the "turret" swivels) as the base point, and the position of the mouse as the other point. By subtracting the mouse's displacement from the displacement of the "center," I effectively get a vector between the two points and find the angle between that vector and the x-axis with atan2. Below is the code I use to do that:
def get_angle(self, mouse):
x_off = (mouse[0]-self.rect.centerx)
y_off = (mouse[1]-self.rect.centery)
self.angle = math.degrees(math.atan2(-y_off, x_off) % 2*math.pi)
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.hand_rect.center)
According to multiple tutorials I've reviewed, this SHOULD be the correct code; however, I discovered later that those tutorials (and, in fact, this tutorial) were all for Python 2.7, while I am trying to write in Python 3.6. I don't think that should make a difference in this scenario, though. As it stands, the view appears to depend entirely upon the "character's" position on the screen. If the "character" is in one corner, the reaction of the "turret" is different than the reaction if the "character" is in the middle of the screen. However, this shouldn't matter; the position of the "character" relative to the mouse is the exact same no matter where on the screen they are. Any ideas, or do I need to supply more code?
Edit: Apparently, more code is required. Rather than attempt to extricate only the entirely necessary parts, I've provided the entire code sample, so everyone can run it. As a side note, the "Bolt" things (intended to fire simple yellow blocks) don't work either, but I'm just trying to get the arm working before I start in on debugging that.
Edit the second: I have discovered that the "Bolt" system works within a certain distance of the origin (0,0 in the window coordinate system), and that the arm also works within a much lesser distance. I added the line Block(pg.Color("chocolate"), (0,0,100,100)) under the "walls" grouping as a decision point, and the block was positioned in the top left corner. I've corrected Bolt by changing screen_rect to viewport in the control loop; however, I don't know why the "arm" swinging is dependent on adjacency to the origin. The positions of the mouse and "character" SHOULD be absolute. Am I missing something?
"""
Basic moving platforms using only rectangle collision.
-Written by Sean J. McKiernan 'Mekire'
Edited for a test of "arms"
"""
import os
import sys
import math
import pygame as pg
CAPTION = "Moving Platforms"
SCREEN_SIZE = (700,700)
BACKGROUND_COLOR = (50, 50, 50)
COLOR_KEY = (255, 255, 255)
class _Physics(object):
"""A simplified physics class. Psuedo-gravity is often good enough."""
def __init__(self):
"""You can experiment with different gravity here."""
self.x_vel = self.y_vel = 0
self.grav = 0.4
self.fall = False
def physics_update(self):
"""If the player is falling, add gravity to the current y velocity."""
if self.fall:
self.y_vel += self.grav
else:
self.y_vel = 0
class Player(_Physics, object):
def __init__(self,location,speed):
_Physics.__init__(self)
HAND = pg.image.load("playertst2.png").convert()
HAND.set_colorkey(COLOR_KEY)
self.image = pg.image.load('playertst.png').convert()
self.rect = self.image.get_rect(topleft=location)
self.speed = speed
self.jump_power = -9.0
self.jump_cut_magnitude = -3.0
self.on_moving = False
self.collide_below = False
self.original_hand = HAND
self.fake_hand = self.original_hand.copy()
self.hand = self.original_hand.copy()
self.hand_rect = self.hand.get_rect(topleft=location)
self.angle = self.get_angle(pg.mouse.get_pos())
def check_keys(self, keys):
"""Find the player's self.x_vel based on currently held keys."""
self.x_vel = 0
if keys[pg.K_LEFT] or keys[pg.K_a]:
self.x_vel -= self.speed
if keys[pg.K_RIGHT] or keys[pg.K_d]:
self.x_vel += self.speed
def get_position(self, obstacles):
"""Calculate the player's position this frame, including collisions."""
if not self.fall:
self.check_falling(obstacles)
else:
self.fall = self.check_collisions((0,self.y_vel), 1, obstacles)
if self.x_vel:
self.check_collisions((self.x_vel,0), 0, obstacles)
def check_falling(self, obstacles):
"""If player is not contacting the ground, enter fall state."""
if not self.collide_below:
self.fall = True
self.on_moving = False
def check_moving(self,obstacles):
"""
Check if the player is standing on a moving platform.
If the player is in contact with multiple platforms, the prevously
detected platform will take presidence.
"""
if not self.fall:
now_moving = self.on_moving
any_moving, any_non_moving = [], []
for collide in self.collide_below:
if collide.type == "moving":
self.on_moving = collide
any_moving.append(collide)
else:
any_non_moving.append(collide)
if not any_moving:
self.on_moving = False
elif any_non_moving or now_moving in any_moving:
self.on_moving = now_moving
def check_collisions(self, offset, index, obstacles):
"""
This function checks if a collision would occur after moving offset
pixels. If a collision is detected, the position is decremented by one
pixel and retested. This continues until we find exactly how far we can
safely move, or we decide we can't move.
"""
unaltered = True
self.rect[index] += offset[index]
self.hand_rect[index] += offset[index]
while pg.sprite.spritecollideany(self, obstacles):
self.rect[index] += (1 if offset[index]<0 else -1)
self.hand_rect[index] += (1 if offset[index]<0 else -1)
unaltered = False
return unaltered
def check_above(self, obstacles):
"""When jumping, don't enter fall state if there is no room to jump."""
self.rect.move_ip(0, -1)
collide = pg.sprite.spritecollideany(self, obstacles)
self.rect.move_ip(0, 1)
return collide
def check_below(self, obstacles):
"""Check to see if the player is contacting the ground."""
self.rect.move_ip((0,1))
collide = pg.sprite.spritecollide(self, obstacles, False)
self.rect.move_ip((0,-1))
return collide
def jump(self, obstacles):
"""Called when the user presses the jump button."""
if not self.fall and not self.check_above(obstacles):
self.y_vel = self.jump_power
self.fall = True
self.on_moving = False
def jump_cut(self):
"""Called if player releases the jump key before maximum height."""
if self.fall:
if self.y_vel < self.jump_cut_magnitude:
self.y_vel = self.jump_cut_magnitude
def get_angle(self, mouse):
x_off = (mouse[0]-self.rect.centerx)
y_off = (mouse[1]-self.rect.centery)
self.angle = math.degrees(math.atan2(-y_off, x_off) % (2*math.pi))
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.hand_rect.center)
"""
offset = (mouse[1]-self.hand_rect.centery, mouse[0]-self.hand_rect.centerx)
self.angle = math.atan2(-offset[0], offset[1]) % (2 * math.pi)
self.angle = math.degrees(self.angle)
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.rect.center)
self.angle = 135-math.degrees(math.atan2(*offset))
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(topleft=self.rect.topleft)
"""
def pre_update(self, obstacles):
"""Ran before platforms are updated."""
self.collide_below = self.check_below(obstacles)
self.check_moving(obstacles)
def update(self, obstacles, keys):
"""Everything we need to stay updated; ran after platforms update."""
self.check_keys(keys)
self.get_position(obstacles)
self.physics_update()
def get_event(self, event, bolts):
if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
bolts.add(Bolt(self.rect.center))
elif event.type == pg.MOUSEMOTION:
self.get_angle(event.pos)
def draw(self, surface):
"""Blit the player to the target surface."""
surface.blit(self.image, self.rect)
surface.blit(self.hand, self.hand_rect)
class Bolt(pg.sprite.Sprite):
def __init__(self, location):
pg.sprite.Sprite.__init__(self)
"""self.original_bolt = pg.image.load('bolt.png')"""
"""self.angle = -math.radians(angle-135)"""
"""self.image = pg.transform.rotate(self.original_bolt, angle)"""
"""self.image = self.original_bolt"""
self.image=pg.Surface((5,10)).convert()
self.image.fill(pg.Color("yellow"))
self.rect = self.image.get_rect(center=location)
self.move = [self.rect.x, self.rect.y]
self.speed_magnitude = 5
"""self.speed = (self.speed_magnitude*math.cos(self.angle), self.speed_magnitude*math.sin(self.angle))"""
"""self.speed = (5,0)"""
self.done = False
def update(self, screen_rect, obstacles):
self.move[0] += self.speed_magnitude
"""self.move[1] += self.speed[1]"""
self.rect.topleft = self.move
self.remove(screen_rect, obstacles)
def remove(self, screen_rect, obstacles):
if not self.rect.colliderect(screen_rect):
self.kill()
class Block(pg.sprite.Sprite):
"""A class representing solid obstacles."""
def __init__(self, color, rect):
"""The color is an (r,g,b) tuple; rect is a rect-style argument."""
pg.sprite.Sprite.__init__(self)
self.rect = pg.Rect(rect)
self.image = pg.Surface(self.rect.size).convert()
self.image.fill(color)
self.type = "normal"
class MovingBlock(Block):
"""A class to represent horizontally and vertically moving blocks."""
def __init__(self, color, rect, end, axis, delay=500, speed=2, start=None):
"""
The moving block will travel in the direction of axis (0 or 1)
between rect.topleft and end. The delay argument is the amount of time
(in miliseconds) to pause when reaching an endpoint; speed is the
platforms speed in pixels/frame; if specified start is the place
within the blocks path to start (defaulting to rect.topleft).
"""
Block.__init__(self, color, rect)
self.start = self.rect[axis]
if start:
self.rect[axis] = start
self.axis = axis
self.end = end
self.timer = 0.0
self.delay = delay
self.speed = speed
self.waiting = False
self.type = "moving"
def update(self, player, obstacles):
"""Update position. This should be done before moving any actors."""
obstacles = obstacles.copy()
obstacles.remove(self)
now = pg.time.get_ticks()
if not self.waiting:
speed = self.speed
start_passed = self.start >= self.rect[self.axis]+speed
end_passed = self.end <= self.rect[self.axis]+speed
if start_passed or end_passed:
if start_passed:
speed = self.start-self.rect[self.axis]
else:
speed = self.end-self.rect[self.axis]
self.change_direction(now)
self.rect[self.axis] += speed
self.move_player(now, player, obstacles, speed)
elif now-self.timer > self.delay:
self.waiting = False
def move_player(self, now, player, obstacles, speed):
"""
Moves the player both when on top of, or bumped by the platform.
Collision checks are in place to prevent the block pushing the player
through a wall.
"""
if player.on_moving is self or pg.sprite.collide_rect(self,player):
axis = self.axis
offset = (speed, speed)
player.check_collisions(offset, axis, obstacles)
if pg.sprite.collide_rect(self, player):
if self.speed > 0:
self.rect[axis] = player.rect[axis]-self.rect.size[axis]
else:
self.rect[axis] = player.rect[axis]+player.rect.size[axis]
self.change_direction(now)
def change_direction(self, now):
"""Called when the platform reaches an endpoint or has no more room."""
self.waiting = True
self.timer = now
self.speed *= -1
"""class Spell(pg.sprite.Sprite):
def __init__(self, location, angle)"""
class Control(object):
"""Class for managing event loop and game states."""
def __init__(self):
"""Initalize the display and prepare game objects."""
self.screen = pg.display.get_surface()
self.screen_rect = self.screen.get_rect()
self.clock = pg.time.Clock()
self.fps = 60.0
self.keys = pg.key.get_pressed()
self.done = False
self.player = Player((50,875), 4)
self.viewport = self.screen.get_rect()
self.level = pg.Surface((1000,1000)).convert()
self.level_rect = self.level.get_rect()
self.win_text,self.win_rect = self.make_text()
self.obstacles = self.make_obstacles()
self.bolts = pg.sprite.Group()
def make_text(self):
"""Renders a text object. Text is only rendered once."""
font = pg.font.Font(None, 100)
message = "You win. Celebrate."
text = font.render(message, True, (100,100,175))
rect = text.get_rect(centerx=self.level_rect.centerx, y=100)
return text, rect
def make_obstacles(self):
"""Adds some arbitrarily placed obstacles to a sprite.Group."""
walls = [Block(pg.Color("chocolate"), (0,980,1000,20)),
Block(pg.Color("chocolate"), (0,0,20,1000)),
Block(pg.Color("chocolate"), (980,0,20,1000))]
static = [Block(pg.Color("darkgreen"), (250,780,200,100)),
Block(pg.Color("darkgreen"), (600,880,200,100)),
Block(pg.Color("darkgreen"), (20,360,880,40)),
Block(pg.Color("darkgreen"), (950,400,30,20)),
Block(pg.Color("darkgreen"), (20,630,50,20)),
Block(pg.Color("darkgreen"), (80,530,50,20)),
Block(pg.Color("darkgreen"), (130,470,200,215)),
Block(pg.Color("darkgreen"), (20,760,30,20)),
Block(pg.Color("darkgreen"), (400,740,30,40))]
moving = [MovingBlock(pg.Color("olivedrab"), (20,740,75,20), 325, 0),
MovingBlock(pg.Color("olivedrab"), (600,500,100,20), 880, 0),
MovingBlock(pg.Color("olivedrab"),
(420,430,100,20), 550, 1, speed=3, delay=200),
MovingBlock(pg.Color("olivedrab"),
(450,700,50,20), 930, 1, start=930),
MovingBlock(pg.Color("olivedrab"),
(500,700,50,20), 730, 0, start=730),
MovingBlock(pg.Color("olivedrab"),
(780,700,50,20), 895, 0, speed=-1)]
return pg.sprite.Group(walls, static, moving)
def update_viewport(self):
"""
The viewport will stay centered on the player unless the player
approaches the edge of the map.
"""
self.viewport.center = self.player.rect.center
self.viewport.clamp_ip(self.level_rect)
def event_loop(self):
"""We can always quit, and the player can sometimes jump."""
for event in pg.event.get():
if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]:
self.done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.player.jump(self.obstacles)
elif event.type == pg.KEYUP:
if event.key == pg.K_SPACE:
self.player.jump_cut()
elif event.type == pg.MOUSEMOTION or event.type == pg.MOUSEBUTTONDOWN:
self.player.get_event(event, self.bolts)
def update(self):
"""Update the player, obstacles, and current viewport."""
self.keys = pg.key.get_pressed()
self.player.pre_update(self.obstacles)
self.obstacles.update(self.player, self.obstacles)
self.player.update(self.obstacles, self.keys)
self.update_viewport()
self.bolts.update(self.screen_rect, self.obstacles)
def draw(self):
"""
Draw all necessary objects to the level surface, and then draw
the viewport section of the level to the display surface.
"""
self.level.fill(pg.Color("lightblue"))
self.obstacles.draw(self.level)
self.level.blit(self.win_text, self.win_rect)
self.player.draw(self.level)
self.bolts.draw(self.level)
self.screen.blit(self.level, (0,0), self.viewport)
def display_fps(self):
"""Show the programs FPS in the window handle."""
caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps())
pg.display.set_caption(caption)
def main_loop(self):
"""As simple as it gets."""
while not self.done:
self.event_loop()
self.update()
self.draw()
pg.display.update()
self.clock.tick(self.fps)
self.display_fps()
if __name__ == "__main__":
os.environ['SDL_VIDEO_CENTERED'] = '1'
pg.init()
pg.display.set_caption(CAPTION)
pg.display.set_mode(SCREEN_SIZE)
PLAYERIMG = pg.image.load("playertst.png").convert()
PLAYERIMG.set_colorkey(COLOR_KEY)
run_it = Control()
run_it.main_loop()
pg.quit()
sys.exit()
The % 2*pi unnecessary, and your get_angle function has no return value, but you do an assignment to self.angle = self.get_angle, but that is not the issue. The issue is that the mouse position is relative to the screen (i.e. clicking in the top right area of your game screen will always yield (0,480) if your screen is 640x480), while the position of the (character) rectangle is given in your game play area, which is larger than the screen, ergo if you move the character and thus the view shifts, you are getting coordinates in two different coordinate systems. You will have to keep track of where the view is in your game play area and add the offset to the mouse coordinates.
I have Particle and Explosion classes I am drawing using pygame.
An Explosion represents a bunch of flying Particles.
A Particle fades eventually; when its ttl property becomes 0 it should not be visible and should be removed from the Explosion.particles.
I want the program to delete dead particles so that they are not updated.
The issue I am having is with the Explosion.update() method. It seems it's not removing any Particles. I am experiencing a bug where supposedly 'dead' Particles are still drawn but their movement is not updated.I've experimented on lists in python and verified that the premise of iterating through a 'dead' list to remove from the other list works.Any suggestions on where the fault lies in this code would be greatly appreciated.
Edit: I've attached and shortened both source files for better context.
explosion.py
import sys
import pygame
from particleac import *
class App:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode(SCREEN_SIZE, pygame.HWSURFACE|pygame.DOUBLEBUF)
self.clock = pygame.time.Clock()
self.running = True
self.explosions = []
self.frame_no = 0
def check_input(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
if event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEMOTION:
x, y = pygame.mouse.get_pos()
self.explosions.append(Explosion(x, y))
def update_screen(self):
self.screen.fill(BLACK)
dead_explosions = []
# remove explosion if particles all gone
for e in self.explosions:
if len(e.particles) == 0:
dead_explosions.append(e)
else:
for p in e.particles:
pygame.draw.circle(self.screen, p.colour, [p.x, p.y], p.size)
p.update()
for e in dead_explosions:
self.explosions.remove(e)
pygame.display.flip()
self.frame_no += 1
self.frame_no %= 60
self.clock.tick(FRAMERATE)
def run(self):
while self.running:
self.check_input()
self.update_screen()
pygame.quit()
app = App()
app.run()
particleac.py
import random
import sys
BLACK = [ 0, 0, 0]
WHITE = [255, 255, 255]
BLUE = [0, 0, 255]
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 400
SCREEN_CENTRE = [SCREEN_WIDTH/2, SCREEN_HEIGHT/2]
FRAMERATE = 40
SCREEN_SIZE = [SCREEN_WIDTH, SCREEN_HEIGHT]
GRAVITY = 1
TERMINAL_VELOCITY = 10
class Particle:
def __init__(self, x=SCREEN_CENTRE[0], y=SCREEN_CENTRE[1], colour=WHITE):
self.x = x
self.y = y
self.colour = colour
self.brightness = 255
self.size = 2
self.x_velocity = random.randrange(-4, 4)
self.y_velocity = random.randrange(-8, 3)
self.ttl = random.randrange(50, 200)
self.decay = (self.ttl / FRAMERATE) * 2
def update(self):
if self.ttl > 0:
self.ttl -= 1
self.brightness -= self.decay
if self.brightness < 0:
self.brightness = 0
self.colour[0] = self.colour[1] = self.colour[2] = self.brightness
self.y_velocity += GRAVITY
self.y += self.y_velocity
self.x += self.x_velocity
if self.y > SCREEN_HEIGHT:
self.y -= SCREEN_HEIGHT
class Explosion:
MAX_NUM_PARTICLES = 2
def __init__(self, x=0, y=0, colour=WHITE):
self.x = x
self.y = y
self.colour = colour
self.x_velocity = random.randrange(-4, 4)
self.y_velocity = random.randrange(-6, -1)
self.num_particles = random.randrange(1, self.MAX_NUM_PARTICLES)
self.particles = []
for i in range(self.MAX_NUM_PARTICLES):
p = Particle(self.x, self.y)
p.colour = self.colour
p.x_velocity += self.x_velocity
p.y_velocity += self.y_velocity
self.particles.append(p)
def update(self):
for p in self.particles:
p.update()
self.particles = [p for p in self.particles if p.ttl > 0]
sys.stdout.write("len(self.particles) == {}".format(len(self.particles)))
sys.stdout.flush()
Your code works for me, once sys.out.write() is changed to sys.stdout.write(). Since there is that error in your code, are you sure that the code in your post the same as the code that is failing?
You can simplify Explosion.update() to this:
def update(self):
for p in self.particles:
p.update()
self.particles = [p for p in self.particles if p.ttl > 0]
Possibly this change might fix the problem because you are now dealing with only one list, but I believe that your code should work.
The problem may come from the fact that self.particles is defined at the class level. This may cause some problems elsewhere in your code of you create more than one instance of the Explosion class. Try moving the self.particle in the constructor, and see what happens.
(As a suggestion) Since you build a list anyways, why not copy the ones that are alive?
def update(self):
particles_alive = []
for p in self.particles:
p.update()
sys.out.write("p.ttl == {}\n".format(p.ttl))
if p.ttl == 0:
sys.out.write("Particle died.\n")
else:
particles_alive.append(p)
self.particles = particles_alive
Solution: I was never calling e.update() !Therefore Explosions weren't updating while iterating through them, which meant the dead Particles weren't being removed. Aaargh!I added e.update() to app.update_screen() and removed the incorrect p.update() (which is called for each Particle in the e.update()).
def update_screen(self):
self.screen.fill(BLACK)
dead_explosions = []
for e in self.explosions:
if len(e.particles) == 0:
dead_explosions.append(e)
else:
e.update()
for p in e.particles:
pygame.draw.circle(self.screen, p.colour, [p.x, p.y], p.size)
for e in dead_explosions:
self.explosions.remove(e)
pygame.display.flip()
self.clock.tick(FRAMERATE)
I now know to give better context when posting up code. Thank you kindly for your trouble guys!
I'm running this python code and having a problem with the accel function. The rotate method works fine when left and right are pressed however when up is pressed nothing happens. I've stepped through the code in a debugger and the my_ship.accel line is executed but it doesn't go to method body, it just continues as if that line isn't there. Idk what's wrong please help. Also my_ship is the name of a Ship object and it is defined properly lower in my code.
import simplegui
WIDTH = 800
HEIGHT = 600
class ImageInfo:
def __init__(self, center, size, radius = 0, lifespan = None, animated = False):
self.center = center
self.size = size
self.radius = radius
if lifespan:
self.lifespan = lifespan
else:
self.lifespan = float('inf')
self.animated = animated
def get_center(self):
return self.center
def get_size(self):
return self.size
def get_radius(self):
return self.radius
def get_lifespan(self):
return self.lifespan
def get_animated(self):
return self.animated
def change_center(self, new_center):
self.center = new_center
# ship image
ship_info = ImageInfo([45, 45], [90, 90], 35)
ship_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/double_ship.png")
class Ship:
def __init__(self, pos, vel, angle, image, info):
self.pos = [pos[0],pos[1]]
self.vel = [vel[0],vel[1]]
self.thrust = False
self.angle = angle
self.angle_vel = 0
self.image = image
self.image_center = info.get_center()
self.image_size = info.get_size()
self.radius = info.get_radius()
self.info = info
self.accel = 10
self.angle_accel = .1
def draw(self,canvas):
if not self.thrust:
self.info.change_center(ship_center)
canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle)
else:
self.info.change_center(thrust_ship_center)
canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle)
def update(self):
self.pos[0] += self.vel[0]
self.pos[1] += self.vel[1]
self.angle += self.angle_vel
def accel(self):
self.thrust = True
self.vel[0] += self.accel
self.vel[1] += self.accel
def rotate(self, direction):
if direction == "left":
self.angle_vel -= self.angle_accel
elif direction == "right":
self.angle_vel += self.angle_accel
else:
print "error"
def keydown_handler(key):
if key == simplegui.KEY_MAP['left']:
my_ship.rotate("left")
elif key == simplegui.KEY_MAP['right']:
my_ship.rotate("right")
elif key == simplegui.KEY_MAP['up']:
my_ship.accel
elif key == simplegui.KEY_MAP['space']:
self.angle_vel += self.angle_accel
def keyup_handler(key):
if key == simplegui.KEY_MAP['left']:
my_ship.rotate("right")
elif key == simplegui.KEY_MAP['right']:
my_ship.rotate("left")
my_ship = Ship([WIDTH / 2, HEIGHT / 2], [0, 0], 1, ship_image, ship_info)
This:
my_ship.accel
Doesn't call the method my_ship.accel, any more than 2 calls the number 2. To call something in Python, you need parentheses. So:
my_ship.accel()
(If you're wondering why Python does it this way when other languages, like Ruby, don't… well, this means that you can use the method object my_ship.accel as a value—store it to call later, pass it to map, etc.)
But you've got another problem on top of that.
You define a method accel on Ship objects. But you also assign an integer value 10 to self.accel on Ship objects. There's no way self.accel can mean two different things at once, both the method and the number. So, which one "wins"? In this case, the self.accel = 10 happens at the time you constructed your Ship, which is later, so it wins.
So, when you write my_ship.accel, you're just referring to the number 10. And when you write my_ship.accel(), you're trying to call the number 10 as if it were a function. Hence the TypeError.
The solution is to not reuse the same name for two different things. Often, naming functions after verbs and attributes after nouns is a good way to avoid this problem—although you also have to avoid gratuitous abbreviations, because otherwise you're probably going to abbreviate acceleration and accelerate to the same accel, as you did here.