Why is a variable not updating after leaving a function? - python

I've made a slider which should update the size of a grid that I have made. When the slider is clicked and moved the value of the slider changes however the grid size stays the same and does not update. How would I solve this? thanks.
Here is the function that I use to update the slider value. This is called when the slider is clicked in the game loop
def slider_loop(s):
s.move()
grid_size = int(slider_value.val)
return grid_size
here is the section in the main game loop where the slider loop is called
for s in slides:
if s.hit:
slider_loop(s)
here is the slider class
class slider():
def __init__(self, name, val, maxi, mini, x_pos, y_pos):
font = pygame.font.SysFont("Verdana", 12)
self.val = val # start value
self.maxi = maxi # maximum at slider position right
self.mini = mini # minimum at slider position left
self.xpos = x_pos # x-location on screen
self.ypos = y_pos
self.surf = pygame.surface.Surface((100, 50))
self.hit = False # the hit attribute indicates slider movement due to mouse interaction
self.txt_surf = font.render(name, 1, black)
self.txt_rect = self.txt_surf.get_rect(center=(50, 15))
# Static graphics - slider background #
self.surf.fill((100, 100, 100))
pygame.draw.rect(self.surf, grey, [0, 0, 100, 50], 3)
pygame.draw.rect(self.surf, orange, [10, 10, 80, 10], 0)
pygame.draw.rect(self.surf, white, [10, 30, 80, 5], 0)
self.surf.blit(self.txt_surf, self.txt_rect) # this surface never changes
# dynamic graphics - button surface #
self.button_surf = pygame.surface.Surface((20, 20))
self.button_surf.fill(trans)
self.button_surf.set_colorkey(trans)
pygame.draw.circle(self.button_surf, black, (10, 10), 6, 0)
pygame.draw.circle(self.button_surf, orange, (10, 10), 4, 0)
def draw(self):
""" Combination of static and dynamic graphics in a copy of
the basic slide surface
"""
# static
surf = self.surf.copy()
# dynamic
pos = (10+int((self.val-self.mini)/(self.maxi-self.mini)*80), 33)
self.button_rect = self.button_surf.get_rect(center=pos)
surf.blit(self.button_surf, self.button_rect)
self.button_rect.move_ip(self.xpos, self.ypos) # move of button box to correct screen position
# screen
screen.blit(surf, (self.xpos, self.ypos))
def move(self):
"""
The dynamic part; reacts to movement of the slider button.
"""
self.val = (pygame.mouse.get_pos()[0] - self.xpos - 10) / 80 * (self.maxi - self.mini) + self.mini
if self.val < self.mini:
self.val = self.mini
if self.val > self.maxi:
self.val = self.maxi

Looks like grid_size is returned by slider_loop but never use in your code.
Do something like
new_grid_size = slider_loop(s)
if you want to get it.

