Related
I've been trying to code the snake game with pyglet and I have everything worked out except for one problem, and that problem is that I can't figure out how to have the snake actually turn instead of just rotate 90 degrees. My current code makes the snake always move the direction the head is facing, however, when you turn the snake, it makes a full 90 degree turn instead of doing what the snake normally does in for example the google doodle snake game, and I can't figure out why. Does anyone have any ideas? (FYI I am pretty new to python, so my code may be very ugly.)
# Modules
import pyglet as pyg
import random
import math
from pyglet.window import key
from time import sleep
# Creating the window, sprites, and labels
window = pyg.window.Window(800,600)
snake_head_image = pyg.image.load('snake_head.png')
snake_head_image.anchor_x = 12
snake_head_image.anchor_y = 12
snake_body_image = pyg.image.load('snake_body.png')
snake_body_image.anchor_x = 12
snake_body_image.anchor_y = 12
snake_head = pyg.sprite.Sprite(snake_head_image,x=400,y=300)
snake_bodies = [pyg.sprite.Sprite(snake_body_image,x=snake_head.x,y=snake_head.y-25),pyg.sprite.Sprite(snake_body_image,x=snake_head.x,y=snake_head.y)]
apple = pyg.shapes.Circle(x=random.randint(10,790),y=random.randint(10,590),radius=10,color=(255,0,0))
keys = key.KeyStateHandler()
window.push_handlers(keys)
score = pyg.text.Label(f"Score: {len(snake_bodies)}",font_size=12,x=760,y=590,anchor_x='center',anchor_y='center')
end = pyg.text.Label("",font_size=36,x=400,y=300,anchor_x='center',anchor_y='center')
#window.event
def on_draw():
window.clear()
apple.draw()
snake_head.draw()
snake_bodies[0].draw()
for z in range(len(snake_bodies)):
snake_bodies[z].draw()
score.draw()
end.draw()
# Rotating the snake on key press
#window.event
def on_key_press(symbol,modifiers):
if (symbol == pyg.window.key.LEFT) and snake_head.rotation != 90:
snake_head.rotation = 270
snake_bodies[0].rotation = 270
snake_bodies[0].y = snake_head.y
snake_bodies[0].x = snake_head.x + 25
elif (symbol == pyg.window.key.RIGHT) and snake_head.rotation != 270:
snake_head.rotation = 90
snake_bodies[0].rotation = 90
snake_bodies[0].y = snake_head.y
snake_bodies[0].x = snake_head.x - 25
elif (symbol == pyg.window.key.UP) and snake_head.rotation != 180:
snake_head.rotation = 0
snake_bodies[0].rotation = 0
snake_bodies[0].y = snake_head.y - 25
snake_bodies[0].x = snake_head.x
elif (symbol == pyg.window.key.DOWN) and snake_head.rotation != 0:
snake_head.rotation = 180
snake_bodies[0].rotation = 180
snake_bodies[0].y = snake_head.y + 25
snake_bodies[0].x = snake_head.x
# Always making the snake move forward
#window.event
def moveSnake(dt):
if snake_head.rotation == 270:
snake_head.x -= 3
snake_bodies[0].x -= 3
for z in range(len(snake_bodies)):
if z != 0:
snake_bodies[z].x = snake_bodies[z-1].x + 25
snake_bodies[z].y = snake_bodies[z-1].y
snake_bodies[z].rotation = snake_bodies[0].rotation
else:
pass
elif snake_head.rotation == 90:
snake_head.x += 3
snake_bodies[0].x += 3
for z in range(len(snake_bodies)):
if z != 0:
snake_bodies[z].x = snake_bodies[z-1].x - 25
snake_bodies[z].y = snake_bodies[z-1].y
snake_bodies[z].rotation = snake_bodies[0].rotation
else:
pass
elif snake_head.rotation == 0:
snake_head.y += 3
snake_bodies[0].y += 3
for z in range(len(snake_bodies)):
if z != 0:
snake_bodies[z].x = snake_bodies[z-1].x
snake_bodies[z].y = snake_bodies[z-1].y - 25
snake_bodies[z].rotation = snake_bodies[0].rotation
else:
pass
elif snake_head.rotation == 180:
snake_head.y -= 3
snake_bodies[0].y -= 3
for z in range(len(snake_bodies)):
if z != 0:
snake_bodies[z].x = snake_bodies[z-1].x
snake_bodies[z].y = snake_bodies[z-1].y + 25
snake_bodies[z].rotation = snake_bodies[0].rotation
else:
pass
# Collisions with the apple and the borders
distance_x_apple = snake_head.x - apple.x
distance_y_apple = snake_head.y - apple.y
distance_x_upper_border = snake_head.x - window.width
distance_y_upper_border = snake_head.y - window.height
distance_x_lower_border = snake_head.x - 0
distance_y_lower_border = snake_head.y - 0
if 10 >= abs(distance_x_apple) and 10 >= abs(distance_y_apple):
apple.x = random.randint(10,790)
apple.y = random.randint(10,590)
if snake_head.rotation == 270:
snake_bodies.append(pyg.sprite.Sprite(snake_body_image,x=snake_bodies[len(snake_bodies)-1].x-25,y=snake_bodies[len(snake_bodies)-1].y))
snake_bodies[-1].rotation = 270
elif snake_head.rotation == 90:
snake_bodies.append(pyg.sprite.Sprite(snake_body_image,x=snake_bodies[len(snake_bodies)-1].x+25,y=snake_bodies[len(snake_bodies)-1].y))
snake_bodies[-1].rotation = 90
elif snake_head.rotation == 180:
snake_bodies.append(pyg.sprite.Sprite(snake_body_image,x=snake_bodies[len(snake_bodies)-1].x,y=snake_bodies[len(snake_bodies)-1].y-25))
snake_bodies[-1].rotation = 180
elif snake_head.rotation == 0:
snake_bodies.append(pyg.sprite.Sprite(snake_body_image,x=snake_bodies[len(snake_bodies)-1].x,y=snake_bodies[len(snake_bodies)-1].y+25))
snake_bodies[-1].rotation = 0
score.text = f"Score: {len(snake_bodies)}"
if 5 >= abs(distance_x_upper_border) or 5 >= abs(distance_y_upper_border) or 5 >= abs(distance_x_lower_border) or 5>= abs(distance_y_lower_border):
snake_head.visible = False
for z in range(len(snake_bodies)):
snake_bodies[z].visible = False
sleep(0.3)
end.text = f"Game Over!\nScore: {len(snake_bodies)}"
pyg.clock.schedule_interval(moveSnake, 1 / 60)
pyg.app.run()
You have probably already done it, except because everything doesn't move in cells, and there is no time between snake's head and snake's body updating and moving, it is turning itself so fast that you cannot see it. You can either keep it without cells and make time intervals in between turning, or create cells and it makes everything much easier.
Either that your you have your turning done wrong. I am not sure if your code is doing this already, but if you keep it without cells you need to 1. save position of head on turning and 2. iterate through the body pieces and make the the last one follow the next piece.
I will probably try to fix the code and post it here later.
(I cannot comment because I have less than 50 reputation, even though this should have been a comment. Hope this helped in some way)
I have been tasked with producing a simple program that simulates the actions of a vehicle from a list of command stored & accessed within a text file.
The expected output would look something like this;
Loading simulation...
Accelerating...
Speed = 5, Gear = 1, Direction = 0
Accelerating...
Speed = 10, Gear = 1, Direction = 0
Accelerating...
Changing up...
Current gear = 2
Speed = 15, Gear = 2, Direction = 0
Accelerating...
Speed = 20, Gear = 2, Direction = 0
Accelerating...
Yet my output looks like this;
Loading Simulation....
Car Gear is First (1)
Accelerating...
Car Gear is First (1)
Speed = 5, Gear = 1, Direction = [-1, 0, 1]
Braking...
Car Gear is First (1)
Speed = 0, Gear = 1, Direction = [-1, 0, 1]
Car Gear is First (1)
Accelerating...
Car Gear is First (1)
Speed = 5, Gear = 1, Direction = [-1, 0, 1]
Braking...
Car Gear is First (1)
Speed = 0, Gear = 1, Direction = [-1, 0, 1]
Car Gear is First (1)
When I run my code in Idle, i get no errors of any kind, when i use Thonny, it gives me the folloing error when it analyses the code:
Line 113 : Either all return statements in a function should return an expression, or none of them should.
Line 113 : Unused argument 'selected_gear'
Below is a copy of my code also:
RIGHT = 1
LEFT = -1
FORWARD = 1
REVERSE = 0
STRAIGHT = 0
#gears and allowable speeds are as follows:
#zero (reverse) (speed -1 to -10). Max reverse speed of car is -10
#one (speed 0 to 10)
#two (speed 10 to 20)
#three (speed 20 to 30)
#four (speed 30 to 45)
#five (speed 45 to 80). Max speed of car is 80
#gears change automatically, one gear at a time
#direction values are similar to numbers on clock face
#0 = 12 = straight on. All other directions = 1-11
class Car:
def __init__(self):
self.speed = 0
self.gear = [0,1,2,3,4,5]
self.direction = [-1,0,1]
self.broken = False #indicates whether car is broken
self.simulation = []
self.simulation_loaded = False
def accelerate(self):
if self.broken:
print("Car is broken!")
return
print("Accelerating...")
if self.gear == REVERSE:
self.speed -= 5
else:
self.speed += 5
if self.speed > 80:
self.speed = 80
if self.speed < -10:
self.speed = -10
self.change_gear(self.gear)
self.display_stats()
def brake(self):
if self.broken:
print("Car is broken...")
return
print("Braking...")
if self.speed < 0:
self.speed += 5
if self.speed > 0:
self.speed = 0
elif self.speed > 0:
self.speed -= 5
if self.speed < 0:
self.speed = 0
self.change_gear(self.gear)
self.display_stats()
def turn_steering_wheel(self, direction_change):
if self.broken:
print("Car is broken...")
return
if self.gear == REVERSE:
print ("Car is in reverse...")
if direction_change == RIGHT:
self.direction = -1
print("Reversing Right")
elif direction_change == REVERSE:
self.direction = 12
print("Reversing")
elif direction_change == LEFT:
self.direction = 1
print("Reversing Left")
elif self.gear == FORWARD:
if direction_change == LEFT:
self.direction = -1
print("Turning Left")
elif direction_change == STRAIGHT:
self.direction = 0
print("Moving Forward")
elif direction_change == RIGHT:
self.direction = 1
print("Turning Right")
self.display_stats()
def change_gear(self, selected_gear = FORWARD):
if self.broken:
print("Car is broken...")
return self.broken
# if self.gear == 0 and self.speed >= 0:
# print("you are going forward while in reverse gear...")
# return self.broken
# elif self.gear >= 1 and self.speed <= -1:
# print("you are going reverse while in foward gear...")
# return self.broken
if self.speed <= -1:
self.gear = 0
print("Car Gear is Neutral (0)")
return self.gear
elif self.speed <= 10:
self.gear = 1
print("Car Gear is First (1)")
return
elif self.speed <= 20:
self.gear = 2
print("Car Gear is Second (2)")
return
elif self.speed <= 30:
self.gear = 3
print("Car Gear is Third (3)")
return
elif self.speed <= 40:
self.gear = 4
print("Car Gear is Fourth (4)")
return
elif self.speed <= 50:
self.gear = 5
print("Car Gear is Fifth (5)")
return
self.display_stats()
self.change_gear(self.gear)
#check to see if car is going forward when reverse is selected and vice versa
#work out what gear you need to be in based on car’s speed
#Loop one gear at a time, either changing up or down, to get to required gear
print("Changing up...")
def display_stats(self):
print(f"Speed = {self.speed}, Gear = {self.gear}, Direction = {self.direction}")
def load_simulation(self, filename):
file = open(filename)
line = file.readline()
while line !='':
self.simulation.append(line.strip())
line = file.readline()
file.close()
self.simulation_loaded = True
return self.simulation
def run_simulation(self):
if self.simulation_loaded == False:
print("Error - Simulation.txt file is present")
else:
print("Loading Simulation....")
for action in self.simulation:
if action == "FORWARD":
self.change_gear (FORWARD)
elif action == "ACCELERATE":
self.accelerate()
elif action == "LEFT":
#self.direction(LEFT)
print("TURNING LEFT")
elif action == "RIGHT":
#self.direction(RIGHT)
print("TURNING RIGHT")
'''***WHEN USING SELF.DIRECTION(LEFT)/(RIGHT) the following error is given:
Traceback (most recent call last):
File "C:\Users\lenovo\Desktop\Bsc Computer Forensics - Laptop\Software-Dev\car-temp-2.py", line 207, in <module>
my_car.run_simulation()
File "C:\Users\lenovo\Desktop\Bsc Computer Forensics - Laptop\Software-Dev\car-temp-2.py", line 183, in run_simulation
self.direction(LEFT)
TypeError: 'list' object is not callable*** '''
elif action == "BRAKE":
self.brake()
else:
self.change_gear (REVERSE)
if __name__ == '__main__':
my_car = Car()
my_car.load_simulation("simulation.txt")
my_car.run_simulation()
Would I please be able to ask if anyone could explain what the errors im getting mean and where in my code I need to be looking, its got me feeling a little lost now - I've tried to research them, fix them but anyhting I try either seems to have no impact or gives errors that otherwise don't exist.
The errors occur in your change_gear() function, although they are more like warnings and should not pose serious problems:
Line 113 : Either all return statements in a function should return an expression, or none of them should.
The first two return statements return a value while the others don't. This is inconsistent and makes the code harder to understand. Since you don't actually use the returned values anywhere, you can remove them (i.e. use plain return).
Line 113 : Unused argument 'selected_gear'
You don't use the selected_gear argument anywhere inside change_gear(). You can remove it to get rid of the warning/error.
I have been learning Pygame for 3 days. Now I want to save and load the high score and display it.
This has been a very difficult task to achieve in other engines I've used - saving and loading, that is - but with sheer determination, I have managed to do it on one of my main engines.
Any help will do.
The code:
import pygame, sys, random
def draw_floor():
screen.blit(floor_surface,(floor_x_pos,900))
screen.blit(floor_surface,(floor_x_pos + 576,900))
def create_pipe():
random_pipe_pos = random.choice(pipe_height)
bottom_pipe = pipe_surface.get_rect(midtop = (584,random_pipe_pos))
top_pipe = pipe_surface.get_rect(midbottom = (584,random_pipe_pos -300))
return bottom_pipe,top_pipe
def move_pipes(pipes):
for pipe in pipes:
pipe.centerx -= 5
visible_pipes = [pipe for pipe in pipes if pipe.right > -50]
return visible_pipes
def draw_pipes(pipes):
for pipe in pipes:
if pipe.bottom >= 1024:
screen.blit(pipe_surface,pipe)
else:
flip_pipe = pygame.transform.flip(pipe_surface,False,True)
screen.blit(flip_pipe,pipe)
def check_collision(pipes):
global can_score
for pipe in pipes:
if bird_rect.colliderect(pipe):
hit_sound.play()
can_score = True
return False
if bird_rect.top <= -100 or bird_rect.bottom >= 900:
can_score = True
hit_sound.play()
return False
return True
def rotate_bird(bird):
new_bird = pygame.transform.rotozoom(bird,-bird_movement * 3,1)
return new_bird
def bird_animation():
new_bird = bird_frames[bird_index]
new_bird_rect = new_bird.get_rect(center = (100,bird_rect.centery))
return new_bird,new_bird_rect
def score_display(game_state):
if game_state == 'main_game':
score_surface = game_font.render(str(int(score)),True,(255,255,255))
score_rect = score_surface.get_rect(center = (288,100))
screen.blit(score_surface,score_rect)
if game_state == 'game_over':
score_surface = game_font.render(f'Score: {int(score)}',True,(255,255,255))
score_rect = score_surface.get_rect(center = (288,100))
screen.blit(score_surface,score_rect)
high_score_surface = game_font.render(f'High score: {int(high_score)}',True,(255,255,255))
high_score_rect = high_score_surface.get_rect(center = (288,850))
screen.blit(high_score_surface,high_score_rect)
def update_score(score,high_score):
if score > high_score:
high_score = score
return high_score
def pipe_score_check():
global score, can_score
if pipe_list:
for pipe in pipe_list:
if 95 < pipe.centerx < 105 and can_score:
score += 1
point_sound.play()
can_score = False
if pipe.centerx < 0:
can_score = True
# pygame.mixer.pre_init(frequency = 44100, size = 16, channels = 1, buffer = 512)
pygame.init()
screen = pygame.display.set_mode((576,1024))
clock = pygame.time.Clock()
game_font = pygame.font.Font('04B_19.ttf',40)
# Variables
gravity = 0.25
bird_movement = 0
game_active = True
score = 0
high_score = 0
can_score = True
# Loading images
bg_surface = pygame.image.load('assets/background-day.png').convert()
bg_surface = pygame.transform.scale2x(bg_surface)
floor_surface = pygame.image.load('assets/base.png').convert()
floor_surface = pygame.transform.scale2x(floor_surface)
floor_x_pos = 0
bird_downflap = pygame.transform.scale2x(pygame.image.load('assets/yellowbird-downflap.png').convert_alpha())
bird_midflap = pygame.transform.scale2x(pygame.image.load('assets/yellowbird-midflap.png').convert_alpha())
bird_upflap = pygame.transform.scale2x(pygame.image.load('assets/yellowbird-upflap.png').convert_alpha())
bird_frames = [bird_downflap,bird_midflap,bird_upflap]
bird_index = 0
bird_surface = bird_frames[bird_index]
bird_rect = bird_surface.get_rect(center = (100,512))
BIRDFLAP = pygame.USEREVENT + 1
pygame.time.set_timer(BIRDFLAP,200)
# bird_surface = pygame.image.load('assets/yellowbird-midflap.png').convert_alpha()
# bird_surface = pygame.transform.scale2x(bird_surface)
# bird_rect = bird_surface.get_rect(center = (100,512))
pipe_surface = pygame.image.load('assets/pipe-green.png').convert()
pipe_surface = pygame.transform.scale2x(pipe_surface)
pipe_list = []
SPAWNPIPE = pygame.USEREVENT
pygame.time.set_timer(SPAWNPIPE,1500)
pipe_height = [400,600,800]
game_over_surface = pygame.transform.scale2x(pygame.image.load('assets/message.png').convert_alpha())
game_over_rect = game_over_surface.get_rect(center = (288,512))
flap_sound = pygame.mixer.Sound('sound/sfx_wing.wav')
hit_sound = pygame.mixer.Sound('sound/sfx_hit.wav')
point_sound = pygame.mixer.Sound('sound/sfx_point.wav')
countdown_sound_score = 100
# Event loop
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and game_active:
bird_movement = 0
bird_movement -= 12
flap_sound.play()
if event.key == pygame.K_SPACE and game_active == False:
game_active = True
pipe_list.clear()
bird_rect.center = (100,512)
bird_movement = 0
score = 0
if event.type == SPAWNPIPE:
pipe_list.extend(create_pipe())
if event.type == BIRDFLAP:
if bird_index < 2:
bird_index += 1
else:
bird_index = 0
bird_surface,bird_rect = bird_animation()
# Pasting background image
screen.blit(bg_surface,(0,0))
if game_active:
# Bird movement and pasting bird
bird_movement += gravity
rotated_bird = rotate_bird(bird_surface)
bird_rect.centery += bird_movement
screen.blit(rotated_bird,bird_rect)
game_active = check_collision(pipe_list)
# Pipes
pipe_list = move_pipes(pipe_list)
draw_pipes(pipe_list)
# Score
pipe_score_check()
score_display('main_game')
else:
screen.blit(game_over_surface,game_over_rect)
high_score = update_score(score,high_score)
score_display('game_over')
# Floor movement and pasting floor image
floor_x_pos -= 1
draw_floor()
if floor_x_pos <=-576:
floor_x_pos = 0
pygame.display.update()
clock.tick(120)
This will help me greatly.
Thanks,
Josh
The easiest way would be to have a file with your score. You will write the score to it before closing the game, or whenever you want to save the score. and read the score when you open the game.
Here is an example of the read and write functions to achive this:
def write_score(score):
with open("highscore.txt", 'w') as file:
file.write(str(score))
def read_score():
with open("highscore.txt", 'r') as file:
return int(file.read())
(Haven't tested the code but I'm pretty sure that this will work, if not, I can fix it)
I just currently finished making the game 'snake' as a practice to learn how to program, as I am new to programming for about 3 months.
Although the game is completed and runs the way I intended, I want to try to simplify my code and reduce the amount of lines as much as possible, and possibly make the script tidier as the current majority of my codes are cluster in the while loop.
Until now I haven't touched upon class objects, and I want everything in the while loop to go into individual classes that get called out from the while loop to reduce the amount of lines in it.
off-topic: by reading through the script, how else can I improve it to be run more efficiently, including simplifying some code as I may have over-complicated it?
I looked up how class object is used from w3school and other programming tutorials, but I still don't fully understand it as it only shows examples in using print. I did play around and experimented with class object examples and attempted to call them without using print, but I lack the knowledge of how to use them properly.
from graphics import *
from threading import Timer
import keyboard, random, time
# configurations
width = 400
gridHeight = width
height = 470
timer = False
game = True
score = 0
bonus = 0
x = 70
y = 30
radius = 10
length = radius * 2
playerLength = 3
poisonLength = playerLength
i = 0
k = 0
pointRadius = 5
points = False
cherryPoints = False
key = "Right"
countDown = 0
# set coordinations
cX = 90
cY = 30
coordX = [10]
coordY = [10]
while coordX[len(coordX)-1] != width-10:
cX+=20
coordX.append(cX)
while coordY[len(coordY)-1] != 390:
cY+=20
coordY.append(cY)
randomX = random.choice(coordX)
randomY = random.choice(coordY)
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
# window set up
win = GraphWin("SNAKE", width, height, autoflush = False)
win.setBackground(color_rgb(15,15,15))
# grid
lineX = 20
while lineX < width:
gridX = Line(Point(lineX,0),Point(lineX,gridHeight))
gridX.setOutline(color_rgb(25,25,25))
gridX.draw(win)
lineX += 20
lineY = 20
while lineY <= gridHeight:
gridX = Line(Point(0,lineY),Point(width,lineY))
gridX.setOutline(color_rgb(25,25,25))
gridX.draw(win)
lineY += 20
# snake banner
UI = Rectangle(Point(0,400),Point(width,height))
UI.setFill(color_rgb(102,51,0))
UI.setOutline(color_rgb(102,51,0))
UI.draw(win)
snakeTitle = Text(Point(width/2,420),"SNAKE")
snakeTitle.setTextColor("green")
snakeTitle.setSize(20)
snakeTitle.draw(win)
scoreTitle = Text(Point(320,424),"SCORE")
scoreTitle.setTextColor("white")
scoreTitle.setSize(10)
scoreTitle.draw(win)
scoreUI = Text(Point(320,435),score)
scoreUI.setTextColor("white")
scoreUI.setSize(10)
scoreUI.draw(win)
# make player
player = {}
player[0] = Rectangle(Point(x-20-radius,y-radius), Point(x-20+radius, y+radius))
player[1] = Rectangle(Point(x-40-radius,y-radius), Point(x-40+radius, y+radius))
player[2] = Rectangle(Point(x-60-radius,y-radius), Point(x-60+radius, y+radius))
# make poison
poison = {}
def main():
global timer, scoreUI, score, bonus, playerLength, poisonLength, x, y, points, cherryPoints, randomX, randomY, cherryRandomX, cherryRandomY, poisonRandomX, poisonRandomY, key, countDown, k, game
while(game==True):
# score update
scoreUI.undraw()
scoreUI = Text(Point(320,435),score)
scoreUI.setTextColor("white")
scoreUI.setSize(10)
scoreUI.draw(win)
# generating new body blocks
if len(player) < playerLength:
i+=1
player[i] = player[i-1].clone()
# body following player
player[0].undraw()
for i in range(1,len(player)):
player[len(player)-i].undraw()
player[len(player)-i] = player[len(player)-i-1].clone()
player[len(player)-i].draw(win)
# update player's head coordinate
player[0] = Rectangle(Point(x-radius,y-radius), Point(x+radius,y+radius))
player[0].setFill("green")
player[0].setWidth(2)
player[0].draw(win)
# player movement
if keyboard.is_pressed("Up") and key != "Down":
key = "Up"
elif keyboard.is_pressed("Left") and key != "Right":
key = "Left"
elif keyboard.is_pressed("Down") and key != "Up":
key = "Down"
elif keyboard.is_pressed("Right") and key != "Left":
key = "Right"
if key == "Up":
y -= length
elif key == "Left":
x -= length
elif key == "Down":
y += length
elif key == "Right":
x += length
# point
if points == False: # generates new point when eaten
point = Rectangle(Point(randomX-pointRadius,randomY-pointRadius),Point(randomX+pointRadius,randomY+pointRadius))
point.setFill("white")
point.setWidth(2)
point.draw(win)
points = True
if player[0].getCenter().getX() == point.getCenter().getX() and player[0].getCenter().getY() == point.getCenter().getY(): # when player eats the point
point.undraw()
playerLength += 1
poisonLength += 1
score += 200+bonus
randomX = random.choice(coordX)
randomY = random.choice(coordY)
for i in range(len(player)):
if (point.getCenter().getX() == player[i].getCenter().getX() and point.getCenter().getY() == player[i].getCenter().getY()) or (cherryPoints == True and cherryPoint.getCenter().getX() == point.getCenter().getX() and cherryPoint.getCenter().getY() == point.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and cherry
randomX = random.choice(coordX)
randomY = random.choice(coordY)
for i in range(len(poison)): # regenerate x and y coordinate if point shares the same coordinate to other array of poisons
if point.getCenter().getX() == poison[i].getCenter().getX() and point.getCenter().getY() == poison[i].getCenter().getY():
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
points = False
# cherry
if countDown == 150:
countDown = 0
if cherryPoints == False: # generates new cherry from countdown
cherryPoint = Rectangle(Point(cherryRandomX-pointRadius,cherryRandomY-pointRadius),Point(cherryRandomX+pointRadius,cherryRandomY+pointRadius))
cherryPoint.setFill(color_rgb(213,0,50))
cherryPoint.setWidth(2)
cherryPoint.draw(win)
cherryPoints = True
if cherryPoints == True:
for i in range(2, 6): # cherry blinks between countdown 40 to 100
if countDown == 20*i:
cherryPoint.undraw()
elif countDown == 10+(20*i):
cherryPoint.draw(win)
if countDown >= 100: # when countdown becomes 100, remove cherry and reset count down
cherryPoints = False
countDown = 0
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
if cherryPoints==True and player[0].getCenter().getX() == cherryPoint.getCenter().getX() and player[0].getCenter().getY() == cherryPoint.getCenter().getY(): # when player eats the cherry
cherryPoint.undraw()
score += 500
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
for i in range(len(player)):
if (cherryPoint.getCenter().getX() == player[i].getCenter().getX() and cherryPoint.getCenter().getY() == player[i].getCenter().getY()) or (cherryPoint.getCenter().getX() == point.getCenter().getX() and cherryPoint.getCenter().getY() == point.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and point
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
for i in range(len(poison)): # regenerate x and y coordinate if cherry shares the same coordinate to other array of poisons
if cherryPoint.getCenter().getX() == poison[i].getCenter().getX() and cherryPoint.getCenter().getY() == poison[i].getCenter().getY():
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
cherryPoints = False
# poison
if poisonLength % 5 == 0: # generates a poison block each time the player size reaches the multiple of 5
poison[k] = Rectangle(Point(poisonRandomX-pointRadius,poisonRandomY-pointRadius),Point(poisonRandomX+pointRadius,poisonRandomY+pointRadius))
poison[k].setFill("green")
poison[k].setWidth(2)
poison[k].draw(win)
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
for i in range(len(player)):
if (poison[k].getCenter().getX() == player[i].getCenter().getX() and poison[k].getCenter().getY() == player[i].getCenter().getY()) or (poison[k].getCenter().getX() == point.getCenter().getX() and poison[k].getCenter().getY() == point.getCenter().getY()) or (cherryPoints==True and poison[k].getCenter().getX() == cherryPoint.getCenter().getX() and poison[k].getCenter().getY() == cherryPoint.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and point and cherry
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
for i in range(len(poison)):
if poison[k].getCenter().getX() == poison[i].getCenter().getX() and poison[k].getCenter().getY() == poison[i].getCenter().getY(): # regenerate x and y coordinate if new poison shares the same coordinate to other array of poisons
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
bonus+=50
k+=1
poisonLength+=1
# game over requirements
for i in range(len(poison)): # if player touches poison
if player[0].getCenter().getX() == poison[i].getCenter().getX() and player[0].getCenter().getY() == poison[i].getCenter().getY():
game = False
for i in range(2, len(player)): # if player touches its own body or reach out of window
if (player[0].getCenter().getX() == player[i].getCenter().getX() and player[0].getCenter().getY() == player[i].getCenter().getY()) or x < 0 or x > width or y < 0 or y > gridHeight:
game = False
# FPS
update(10)
countDown += 1
# GAME OVER
gameOver = Text(Point(width/2,200), "GAME OVER")
gameOver.setTextColor("red")
gameOver.setSize(30)
gameOver.draw(win)
update()
time.sleep(2)
win.close()
main()
Ideally the result should replace each code in the while loop with individual classes outside of the function to reduce the amount of lines in the main() function and make the script easier to read.
Classes are essentially just bundles of code that contain various attributes and methods.
A Snake class might have a list of coordinates for each section of the body (the first is the head).
class Snake:
def __init__(self, x, y):
self.positions = [(x, y)]
def get_head(self):
return self.positions[0]
def move_forward(self):
self.positions.pop()
self.positions.insert(0, self.get_head()[1] + 1)
def move_backward(self):
self.positions.pop()
self.positions.insert(0, self.get_head()[1] - 1)
...
And so on. Classes, at this level, let you think of objects as concrete entities, distinct from each other but easily manipulated.
I'm looking to get some help with a match-3 game I'm working on in pygame. I've loaded the images and sounds as follows and the 'gems' will be the classical elements air, earth, fire and water. How can I play the appropriate water sound file, for example, when 3 or more water sprites are matched? Don't need help with any of the game code just to create an association between the image and audio files and how to play it 'while matchedElements != []'. Thank you.
# Directions
UP = 'up'
DOWN = 'down'
LEFT = 'left'
RIGHT = 'right'
# Space to the sides of grid
XMARGIN = int((WIDTH - ELEMENTSIZE * GRIDWIDTH) / 2)
YMARGIN = int((HEIGHT - ELEMENTSIZE * GRIDHEIGHT) / 2)
EMPTY_SPACE = -1
ROWABOVEBOARD = 'row above board'
# Colours
AIR = pygame.Color(145, 129, 129)
FIRE = pygame.Color(255, 123, 0)
WATER = pygame.Color(93, 118, 245)
EARTH = pygame.Color(22, 136, 0)
ELECTRIC = pygame.Color(22, 204, 0)
SMOKE = pygame.Color(222, 222, 222)
ICE = pygame.Color(234, 231, 255)
METAL = pygame.Color(105, 105, 105)
BLOOD = pygame.Color(222, 7, 7)
# FPS controller
fpsController = pygame.time.Clock()
def main():
global FPSCLOCK, BOARDRECTS, ELEMENTIMAGES, SOUNDS, PLAYSURF, BASICFONT
# Basic set up
pygame.init()
FPSCLOCK = pygame.time.Clock()
PLAYSURF = pygame.display.set_mode((WIDTH, HEIGHT))
BASICFONT = pygame.font.Font('freesansbold.ttf', 36)
# Load images
ELEMENTIMAGES = []
for i in range(1, NUMELEMENTS+1):
elementImage = pygame.image.load('element%s.jpg' % i)
if elementImage.get_size() != (ELEMENTSIZE, ELEMENTSIZE):
elementImage = pygame.transform.smoothscale(elementImage, (ELEMENTSIZE, ELEMENTSIZE))
ELEMENTIMAGES.append(elementImage)
# Load sounds
SOUNDS = {}
SOUNDS['bad swap'] = pygame.mixer.Sound('badswap.wav')
SOUNDS['match'] = []
for i in range(NUMMATCHSOUNDS):
SOUNDS['match'].append(pygame.mixer.Sound('elementsound%s.wav' % i))
# Rect objects for board space conversions
BOARDRECTS = []
for x in range(GRIDWIDTH):
BOARDRECTS.append([])
for y in range(GRIDHEIGHT):
r = pygame.Rect((XMARGIN + (x * ELEMENTSIZE),
YMARGIN + (y * ELEMENTSIZE),
ELEMENTSIZE, ELEMENTSIZE))
BOARDRECTS[x].append(r)
while True:
runGame()
def runGame():
# Board initialisation
gameBoard = getBlankBoard()
score = 0
fillBoardAndAnimate(gameBoard, [], score) # Drop initial elements
# Initialise new game variables
firstSelectedElement = None
lastMouseDownX = None
lastMouseDownY = None
gameIsOver = False
lastScoreDeduction = time.time()
clickContinueTextSurf = None
# Main game loop
while True:
clickedSpace = None
for event in pygame.event.get(): # Event handling
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
elif event.type == KEYUP and event.key == K_BACKSPACE:
return # new game
elif event.type == MOUSEBUTTONUP:
if gameIsOver:
return # click to start new game
if event.pos == (lastMouseDownX, lastMouseDownY):
clickedSpace = checkForElementClick(event.pos)
else:
firstSelectedElement = checkForElementClick((lastMouseDownX, lastMouseDownY))
clickedSpace = checkForElementClick(event.pos)
if not firstSelectedElement or not clickedSpace:
firstSelectedElement = None
clickedSpace = None
elif event.type == MOUSEBUTTONDOWN:
lastMouseDownX, lastMouseDownY = event.pos
if clickedSpace and not firstSelectedElement:
firstSelectedElement = clickedSpace
elif clickedSpace and firstSelectedElement:
firstSwappingElement, secondSwappingElement = getSwappingElements(gameBoard, firstSelectedElement, clickedSpace)
if firstSwappingElement == None and secondSwappingElement == None:
# If both are None, elements are not adjacent
firstSelectedElement = None
continue
# Swap animation
boardCopy = getBoardCopyMinusElements(gameBoard, (firstSwappingElement, secondSwappingElement))
animateMovingElements(boardCopy, [firstSwappingElement, secondSwappingElement], [], score)
# Swap elements in the board
gameBoard[firstSwappingElement['x']][firstSwappingElement['y']] = secondSwappingElement['imageNum']
gameBoard[secondSwappingElement['x']][secondSwappingElement['y']] = firstSwappingElement['imageNum']
# See if this is a match
matchedElements = findMatchingElements(gameBoard)
if matchedElements == []:
# No match - swap back
SOUNDS['bad swap'].play()
animateMovingElements(boardCopy, [firstSwappingElement, secondSwappingElement], [], score)
gameBoard[firstSwappingElement['x']][firstSwappingElement['y']] = firstSwappingElement['imageNum']
gameBoard[secondSwappingElement['x']][secondSwappingElement['y']] = secondSwappingElement['imageNum']
else:
# A match
scoreAdd = 0
while matchedElements != []:
points = []
for elementSet in matchedElements:
scoreAdd += (10 + (len(elementSet) - 3) * 10)
for element in elementSet:
gameBoard[element[0]][element[1]] = EMPTY_SPACE
points.append({'points': scoreAdd,
'x': element[0] * ELEMENTSIZE + XMARGIN,
'y': element[1] * ELEMENTSIZE + YMARGIN})
score += scoreAdd
# Drop new elements
fillBoardAndAnimate(gameBoard, points, score)
# Check for new matches
matchedElements = findMatchingElements(gameBoard)
firstSelectedElement = None
if not canMakeMove(gameBoard):
gameIsOver = True
# Draw the board
PLAYSURF.fill(BGCOLOUR)
drawBoard(gameBoard)
if firstSelectedElement != None:
highlightSpace(firstSelectedElement['x'], firstSelectedElement['y'])
if gameIsOver:
if clickContinueTextSurf == None:
clickContinueTextSurf = BASICFONT.render('Final Score: %s (Click to continue)' % (score), 1, GAMEOVERCOLOUR, GAMEOVERBGCOLOUR)
clickContinueTextRect = clickContinueTextSurf.get_rect()
clickContinueTextRect.center = int(WIDTH / 2), int(HEIGHT / 2)
PLAYSURF.blit(clickContinueTextSurf, clickContinueTextRect)
elif score > 0 and time.time() - lastScoreDeduction > DEDUCTSPEED:
# score drops over time
score -= 1
lastScoreDeduction = time.time()
drawScore(score)
pygame.display.update()
FPSCLOCK.tick(FPS)
def getSwappingElements(board, firstXY, secondXY):
# If the elements at the (X, Y) coordinates of the two elements are adjacent,
# then their 'direction' keys are set to the appropriate direction
# value to be swapped with each other.
# Otherwise, (None, None) is returned.
firstElement = {'imageNum': board[firstXY['x']][firstXY['y']],
'x': firstXY['x'],
'y': firstXY['y']}
secondElement = {'imageNum': board[secondXY['x']][secondXY['y']],
'x': secondXY['x'],
'y': secondXY['y']}
highlightedElement = None
if firstElement['x'] == secondElement['x'] + 1 and firstElement['y'] == secondElement['y']:
firstElement['direction'] = LEFT
secondElement['direction'] = RIGHT
elif firstElement['x'] == secondElement['x'] - 1 and firstElement['y'] == secondElement['y']:
firstElement['direction'] = RIGHT
secondElement['direction'] = LEFT
elif firstElement['y'] == secondElement['y'] + 1 and firstElement['x'] == secondElement['x']:
firstElement['direction'] = UP
secondElement['direction'] = DOWN
elif firstElement['y'] == secondElement['y'] - 1 and firstElement['x'] == secondElement['x']:
firstElement['direction'] = DOWN
secondElement['direction'] = UP
else:
# These elements are not adjacent and can't be swapped.
return None, None
return firstElement, secondElement
def getBlankBoard():
# Create and return a blank board data structure.
board = []
for x in range(GRIDWIDTH):
board.append([EMPTY_SPACE] * GRIDHEIGHT)
return board
def canMakeMove(board):
# Return True if the board is in a state where a matching
# move can be made on it. Otherwise return False.
# The patterns in oneOffPatterns represent elements that are configured
# in a way where it only takes one move to make a triplet.
oneOffPatterns = (((0,1), (1,0), (2,0)),
((0,1), (1,1), (2,0)),
((0,0), (1,1), (2,0)),
((0,1), (1,0), (2,1)),
((0,0), (1,0), (2,1)),
((0,0), (1,1), (2,1)),
((0,0), (0,2), (0,3)),
((0,0), (0,1), (0,3)))
# The x and y variables iterate over each space on the board.
# If we use + to represent the currently iterated space on the
# board, then this pattern: ((0,1), (1,0), (2,0))refers to identical
# elements being set up like this:
#
# +A
# B
# C
#
# That is, element A is offset from the + by (0,1), element B is offset
# by (1,0), and element C is offset by (2,0). In this case, element A can
# be swapped to the left to form a vertical three-in-a-row triplet.
#
# There are eight possible ways for the elements to be one move
# away from forming a triple, hence oneOffPattern has 8 patterns.
for x in range(GRIDWIDTH):
for y in range(GRIDHEIGHT):
for pat in oneOffPatterns:
# check each possible pattern of "match in next move" to
# see if a possible move can be made.
if (getElementAt(board, x+pat[0][0], y+pat[0][1]) == \
getElementAt(board, x+pat[1][0], y+pat[1][1]) == \
getElementAt(board, x+pat[2][0], y+pat[2][1]) != None) or \
(getElementAt(board, x+pat[0][1], y+pat[0][0]) == \
getElementAt(board, x+pat[1][1], y+pat[1][0]) == \
getElementAt(board, x+pat[2][1], y+pat[2][0]) != None):
return True # return True the first time you find a pattern
return False
def drawMovingElement(element, progress):
# Draw an element sliding in the direction that its 'direction' key
# indicates. The progress parameter is a number from 0 (just
# starting) to 100 (slide complete).
movex = 0
movey = 0
progress *= 0.01
if element['direction'] == UP:
movey = -int(progress * ELEMENTSIZE)
elif element['direction'] == DOWN:
movey = int(progress * ELEMENTSIZE)
elif element['direction'] == RIGHT:
movex = int(progress * ELEMENTSIZE)
elif element['direction'] == LEFT:
movex = -int(progress * ELEMENTSIZE)
basex = element['x']
basey = element['y']
if basey == ROWABOVEBOARD:
basey = -1
pixelx = XMARGIN + (basex * ELEMENTSIZE)
pixely = YMARGIN + (basey * ELEMENTSIZE)
r = pygame.Rect( (pixelx + movex, pixely + movey, ELEMENTSIZE, ELEMENTSIZE) )
PLAYSURF.blit(ELEMENTIMAGES[element['imageNum']], r)
def pullDownAllElements(board):
# pulls down elements on the board to the bottom to fill in any gaps
for x in range(GRIDWIDTH):
elementsInColumn = []
for y in range(GRIDHEIGHT):
if board[x][y] != EMPTY_SPACE:
elementsInColumn.append(board[x][y])
board[x] = ([EMPTY_SPACE] * (GRIDHEIGHT - len(elementsInColumn))) + elementsInColumn
def getElementAt(board, x, y):
if x < 0 or y < 0 or x >= GRIDWIDTH or y >= GRIDHEIGHT:
return None
else:
return board[x][y]
def getDropSlots(board):
# Creates a "drop slot" for each column and fills the slot with a
# number of elements that that column is lacking. This function assumes
# that the elements have been gravity dropped already.
boardCopy = copy.deepcopy(board)
pullDownAllElements(boardCopy)
dropSlots = []
for i in range(GRIDWIDTH):
dropSlots.append([])
# count the number of empty spaces in each column on the board
for x in range(GRIDWIDTH):
for y in range(GRIDHEIGHT-1, -1, -1): # start from bottom, going up
if boardCopy[x][y] == EMPTY_SPACE:
possibleElements = list(range(len(ELEMENTIMAGES)))
for offsetX, offsetY in ((0, -1), (1, 0), (0, 1), (-1, 0)):
# Narrow down the possible elements we should put in the
# blank space so we don't end up putting an two of
# the same elements next to each other when they drop.
neighborElement = getElementAt(boardCopy, x + offsetX, y + offsetY)
if neighborElement != None and neighborElement in possibleElements:
possibleElements.remove(neighborElement)
newElement = random.choice(possibleElements)
boardCopy[x][y] = newElement
dropSlots[x].append(newElement)
return dropSlots
This is just a partial answer to demonstrate what I suggested in the comments. I'm not sure how your gameBoard actually looks like, so you have to adjust the code as needed.
I use a board which is just filled with strings in this example (you could also use constants WATER = 1, FIRE = 2, etc. or an enum). The sounds are in a dictionary with the elements as the keys. If you have a match, figure out which element it is, then use it to get the associated sound out of the dict and play it.
ELEMENT_SOUNDS = {
'water': WATER_SOUND,
'fire': FIRE_SOUND,
}
board = [
['water', 'fire', 'water'],
['fire', 'water', 'fire'],
['water', 'water', 'water'],
]
if match:
# Figure out the kind of the matching element 'water' in this case.
element_kind = 'water'
ELEMENT_SOUNDS[element_kind].play()