I am working through pygame tutorials and at the moment I am trying to figure out how to select a circle which is firing a ball where a ball hits another ball which in turn knocks a box down. The knocking of the box works fine when the ball hits the box. However, when I add the mouse movement so that I can select the ball again and place it in the same position so that it can be hit again so that the box knocks again. The ball just rolls backwards without firing the second ball to knock the box.Here is the previous code that works with one ball firing another ball but without the mouse movements i.e without allowing the ball to be selected and dragged.
import pygame
from pygame.locals import *
from pygame.color import *
import pymunk as pm
from pymunk import Vec2d
import sys
from random import randint
def to_pygame(p):
"""Small hack to convert pymunk to pygame coordinates"""
return int(p[0]), int(-p[1]+600)
def draw_ball(screen, ball, colour):
r = ball.radius
rot = ball.body.rotation_vector
p = to_pygame(ball.body.position)
p2 = Vec2d(rot.x, -rot.y) * r * 0.9
pygame.draw.line(screen, THECOLORS["red"], p, p+p2)
pygame.draw.circle(screen, colour, p, int(r), 3)
def add_ball(space, x=0, y=130):
mass = 1.3
radius = 20
inertia = pm.moment_for_circle(mass, 0, radius, (0,0))
body = pm.Body(mass, inertia)
body.position = (x,y)
shape = pm.Circle(body, radius, (0,0))
shape.friction = 10.0
shape.elasticity = 1.0
space.add(body, shape)
return shape
def add_box(space, size, pos, mass=0.3):
points = [(-size, -size), (-size, size), (size,size), (size, -size)]
moment = pm.moment_for_poly(int(mass), points, (0,0))
body = pm.Body(mass, moment)
body.position = pos
shape = pm.Poly(body, points, (0,0))
shape.friction = 1
space.add(body,shape)
return shape
def draw_box(screen, box):
ps = box.get_points()
ps.append(ps[0])
newps = [to_pygame(x) for x in ps]
pygame.draw.polygon(screen, THECOLORS["blue"], newps, 3)
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Impulsive balls")
clock = pygame.time.Clock()
balls = []
space = pm.Space()
space.gravity = (0.0, -300.0)
# ground
body = pm.Body()
shape = pm.Segment(body, (0,100), (450,100), .0)
shape.friction = 6.0
space.add(shape)
# hidden ramp
body = pm.Body()
slope = pm.Segment(body, (0,100), (180,150), .0)
space.add(slope)
balls.append(add_ball(space, 10, 130))
balls.append(add_ball(space, 100, 150))
#joint = pm.PinJoint(balls[0].body, balls[1].body)
#joint.distance = 90
mass = 1.0
size = 20
box = add_box(space, size, (400,100+size), mass)
count = 0
while 1:
space.step(1/30.0)
clock.tick(30)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit(0)
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit(0)
if count == 10:
pm.Body.apply_impulse(balls[0].body, (450,0))
screen.fill(THECOLORS["white"])
pygame.draw.line(screen, THECOLORS["red"], to_pygame((0,100)), to_pygame((450,100)), 3)
pygame.draw.line(screen, THECOLORS["red"], to_pygame((0,100)), to_pygame((180,150)), 3)
draw_box(screen, box)
for ball in balls:
draw_ball(screen, ball, THECOLORS["green"])
pygame.display.flip()
count += 1
if __name__ == '__main__':
main()
Here is the second version where I add the mouse movement code
import pygame
from pygame.locals import *
from pygame.color import *
import pymunk as pm
from pymunk import Vec2d
import sys
from random import randint
def to_pygame(p):
"""Small hack to convert pymunk to pygame coordinates"""
return int(p[0]), int(-p[1]+600)
def from_pygame(p):
return to_pygame(p)
def draw_ball(screen, ball, colour):
r = ball.radius
rot = ball.body.rotation_vector
p = to_pygame(ball.body.position)
p2 = Vec2d(rot.x, -rot.y) * r * 0.9
pygame.draw.line(screen, THECOLORS["blue"], p, p+p2)
pygame.draw.circle(screen, colour, p, int(r), 3)
def add_ball(space, x=0, y=130):
mass = 1.3 #1.5
radius = 20
inertia = pm.moment_for_circle(mass, 0, radius, (0,0))
body = pm.Body(mass, inertia)
body.position = (x,y)
shape = pm.Circle(body, radius, (0,0))
shape.friction = 10.0
shape.elasticity = 1.0
space.add(body, shape)
return shape
def add_box(space, size, pos, mass=0.3):
points = [(-size, -size), (-size, size), (size,size), (size, -size)]
moment = pm.moment_for_poly(int(mass), points, (0,0))
body = pm.Body(mass, moment)
body.position = pos
shape = pm.Poly(body, points, (0,0))
shape.friction = 1
space.add(body,shape)
return shape
def draw_box(screen, box):
ps = box.get_points()
ps.append(ps[0])
newps = [to_pygame(x) for x in ps]
pygame.draw.polygon(screen, THECOLORS["blue"], newps, 3)
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Impulsive balls")
clock = pygame.time.Clock()
body = []
selected = None
balls = []
space = pm.Space()
space.gravity = (0.0, -300.0)
# ground
body = pm.Body()
shape = pm.Segment(body, (0,100), (450,100), .0)
shape.friction = 6.0
space.add(shape)
# hidden ramp
body = pm.Body()
slope = pm.Segment(body, (0,100), (180,150), .0)
space.add(slope)
balls.append(add_ball(space, 10, 130))
balls.append(add_ball(space, 100, 150))
#joint = pm.PinJoint(balls[0].body, balls[1].body)
#joint.distance = 90
mass = 1.0
size = 20
box = add_box(space, size, (400,100+size), mass)
count = 0
while 1:
space.step(1/30.0)
clock.tick(30)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit(0)
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit(0)
if count == 10:
pm.Body.apply_impulse(balls[0].body, (450,0))
if event.key == K_p:
balls[0].body.apply_impulse((450,0))
if event.key == K_s:
balls[0].body.apply_impulse((-450,0))
elif event.type == MOUSEBUTTONDOWN:
p = from_pygame(Vec2d(event.pos))
selected = space.point_query_first(p)
elif event.type == MOUSEBUTTONUP:
if selected != None:
selected = None
elif event.type == MOUSEMOTION:
if selected != None:
selected.body.position = from_pygame(event.pos)
screen.fill(THECOLORS["white"])
pygame.draw.line(screen, THECOLORS["red"], to_pygame((0,100)), to_pygame((450,100)), 3)
pygame.draw.line(screen, THECOLORS["red"], to_pygame((0,100)), to_pygame((180,150)), 3)
draw_box(screen, box)
for ball in balls:
draw_ball(screen, ball, THECOLORS["green"])
pygame.display.flip()
count += 1
if __name__ == '__main__':
main()
Also, how can I have the ball at the same position so that I can drag it and push the other ball to knock the box instead of making the ball roll back, so later I can select the fired ball again and place it next to the ball without rolling back
For your first issue, where the ball is not firing in the second file. The problem is that you have put that code into the event block, which only happens when an external event is fired (like pressing a key). To fix it, that chunk needs to be moved out of that for loop as shown below:
...
while 1:
space.step(1/30.0)
clock.tick(30)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit(0)
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit(0)
if event.key == K_p:
balls[0].body.apply_impulse((450,0))
if event.key == K_s:
balls[0].body.apply_impulse((-450,0))
elif event.type == MOUSEBUTTONDOWN:
p = from_pygame(Vec2d(event.pos))
selected = space.point_query_first(p)
elif event.type == MOUSEBUTTONUP:
if selected != None:
selected = None
elif event.type == MOUSEMOTION:
if selected != None:
selected.body.position = from_pygame(event.pos)
if count == 10:
pm.Body.apply_impulse(balls[0].body, (450,0))
...
To keep the ball from moving, what I would recommend is to place them on flat ground. I've made the following changes to main to show what I mean. Please note that I disabled the ball firing so that you can see that the balls stay in position. I'd also recommend that you put some invisible walls off the screen on to keep all of the objects stuck inside the frame.
...
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Impulsive balls")
clock = pygame.time.Clock()
body = []
selected = None
balls = []
space = pm.Space()
space.gravity = (0.0, -300.0)
# ground
body = pm.Body()
shape = pm.Segment(body, (0,100), (450,100), .0)
shape.friction = 6.0
space.add(shape)
# hidden ramp
body = pm.Body()
slope = pm.Segment(body, (20,100), (180,150), .0)
space.add(slope)
body = pm.Body()
slopetop = pm.Segment(body, (180,150), (190,150), .0)
space.add(slopetop)
balls.append(add_ball(space, 10, 130))
balls.append(add_ball(space, 185, 170))
#joint = pm.PinJoint(balls[0].body, balls[1].body)
#joint.distance = 90
mass = 1.0
size = 20
box = add_box(space, size, (400,100+size), mass)
count = 0
while 1:
space.step(1/30.0)
clock.tick(30)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit(0)
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit(0)
if event.key == K_p:
balls[0].body.apply_impulse((450,0))
if event.key == K_s:
balls[0].body.apply_impulse((-450,0))
elif event.type == MOUSEBUTTONDOWN:
p = from_pygame(Vec2d(event.pos))
selected = space.point_query_first(p)
elif event.type == MOUSEBUTTONUP:
if selected != None:
selected = None
elif event.type == MOUSEMOTION:
if selected != None:
selected.body.position = from_pygame(event.pos)
if count == 10 and 0:
pm.Body.apply_impulse(balls[0].body, (450,0))
screen.fill(THECOLORS["white"])
pygame.draw.line(screen, THECOLORS["red"], to_pygame((0,100)), to_pygame((450,100)), 3)
pygame.draw.line(screen, THECOLORS["red"], to_pygame((20,100)), to_pygame((180,150)), 3)
pygame.draw.line(screen, THECOLORS["red"], to_pygame((180,150)), to_pygame((190,150)), 3)
draw_box(screen, box)
for ball in balls:
draw_ball(screen, ball, THECOLORS["green"])
pygame.display.flip()
count += 1
...
Related
So I keep trying to detect two different moving rects in pygame colliding but its just constantly registering a collision. As in the console constantly print lose, but the enemy isn't even touching the ball. The effect I want is just a simple game where the ball has to avoid the tack so that the ball doesn't pop.
import pygame
import sys
import math
import random
pygame.init()
size = width, height = 800, 600
white = 255, 255, 255
red = 255, 0, 0
clock = pygame.time.Clock()
screen = pygame.display.set_mode(size)
character = pygame.image.load("intro_ball.gif")
charrect = character.get_rect()
x = 340
y = 480
enemy = pygame.image.load("ho.png")
enrect = enemy.get_rect()
ex = random.randint(0, 690)
ey = 0
lose = False
while 1:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
if ey >= 600 and lose != True:
ey = 0
ex = random.randint(0, 690)
collide = pygame.Rect.colliderect(charrect, enrect)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT and x < 690:
x += 4
if event.key == pygame.K_LEFT and x > 0:
x -= 4
if collide:
lose = True
else: lose = False
if lose == True: print("lose")
ey += 2
screen.fill(white)
screen.blit(enemy, (ex, ey))
screen.blit(character, (x, y))
pygame.display.update()
pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. A Surface is blit at a position on the screen. The position of the rectangle can be specified by a keyword argument. For example, the top lelft of the rectangle can be specified with the keyword argument topleft. These keyword argument are applied to the attributes of the pygame.Rect before it is returned (see pygame.Rect for a full list of the keyword arguments):
while 1:
# [...]
charrect = character.get_rect(topleft = (x, y))
enrect = enemy.get_rect(topleft = (ex, ey))
collide = pygame.Rect.colliderect(charrect, enrect)
# [...]
On the other hand you do not need the variables x, y, ex, ey at all. Use the features of the pygame.Rect objects.
Additionally read How can I make a sprite move when key is held down
Minimal example:
import pygame, sys, math, random
pygame.init()
size = width, height = 800, 600
white = 255, 255, 255
red = 255, 0, 0
clock = pygame.time.Clock()
screen = pygame.display.set_mode(size)
#character = pygame.image.load("intro_ball.gif")
character = pygame.Surface((20, 20))
character.fill((0, 255, 0))
charrect = character.get_rect(topleft = (340, 480))
#enemy = pygame.image.load("ho.png")
enemy = pygame.Surface((20, 20))
enemy.fill((255, 0, 0))
enrect = enemy.get_rect(topleft = (random.randint(0, 690), 0))
lose = False
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
charrect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 4
charrect.clamp_ip(screen.get_rect())
enrect.y += 2
if enrect.y >= 600 and lose != True:
enrect.y = 0
enrect.x = random.randint(0, 690)
collide = pygame.Rect.colliderect(charrect, enrect)
if collide:
lose = True
else:
lose = False
if lose == True:
print("lose")
screen.fill(white)
screen.blit(enemy, enrect)
screen.blit(character, charrect)
pygame.display.update()
pygame.quit()
exit()
import pygame as pg
from pygame.constants import MOUSEBUTTONDOWN, MOUSEBUTTONUP
import random
pg.init()
WIDTH = 700
HEIGHT = 700
screen = pg.display.set_mode((WIDTH, HEIGHT))
screen.fill("white")
boxIMG = pg.image.load('asteroid.png')
rndCorner = [(0, 0), (0, 700), (700, 0), (700, 700)]
def draw_screen():
obstacle = pg.draw.line(screen, (0, 0, 0), (random.choice(rndCorner)), (200, 200), 2)
def box(x, y):
screen.blit(boxIMG, (x, y))
def game_loop():
running = True
left_clicking = False
boxX = 0
boxY = 0
clock = pg.time.Clock()
while running:
screen.fill("white")
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
pg.quit()
if event.type == MOUSEBUTTONDOWN:
if event.button == 1:
left_clicking = True
if event.type == MOUSEBUTTONUP:
if event.button == 1:
left_clicking = False
if left_clicking:
boxX, boxY = pg.mouse.get_pos()
boxX -= 32.5
boxY -= 32.5
if boxX < 0:
boxX = 0
elif boxX > 635:
boxX = 635
elif boxY < 0:
boxY = 0
elif boxY > 635:
boxY = 635
# Update Screen
draw_screen()
box(boxX, boxY)
pg.display.update()
clock.tick(60)
game_loop()
So I am running into an issue where the lines I drew when calling 'draw_Screen()` are flickering. I tried using doublebuf but that didn't fix the issue for me. If you are wondering what I am trying to achieve, I am trying to move the box (main player) while holding the left click button, hence the 'if left_clicking'
You choose a new random corner coordinate in every frame. This causes the flickering. Pick a random corner once and use the same coordinate in every frame:
random_corner = random.choice(rndCorner)
def draw_screen():
obstacle = pg.draw.line(screen, (0, 0, 0), random_corner, (200, 200), 2)
The blocks in the code below are falling against gravity. They are moving from the bottom of the screen to the top when I run this code. I have tried to adjust the Gravity function but it's still moving from bottom to top which is opposite gravity. The code is however running well when I tested with another Laptop. I currently learning pygame.
import sys, random
import pygame
from pygame.locals import *
import pymunk
import pymunk.pygame_util
from random import seed
from random import choice
def add_circular_weight(space,mass=1,x=150,y=550):
radius = 5 + mass/10
inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
body = pymunk.Body(mass, inertia)
body.position = x, y
shape = pymunk.Circle(body, radius, (0,0))
space.add(body, shape)
return shape
def main():
masses=[10,20,30,40,50,60,70,80,90,100]
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Experiments")
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = (0.0, -900.0)
balls = []
draw_options = pymunk.pygame_util.DrawOptions(screen)
ticks_to_next_ball = 10
while True:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit(0)
elif event.type == KEYDOWN and event.key == K_ESCAPE:
sys.exit(0)
'''if event.key == pg.K_a:
self.body.angular_velocity = 5.5
elif event.key == pg.K_w:
self.body.apply_impulse_at_local_point(Vec2d(0, 900))'''
ticks_to_next_ball -= 1
if ticks_to_next_ball <= 0:
ticks_to_next_ball = 25
ball_shape = add_circular_weight(space, mass=choice(masses), x = random.randint(120,380))
balls.append(ball_shape)
screen.fill((255,255,255))
balls_to_remove = []
for ball in balls:
if ball.body.position.y < 150:
balls_to_remove.append(ball)
for ball in balls_to_remove:
space.remove(ball, ball.body)
balls.remove(ball)
space.debug_draw(draw_options)
space.step(1/50.0)
pygame.display.flip()
clock.tick(5)
if __name__ == '__main__':
main()
The problem is most likely a change that was made between pymunk 5.7 and 6.0. In the old version pymunk treated the direction of y opposite to how pygame does it when drawing a space with pymunk_utils. To simplify things this was changed, so that both Pymunk and Pygame do the same thing.
To get the old behavior back you can set positive_y_is_up in pygame_util to True: pymunk.pygame_util.positive_y_is_up = True. However, if you dont have a lot of old code I suggest instead to modify the positions and gravity. This will make it simpler to reason about if you need to draw anything using pygame directly. (To convert you do converted_y_value = surface.get_height() - old_y_value)
Converted your code would then look like:
import random
import sys
from random import choice, seed
import pygame
import pymunk
import pymunk.pygame_util
from pygame.locals import *
def add_circular_weight(space,mass=1,x=150,y=150):
radius = 5 + mass/10
inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
body = pymunk.Body(mass, inertia)
body.position = x, y
shape = pymunk.Circle(body, radius, (0,0))
space.add(body, shape)
return shape
def main():
masses=[10,20,30,40,50,60,70,80,90,100]
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Experiments")
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = (0.0, 900.0)
#print(pymunk.pygame_util.positive_y_is_up)
#print(pymunk.version)
balls = []
draw_options = pymunk.pygame_util.DrawOptions(screen)
ticks_to_next_ball = 10
while True:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit(0)
elif event.type == KEYDOWN and event.key == K_ESCAPE:
sys.exit(0)
'''if event.key == pg.K_a:
self.body.angular_velocity = 5.5
elif event.key == pg.K_w:
self.body.apply_impulse_at_local_point(Vec2d(0, -900))'''
ticks_to_next_ball -= 1
if ticks_to_next_ball <= 0:
ticks_to_next_ball = 25
ball_shape = add_circular_weight(space, mass=choice(masses), x = random.randint(120,380))
balls.append(ball_shape)
screen.fill((255,255,255))
balls_to_remove = []
for ball in balls:
if ball.body.position.y > 550:
balls_to_remove.append(ball)
for ball in balls_to_remove:
space.remove(ball, ball.body)
balls.remove(ball)
space.debug_draw(draw_options)
space.step(1/50.0)
pygame.display.flip()
clock.tick(5)
if __name__ == '__main__':
main()
I'm attempting to detect a collision between two objects (both images).
I have rect's for both the objects, but cannot get the collision to be detected.
I've attempted to use rect's, comparing x and y positions.
import sys
import pygame
from pygame.locals import *
#init pygame
pygame.init()
window_width = 840
window_height = 650
size = (window_width, window_height)
screen = pygame.display.set_mode(size)
bg_img = pygame.image.load("img.png").convert_alpha()
crash = False
bot1x = 150
bot1y = 100
x = (window_width * 0.45)
y = (450)
clock = pygame.time.Clock()
fr = clock.tick(30)
x_speed = 0
car_img = pygame.image.load("car.png").convert_alpha()
car_img = pygame.transform.scale(car_img, (125, 175))
bot_img = pygame.image.load("bot.png").convert_alpha()
bot_img = pygame.transform.scale(bot_img, (175, 200))
def car(x, y):
screen.blit(car_img, (x,y))
def bot(x, y):
screen.blit(bot_img, (x,y))
car_rect = pygame.Rect(x, y, 125, 175)
bot_rect = pygame.Rect(bot1x, bot1y, 175, 200)
#game loop
while(crash==False):
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == K_LEFT:
x_speed = -.9
print("Moving Left")
if event.key == K_RIGHT:
x_speed = .9
print("Moving Right")
if event.type == pygame.KEYUP:
if event.key == K_LEFT:
x_speed = 0
if event.key == K_RIGHT:
x_speed= 0
if event.type == pygame.QUIT:
crash = True
bot1y += .5
# Move car
x += x_speed
if car_rect.colliderect(bot_rect):
print("collision deceted")
#blit images to screen
screen.blit(bg_img, [0, 0])
#spawn car
car(x,y)
bot(bot1x, bot1y)
#flip game display
pygame.display.flip()
#end game
pygame.quit()
quit()
I expect the game to output "Collision detected" when the two objects touch eachother.
Thanks,
Lachlan
i'm learning how to make a snake game in pygame from the thenewboston tutorials on youtube and make it my own.
There's a problem in the game where the 'apple' spawns behind the snake's position which is something that i don't want.
I'm sure i have to state that apple position can't be the same as the snake but can't figure it out how:
# Snake eating the apple
if snake_x_pos == apple_x and snake_y_pos == apple_y:
pygame.mixer.Sound.play(eat_apple_sound)
snakelength += 1
apple_x = random.randrange(box_size, field_x, box_size)
apple_y = random.randrange(box_size, field_y, box_size)
Full code:
import pygame
import random
pygame.init()
# Game Title
pygame.display.set_caption("Original Snake")
# Game display 4:3
screen_w = 640
screen_h = 480
surface = pygame.display.set_mode((screen_w, screen_h))
bg_color = (170, 204, 102)
box_color = (43, 51, 26)
box_size = screen_h / 24
# PLAYING FIELD
field_x = screen_w - (box_size*2)
field_y = screen_h - (box_size*2)
# Frames per Second
clock = pygame.time.Clock()
FPS = 8
# Font settings
kongtext = "C:\Windows\Fonts\kongtext.ttf"
verysmall = pygame.font.Font(kongtext, 12)
small = pygame.font.Font(kongtext, 15)
medium = pygame.font.Font(kongtext, 30)
large = pygame.font.Font(kongtext, 60)
verylarge = pygame.font.Font(kongtext, 80)
# sound settings
game_over_sound = pygame.mixer.Sound("sounds/game_over.wav")
eat_apple_sound = pygame.mixer.Sound("sounds/eat_apple.wav")
def snake(box_size, snakelist):
for XnY in snakelist:
pygame.draw.rect(surface, box_color, [XnY[0], XnY[1], box_size, box_size])
# Text Object
def text_objects(text, color, size):
if size == "small":
text_surface = small.render(text, True, color)
elif size == "verysmall":
text_surface = verysmall.render(text, True, color)
elif size == "medium":
text_surface = medium.render(text, True, color)
elif size == "large":
text_surface = large.render(text, True, color)
elif size == "verylarge":
text_surface = large.render(text, True, color)
return text_surface, text_surface.get_rect()
# Start screen
def start_screen():
intro = True
while intro:
# IF USER CLICKS ON THE X
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# Start Game
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
intro = False
surface.fill(bg_color)
# SCREEN FIELD WITH BORDER 3
pygame.draw.rect(surface, box_color, [16, 16, screen_w-33, screen_h-33-box_size], 3)
# START SCREEN
message_to_screen("SNAKE", box_color, -25, size="verylarge")
message_to_screen("PRESS SPACE TO PLAY", box_color, 35, size="verysmall")
pygame.display.update()
clock.tick(15)
# Message to screen
def message_to_screen(msg, color, text_y_pos=0, size="small"):
text_surf, text_rect = text_objects(msg, color, size)
text_rect.center = (screen_w / 2), (screen_h / 2)+text_y_pos
surface.blit(text_surf, text_rect)
def score(score):
text = small.render("Score: "+str((score*10)-20), True, box_color)
surface.blit(text, [box_size, screen_h-box_size-7])
def game_loop():
direction = "right"
quit_game = False
game_over = False
# Box settings
box_color = (43, 51, 26)
# Defining snake position
snakelist = []
snakelength = 3
snake_x_pos = screen_w / 2
snake_y_pos= screen_h / 2
snake_x_chg = box_size
snake_y_chg = 0
# Randomizing the apple position
apple_x = random.randrange(box_size, field_x, box_size)
apple_y = random.randrange(box_size, field_y, box_size)
while not quit_game:
# Game Over
while game_over:
surface.fill(bg_color)
message_to_screen("GAME OVER", box_color, -10, size="large")
message_to_screen("PRESS SPACE TO PLAY AGAIN OR Q TO QUIT", box_color, 50, size="small")
# PLAYING FIELD
pygame.draw.rect(surface, box_color,
[16, 16, screen_w - 33, screen_h - 33 - box_size], 3)
# SCORE
score(snakelength - 1)
pygame.display.update()
# Closing Game Over screen with X
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit_game = True
game_over = False
# Closing Game Over screen with Q or Restart with space
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
quit_game = True
game_over = False
if event.key == pygame.K_SPACE:
direction = "right"
game_loop()
for event in pygame.event.get():
# Closing the game
if event.type == pygame.QUIT:
quit_game = True
game_over = False
# Controlling the snake
if event.type == pygame.KEYDOWN:
if (event.key == pygame.K_LEFT) and direction != "right":
snake_x_chg = -box_size
snake_y_chg = 0
direction = "left"
elif (event.key == pygame.K_RIGHT) and direction != "left":
snake_x_chg = box_size
snake_y_chg = 0
direction = "right"
elif (event.key == pygame.K_UP) and direction != "down":
snake_y_chg = -box_size
snake_x_chg = 0
direction = "up"
elif (event.key == pygame.K_DOWN) and direction != "up":
snake_y_chg = box_size
snake_x_chg = 0
direction = "down"
# Screen boundaries
if snake_x_pos > (field_x) or snake_x_pos < box_size or snake_y_pos > (field_y) or snake_y_pos < box_size:
pygame.mixer.Sound.play(game_over_sound)
game_over = True
# Snake new position
snake_x_pos += snake_x_chg
snake_y_pos += snake_y_chg
# Clear screen
surface.fill(bg_color)
# Draw and update
pygame.draw.rect(surface, box_color, [apple_x, apple_y, box_size, box_size])
snakehead = []
snakehead.append(snake_x_pos)
snakehead.append(snake_y_pos)
snakelist.append(snakehead)
if len(snakelist) > snakelength:
del snakelist[0]
for snaketail in snakelist[:-1]:
if snaketail == snakehead:
pygame.mixer.Sound.play(game_over_sound)
game_over = True
snake(box_size, snakelist)
# PLAYING FIELD
pygame.draw.rect(surface, box_color, [16, 16, screen_w-33, screen_h-33-box_size], 3)
# SCORE
score(snakelength-1)
pygame.display.update()
# Snake eating the apple
if snake_x_pos == apple_x and snake_y_pos == apple_y:
pygame.mixer.Sound.play(eat_apple_sound)
snakelength += 1
apple_x = random.randrange(box_size, field_x, box_size)
apple_y = random.randrange(box_size, field_y, box_size)
clock.tick(FPS)
pygame.quit()
quit()
start_screen()
game_loop()
Create a rectangle at the new random "apple" position:
apple_rect = pygame.Rect(apple_x, apple_y, box_size, box_size)
Check for each position (pos) in snakelist if the "apple" rectangle collides with the part of the snake by pygame.Rect.collidepoint:
apple_rect.collidepoint(*pos)
Use any() to check if the apple "collides" with any part of the snake:
any(apple_rect.collidepoint(*pos) for pos in snakelist)
If the apple "collides" with the snake the create a new random point and repeat the process:
# Snake eating the apple
if snake_x_pos == apple_x and snake_y_pos == apple_y:
pygame.mixer.Sound.play(eat_apple_sound)
snakelength += 1
while True:
apple_x, apple_y = (random.randrange(box_size, fs, box_size) for fs in (field_x, field_y))
apple_rect = pygame.Rect(apple_x, apple_y, box_size, box_size)
if not any(apple_rect.collidepoint(*pos) for pos in snakelist):
break
The snake seems to be a list of "boxes", held in the list snakelist. So to ensure the apple does not appear inside the parts of the snake, the generated random point needs to be created outside the snake's boxes.
A simple (but inefficient) way to do this is to keep generating random points, and testing them for collision.
collision = True
while collision == True:
# Randomizing the apple position
apple_x = random.randrange(box_size, field_x, box_size)
apple_y = random.randrange(box_size, field_y, box_size)
# Check apple not within snake
collision = False
for XnY in snakelist:
part = pygame.Rect( XnY[0], XnY[1], box_size, box_size )
# is the apple-point within the snake part?
if part.collidepoint( apple_x, apple_y ) == True:
collision = True
break # any collision is final, stop checking
It would be worth thinking of a more efficient way to move the point away from the snake instead of looping over-and-over. Picking a random point may simply pick the same point, or a near-by bad point again. Also the longer the snake gets, the worse this looping will be as there are less "non-snake" points on the screen. I guess there's some known safe-spots, like the space the snake just moved out of, but these are obviously not random.