Your for-loop should probably be like this:
(because currently you're not using the returned grid_size value at all)
for s in slides:
if s.hit:
grid_size = slider_loop(s)
And your function should maybe be corrected to be like this:
def slider_loop(s):
s.move()
grid_size = int(s.val)
return grid_size

Related

Pong Game: Paddle Won't Move with Input (Python)

I am a novice to Python trying to make the game Pong. I have created a Paddle class with the Turtle Graphics module, but I can't get the paddle to move. I just want to start with one direction and then down shouldn't be too hard from there. Can anyone see what I am doing wrong with my method?
from turtle import Turtle
COORDINATES = [(350, 20), (350, 0), (350, -20)]
X_COORDINATES = [350, 350, 350]
Y_COORDINATES = [20, 0, -20]
class Paddle(Turtle):
def __init__(self):
super().__init__()
self.paddle = []
self.create_paddles()
self.coordinate_number = 0
def create_paddles(self):
for coordinates in COORDINATES:
self.paddle_block = Turtle(shape='square')
self.paddle_block.goto(coordinates)
self.paddle_block.color('white')
self.paddle.append(self.paddle_block)
def w(self):
global Y_COORDINATES
Y_COORDINATES = [coordinate + 100 for coordinate in Y_COORDINATES]
for self.paddle_block in self.paddle:
self.paddle_block.goto(X_COORDINATES[self.coordinate_number], Y_COORDINATES[self.coordinate_number])
self.coordinate_number += 1
self.coordinate_number = 0
I tried to iterate through the y-coordinates and add to each of them with my function. From there, I tried to iterate through each paddle block and move it's current location to a new one, taking in the newly updated y coordinate. I expect movement, but I am not seeing any movement whatsoever.
This isn't the usual approach to this problem, but I can see why it might be advantageous. Your primary issue seems to be not being able to determine what should be global, what should be local, and what should be a property. Let's make this work to demonstrate the use of all three:
from turtle import Screen, Turtle
COORDINATES = [(350, 20), (350, 0), (350, -20)]
class Paddle(Turtle):
def __init__(self):
super().__init__()
self.paddle = []
self.coordinates = list(COORDINATES) # make copy
self.create_paddles()
def create_paddles(self):
for coordinate in self.coordinates:
paddle_block = Turtle(shape='square', visible=False)
paddle_block.penup()
paddle_block.color('white')
paddle_block.goto(coordinate)
paddle_block.showturtle()
self.paddle.append(paddle_block)
def move_up(self):
self.coordinates = [(x, y + 10) for x, y in self.coordinates]
for coordinate_number, paddle_block in enumerate(self.paddle):
paddle_block.goto(self.coordinates[coordinate_number])
def move_down(self):
self.coordinates = [(x, y - 10) for x, y in self.coordinates]
for coordinate_number, paddle_block in enumerate(self.paddle):
paddle_block.goto(self.coordinates[coordinate_number])
screen = Screen()
screen.bgcolor('black')
paddle_1 = Paddle()
screen.onkey(paddle_1.move_up, 'w')
screen.onkey(paddle_1.move_down, 's')
screen.listen()
screen.mainloop()

Collision detection between pygame.Surface and mouse not working [duplicate]

This question already has answers here:
Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
(1 answer)
How to detect collisions between two rectangular objects or images in pygame
(1 answer)
Closed 2 years ago.
I am trying to make a canvas for pixel art.
class Canvas:
def __init__(self):
self.__blocks = []
self.__positions = []
for i in range(1830):
self.__blocks.append(pygame.Surface((20, 20)).convert())
for y in range(30):
y *= 20
for x in range(61):
x = x* 20
self.__positions.append([x, y])
self.__color = False
def draw(self, window):
for i in range(1830):
self.__color = not self.__color
if self.__color:
self.__blocks[i].fill((200, 200, 200))
else:
self.__blocks[i].fill((50, 50, 50))
window.blit(self.__blocks[i], (self.__positions[i][0]
, self.__positions[i][1]))
Here I am trying to generate and draw 1830 unique surfaces and this works. I then tried implementing collision detection between each block and the mouse and failed.
def collided(self, pos):
for i in range(1380):
block = self.__blocks[i].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
Then I did different tests on why it might be failing. Here is one of them. I will change a single block's color, in our case the 10th block self.__blocks[10].fill((255, 0, 0)) to red so we know which box to click on. Then we will try to check for collision for that particular block.
def testBlock(self, pos):
block = self.__blocks[10].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x)
And it doesn't work, but the weird thing is it works for the first block(in the 0th index) and only the first block no matter which surface I test. Any idea on how to fix this would be appreciated. The following is copy and paste code.
import pygame
pygame.init()
win = pygame.display
D = win.set_mode((1220, 600))
class Canvas:
def __init__(self):
self.__blocks = []
self.__positions = []
for i in range(1830):
self.__blocks.append(pygame.Surface((20, 20)).convert())
for y in range(30):
y *= 20
for x in range(61):
x = x* 20
self.__positions.append([x, y])
self.__color = False
self.testBlock = 10
def draw(self, window):
for i in range(1830):
self.__color = not self.__color
if self.__color:
self.__blocks[i].fill((200, 200, 200))
else:
self.__blocks[i].fill((50, 50, 50))
self.__blocks[self.testBlock].fill((255, 0, 0)) # Changing the color for testing
window.blit(self.__blocks[i], (self.__positions[i][0]
, self.__positions[i][1]))
def test(self, pos):
block = self.__blocks[self.testBlock].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
canvas = Canvas()
while True:
D.fill((0, 0, 0))
pygame.event.get()
mousepos = pygame.mouse.get_pos()
canvas.draw(D)
canvas.test(mousepos)
win.flip()
When you call .get_rect() on a Surface, it does not know its current position, because that is not Surface information. So you need to assign the location to the Rect before collision detection.
With your current code layout, you could do this during the construction. With the Canvass blocks position now held in the __rects list, the __positions list becomes superfluous.
class Canvass:
def __init__(self):
self.__blocks = []
self.__rects = []
for y in range( 30 ):
for x in range( 61 ):
self.__blocks.append(pygame.Surface((20, 20)).convert())
self.__rects.append( self.__blocks[-1].get_rect() )
self.__rects[-1].topleft = ( x, y )
self.__color = False
self.testBlock = 10
This gives you a simple test:
def collided(self, pos):
hit = False
for i in range( len( self.__rects ) ):
if ( self.__rects[i].collidepoint( pos[0], pos[1] ) ):
print( "Click on block %d" % ( i ) )
hit = True
break
return hit, i
.get_rect() gives rect with block's size but with position (0, 0)
you have real position in __positions and you would need
.get_rect(topleft=self.__positions[self.testBlock])
def test(self, pos):
block = self.__blocks[self.testBlock].get_rect(topleft=self.__positions[self.testBlock])
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
But it would be better to get rect and set its position at start and later not use get_rect().
You could also create class Pixel similar to class Sprite with self.image to keep surface and self.rect to keep its size and position. And then you could use Group to check collision with all pixels.
EDIT:
Example which uses class pygame.sprite.Sprite to create class Pixel and it keeps all pixels in pygame.sprite.Group
It also handle events (MOUSEBUTTONDOWN) to change color in any pixel when it is clicked.
import pygame
# --- classes ---
class Pixel(pygame.sprite.Sprite):
def __init__(self, x, y, color, width=20, height=20):
super().__init__()
self.color_original = color
self.color = color
self.image = pygame.Surface((20, 20)).convert()
self.image.fill(self.color)
self.rect = pygame.Rect(x, y, width, height)
def handle_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
if self.color != self.color_original:
self.color = self.color_original
else:
self.color = (255,0,0)
self.image.fill(self.color)
# event handled
return True
# event not handled
return False
class Canvas:
def __init__(self):
# create group for sprites
self.__blocks = pygame.sprite.Group()
# create sprites
self.__color = False
for y in range(30):
y *= 20
for x in range(61):
x *= 20
self.__color = not self.__color
if self.__color:
color = (200, 200, 200)
else:
color = (50, 50, 50)
self.__blocks.add(Pixel(x, y, color))
# changing the color for testing
self.testBlock = 10
all_sprites = self.__blocks.sprites()
block = all_sprites[self.testBlock]
block.image.fill((255, 0, 0))
def draw(self, window):
# draw all sprites in group
self.__blocks.draw(window)
def test(self, pos):
# test collision with one sprite
all_sprites = self.__blocks.sprites()
block = all_sprites[self.testBlock]
if block.rect.collidepoint(pos):
print(block.rect.x, block.rect.y)
def handle_event(self, event):
for item in self.__blocks:
if item.handle_event(event):
# don't check other pixels if event already handled
return True
# --- main ---
pygame.init()
win = pygame.display
D = win.set_mode((1220, 600))
canvas = Canvas()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
canvas.handle_event(event)
#mousepos = pygame.mouse.get_pos()
#canvas.test(mousepos)
# draws (without updates, etc)
#D.fill((0, 0, 0)) # no need clean screen if it will draw all elements again
canvas.draw(D)
win.flip()

Trouble with rendering background boxes class from a 2d list in pygame

I am trying to make a background of boxes for a simple snake game by iterating through a 2d array and drawing boxes which I've stored as instances of a class BackgroundCube in each part of the array. When I run the program there are no errors, but nothing shows up on the pygame screen.
I've printed the length of each sublist which shows a length of 20, my desired grid size. I've also just printed the entire array which shows what I believe to be instances of the class, something like this: <main.BackgroundCube object at 0x11186e090> would be one entry in the list. So I believe the problem lies in how I'm drawing the rectangles.
python
WIDTH = 400
HEIGHT = 420
screen = pygame.display.set_mode((WIDTH, HEIGHT))
class BackgroundCube:
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def draw(self, screen):
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height), 2)
def redrawGameWindow():
for x in range(20):
for y in range(20):
cube2 = background_cube_list[x][y]
cube2.draw(screen)
run = True
background_cube_list = [[0 for x in range(int(WIDTH/20))] for x in range(int((HEIGHT-20)/20))]
while run:
for cube in range(int(WIDTH / 20)):
for cube1 in range(int((HEIGHT - 20) / 20)):
background_cube_list[cube][cube1] = BackgroundCube(cube * 20, cube1 * 20, 20, 20, (144, 144, 144))
clock.tick(30)
redrawGameWindow()
Again, no errors, just a blank white window. Thank you.
You forgot to add
pygame.display.update()
in your main loop. Add it just after redrawGameWindow().
You also need to define clock, which I guess is clock = pygame.time.Clock(). Add it before the main loop.

Sprite in Pyglet not doing what I want

I am trying to have a circle that, when clicked, moves somewhere else on the screen. However, when I click the circle, nothing happens.
#IMPORT STUFF
import pyglet as pg
from random import randint
mouse = pg.window.mouse
#VARS
window = pg.window.Window(width = 640, height = 480)
score = 0
circleImg = pg.image.load("circle.png")
circle = pg.sprite.Sprite(circleImg, randint(1, window.width), randint(1, window.height))
text = pg.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
#DETECT MOUSE PRESS ON CIRCLE
#window.event
def on_mouse_press(x, y, button, modifiers):
if x == circle.x and y == circle.y:
circle.x = randint(1, window.width)
circle.y = randint(1, window.height)
#window.event
def on_draw():
window.clear()
text.draw()
circle.draw()
pg.app.run()
import pyglet
from pyglet.gl import *
from random import randint
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
class Circle(pyglet.sprite.Sprite):
def __init__(self, radiance=5, x=0, y=0):
self.texture = pyglet.image.load('circle.png')
super(Circle, self).__init__(self.texture)
def click(self, x, y):
if x >= self.x and y >= self.y:
if x <= self.x + self.texture.width and y <= self.y + self.texture.height:
return self
mouse = pyglet.window.mouse
#VARS
window = pyglet.window.Window(width = 640, height = 480)
score = 0
#circleImg = pyglet.image.load("circle.png")
#circle = pyglet.sprite.Sprite(circleImg, randint(1, window.width), randint(1, window.height))
circle = Circle(x=50, y=50)
text = pyglet.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
#DETECT MOUSE PRESS ON CIRCLE
#window.event
def on_mouse_press(x, y, button, modifiers):
if circle.click(x, y):
print('Clicked in circle')
circle.x = randint(0, window.width - 10)
circle.y = randint(0, window.height - 10)
#window.event
def on_draw():
window.clear()
text.draw()
circle.draw()
pyglet.app.run()
A short description of what this does is it creates a custom class called Circle that inherits the Sprite class. It loads the circle.png as a texture with a alpha channel that gets blended by the GL library.
We add a custom function called click that checks if the lowest x,y coordinates are higher than the circles lowest x,y, then we check if the cursor is below x+width and same for y of the image region.
If that's the case, we return the circle sprite class as a True value in case we want to use the sprite.
Future enhancements:
You should draw the circle using gl functions, hence why I've defined radiance in the class definitions. However radiance here is never used, it's a placeholder for the future.
This is so you can use math to defined if you actually clicked within the circle, but this is beyond my scope of quick answers.. I would have to do a lot of debugging myself in order to get the math to add up (it's not my strong side).
What makes it work now is that we use the image width, height, x and y data to crudely check if we're within the image, aka "the circle".
trying to draw over sprite or change picture pyglet
As a bonus, I'll add this answer to the list of enhancements because it contains some stuff that might be useful. One would be to replace 90% of your code with a custom pyglet.window.Window class to replace global variables and decorators and stuff.
And it would look something like this:
import pyglet
from pyglet.gl import *
from random import randint
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
key = pyglet.window.key
class Circle(pyglet.sprite.Sprite):
def __init__(self, radiance=5, x=0, y=0):
self.texture = pyglet.image.load('circle.png')
super(Circle, self).__init__(self.texture)
def click(self, x, y):
if x >= self.x and y >= self.y:
if x <= self.x + self.texture.width and y <= self.y + self.texture.height:
return self
class MainScreen(pyglet.window.Window):
def __init__ (self):
super(MainScreen, self).__init__(800, 600, fullscreen = False)
self.x, self.y = 0, 0
self.bg = pyglet.sprite.Sprite(pyglet.image.load('background.jpg'))
self.sprites = {}
self.sprites['circle'] = Circle(x=50, y=50)
self.sprites['label'] = pyglet.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_press(self, x, y, button, modifiers):
if self.sprites['circle'].click(x, y):
print('Clicked in circle')
self.sprites['circle'].x = randint(0, self.width - 10)
self.sprites['circle'].y = randint(0, self.height - 10)
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.bg.draw()
for sprite_name, sprite_obj in self.sprites.items():
sprite_obj.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = MainScreen()
x.run()
I'm not familiar with pyglet, but I'm guessing the problem is that you're checking whether x == circle.x etc, which means it only moves when you click the single pixel at the exact centre of the circle. Try some kind of maximum distance from the centre (e.g. a hypotenuse math.sqrt( (x-circle.x)**2 + (y-circle.y)**2) < circle.radius

All rectangles in one iteration of a game loop drawing the same colour despite explicit colour arguments

I'm writing a simple toy in Pygame. When you press a key on the home row, it does a little burst of particles.
class Particle():
x = 0
y = 0
size = 0
colour = (255, 255, 255)
rect = None
def __init__(self, x, y, size, colour):
self.x = x
self.y = y
self.size = size
self.colour = colour # Particle has its own colour
self.rect = pygame.Rect(self.x, self.y, self.size, self.size)
class Burst():
x = 0
y = 0
colour = (255, 255, 255)
count = 0
sound = None
particles = []
def __init__(self, x, y, colour, count, sound):
self.x = x
self.y = y
self.colour = colour # Burst has its own colour, too - all its particles should have the same colour as it
self.count = count
self.sound = sound
self.particles.append(Particle(self.x, self.y, 5, self.colour))
def update(self):
self.particles.append(Particle(random.randint(1, 30) + self.x, random.randint(1, 30) + self.y, 5, self.colour))
def draw(self):
global screen
for p in self.particles:
pygame.draw.rect(screen, p.colour, p.rect) # This draws the particles with the correct colours
#pygame.draw.rect(screen, self.colour, (60, 60, 120, 120), 4) # This draws the particles all the same colour
#screen.fill(p.colour, p.rect) # This draws the particles all the same colour
The line you're looking for is in Burst.draw. For some reason, only the uncommented one works correctly. The other two lines, which should be the same as far as I can tell, only draw the first burst's particles correctly. Any subsequent bursts change all particles onscreen to match their colour.
I can provide more code, but there's not much more to it. Basically keypresses add Bursts to an array, and every tick I step through that array calling update() and draw().
Does anyone know what I did wrong, and then accidentally fixed?
Because all particles in the screen belong to the same collection Burst.particles.
And every time you process a Burst you are processing all the particles, and all gets painted with the last colour.
Just move the initialization particles = [] to the init method.
def __init__(self, x, y, colour, count, sound):
...
self.particles = []
self.particles.append(Particle(self.x, self.y, 5, self.colour))
Update
You are using a Java/C# style of coding classes. You shouldn't put any of the initializations at the class level, unless they are constants or class attributes.
IE:
class Burst():
class_attribute = 0 # declaration of class (static) attribute
def __init__(self, ...):
self.attribute = 0 # declaration of object (regular) attribute
You shouldn't make class declarations of attribute you will use a object attributes.
Just remove all the declarations previous to the init method in both classes.

Categories

Resources