DQN doesn't make any progress after a little while - python

Here is my code, its a simple DQN that learns to play snake and i dont know why it stops learning after a little while, for example. it learns that the snake head should hit the wall, but it doesnt learn to eat the fruit, even though i give a reward for getting closer to the fruit and give a GREATER negative reward for going farther away (this is to make the snake understand that it should aim to go for the fruit). But for some reason, the score never goes beyond a 1 or a 2:
"""
########################################################
#MAIN.py
# -*- coding: utf-8 -*-
"""
Created on Mon Aug 10 13:04:45 2020
#author: Ryan
"""
from dq_learning import Agent
import numpy as np
import tensorflow as tf
import snake
import sys
import pygame
import gym
if __name__ == '__main__':
observation_space = 31
action_space = 4
lr = 0.001
n_games = 50000
steps = 1000
#env = gym.make("LunarLander-v2")
#observation_space = env.observation_space.shape
#action_space = env.action_space.n
agent = Agent(gamma=0.99, epsilon=1.0, lr=lr,
input_dims=observation_space,
n_actions=action_space,
batch_size=64)
scores = []
eps_history = []
r = False
for i in range(n_games):
score = 0
#first observation
observation = [0 for i in range(observation_space)]
#observation = env.reset()
for j in range(steps):
# env.render()
for evt in pygame.event.get():
if evt.type == pygame.QUIT:
pygame.quit()
sys.exit()
#actions go from 0 to n_actions - based on the model prediction or random choice
#action space is the list of all the possible actions
action = agent.choose_action(observation)
#print("action: ", action)
#env.step(action) returns -> new observation, reward, done, info
observation_, reward, done, info = snake.step(action, 25)
#observation_, reward, done, info = env.step(action)
#print(observation_, reward, done, info)
score += reward
agent.store_transition(observation, action, reward, observation_, done)
observation = observation_
agent.learn()
if done:
break
print("NEXT GAME")
done = False
eps_history.append(agent.epsilon)
scores.append(score)
avg_score = np.mean(scores[-100:])
print("episode: ", i, " scores %.2f" %score,
"average score: %.2f" %avg_score,
" epsilon %.2f" %agent.epsilon)
print("last score: ", scores[-1])
#####################################
#DQ_LEARNING.PY
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 4 12:23:14 2020
#author: Ryan
"""
import numpy as np
import tensorflow as tf
from tensorflow import keras
class ReplayBuffer:
def __init__(self, max_size, input_dims):
self.mem_size = max_size
self.mem_cntr = 0
"""
print("self.mem_size: ", self.mem_size)
print("*input_dims: ", *input_dims)
"""
self.state_memory = np.zeros((self.mem_size, input_dims), dtype=np.float32)
self.new_state_memory = np.zeros((self.mem_size, input_dims), dtype=np.float32)
self.action_memory = np.zeros(self.mem_size, np.int32)
self.reward_memory = np.zeros(self.mem_size, np.float32)
self.terminal_memory = np.zeros(self.mem_size, np.int32) #done flags
def store_transitions(self, state, action, reward, state_, done):
"""print("storing transactions...")
print("mem_cntr: ", self.mem_cntr)
print("mem_size: ", self.mem_size)
"""
index = self.mem_cntr % self.mem_size
self.state_memory[index] = state
self.new_state_memory[index] = state_
self.reward_memory[index] = reward
self.action_memory[index] = action
self.terminal_memory[index] = 1 - int(done)
self.mem_cntr += 1
def sample_buffer(self, batch_size):
#print("sampling buffer...")
max_mem = min(self.mem_cntr, self.mem_size)
batch = np.random.choice(max_mem, batch_size, replace=False)
#print("batch:", batch)
states = self.state_memory[batch]
states_ = self.new_state_memory[batch]
rewards = self.reward_memory[batch]
actions = self.action_memory[batch]
terminal = self.terminal_memory[batch]
#print("self.action_mem: ", self.action_memory)
#print("actions: ", actions)
#print("state action rewards state_, terminal", (states, actions, rewards, states_, terminal))
return states, actions, rewards, states_, terminal
def build_dqn(lr, n_actions, input_dims, fc1_dims, fc2_dims):
model = keras.Sequential()
model.add(keras.layers.Dense(fc1_dims, activation='relu'))
model.add(keras.layers.Dense(fc2_dims, activation='relu'))
model.add(keras.layers.Dense(n_actions))
opt = keras.optimizers.Adam(learning_rate=lr)
model.compile(optimizer=opt, loss='mean_squared_error')
return model
class Agent():
def __init__(self, lr, gamma, n_actions, epsilon, batch_size,
input_dims, epsilon_dec=1e-3, epsilon_end=1e-2,
mem_size=1e6, fname='dqn_model.h5'):
self.action_space = [i for i in range(n_actions)]
self.gamma = gamma
self.epsilon = epsilon
self.eps_min = epsilon_end
self.eps_dec = epsilon_dec
self.batch_size = batch_size
self.model_file = fname
self.memory = ReplayBuffer(int(mem_size), input_dims)
self.q_eval = build_dqn(lr, n_actions, input_dims, 256, 256)
def store_transition(self, state, action, reward, new_state, done):
self.memory.store_transitions(state, action, reward, new_state, done)
def choose_action(self, observation):
if np.random.random() < self.epsilon:
action = np.random.choice(self.action_space)
else:
state = np.array([observation])
actions = self.q_eval.predict(state)
action = np.argmax(actions)
return action
def learn(self):
if self.memory.mem_cntr < self.batch_size:
return
states, actions, rewards, states_, dones = \
self.memory.sample_buffer(self.batch_size)
q_eval = self.q_eval.predict(states)
q_next = self.q_eval.predict(states_)
q_target = np.copy(q_eval)
batch_index = np.arange(self.batch_size, dtype=np.int32)
q_target[batch_index, actions] = rewards + \
self.gamma * np.max(q_next, axis=1)*dones
self.q_eval.train_on_batch(states, q_target)
self.epsilon = self.epsilon - self.eps_dec if self.epsilon > \
self.eps_min else self.eps_min
def save_model(self):
self.q_eval.save(self.model_file)
def load_model(self):
self.q_eval = keras.models.load_model(self.model_file)
##########################################
# snake.py
# -*- coding: utf-8 -*-
"""
Created on Fri Sep 4 14:32:30 2020
#author: Ryan
"""
import pygame
import random
from math import sqrt
import time
class Snakehead:
def __init__(self, posx, posy, width, height):
self.posx = posx
self.posy = posy
self.width = width
self.height = height
self.movement = 'null'
self.speed = 16
self.gameover = False
def draw(self, Display): #RGB #coordinates/dimentions
pygame.draw.rect(Display, [0, 0, 0], [self.posx, self.posy, self.width, self.height])
def read_input(self, key):
if key == 0 and key != 1:
self.movement = 'left'
elif key == 1 and key != 0:
self.movement = 'right'
elif key == 2 and key != 3:
self.movement = 'up'
elif key == 3 and key != 2:
self.movement = 'down'
print(self.movement)
def get_pos(self):
return self.posx, self.posy
def get_movement(self):
return self.movement
def restart(self, ScreenW, ScreenH):
self.posx = ScreenW / 2 - 16/2
self.posy = ScreenH / 2 - 16/2
def move(self, SW, SH):
if self.movement == 'right':
self.posx += self.speed # self.posx = self.posx + self.speed
elif self.movement == 'left':
self.posx -= self.speed # self.posx = self.posx - self.speed
elif self.movement == 'up':
self.posy -= self.speed # self.posy = self.posy - self.speed
elif self.movement == 'down':
self.posy += self.speed # self.posy = self.posy + self.speed
class Food:
def __init__(self, posx, posy, width, height):
self.posx = posx
self.posy = posy
self.width = width
self.height = height
self.red = random.randint(155, 255)
def draw(self, Display):
pygame.draw.rect(Display, [self.red, 0, 0], [self.posx, self.posy, self.width, self.height])
def get_pos(self):
return self.posx, self.posy
def respawn(self, ScreenW, ScreenH):
self.posx = random.randint(1, (ScreenW - 16)/16) * 16
self.posy = random.randint(1, (ScreenH - 16)/16) * 16
self.red = random.randint(155, 255)
class Tail:
def __init__(self, posx, posy, width, height):
self.width = width
self.height = height
self.posx = posx
self.posy = posy
self.RGB = [random.randint(0, 255) for i in range(3)]
def draw(self, Diplay):
pygame.draw.rect(Diplay, self.RGB, [self.posx, self.posy, 16, 16])
def move(self, px, py):
self.posx = px
self.posy = py
def get_pos(self):
return self.posx, self.posy
ScreenW = 720
ScreenH = 720
sheadX = 0
sheadY = 0
fX = 0
fY = 0
counter = 0
pygame.init()
pygame.display.set_caption("Snake Game")
Display = pygame.display.set_mode([ScreenW, ScreenH])
Display.fill([255, 255, 255]) #RGB white
black = [0, 0, 0]
font = pygame.font.SysFont(None, 30)
score = font.render("Score: 0", True, black)
shead = Snakehead(ScreenW / 2 - 16/2, ScreenH / 2 - 16/2, 16, 16)
f = Food(random.randint(0, (ScreenW - 16)/16) * 16 - 8, random.randint(0, (ScreenH - 16)/16) * 16, 16, 16)
tails = []
Fps = 60
timer_clock = pygame.time.Clock()
previous_distance = 0
d = 0
def step(action, observation_space):
global score, counter, tails, shead, gameover, previous_distance, d
shead.gameover = False
observation_, reward, done, info = [0 for i in range(observation_space+6)], 0, 0, 0
Display.fill([255, 255, 255])
shead.read_input(action)
sheadX, sheadY = shead.get_pos()
fX, fY = f.get_pos()
#detect collision
if sheadX + 16 > fX and sheadX < fX + 16:
if sheadY + 16 > fY and sheadY < fY + 16:
#collision
f.respawn(ScreenW, ScreenH)
counter += 1 # counter = counter + 1
score = font.render("Score: " + str(counter), True, black)
if len(tails) == 0:
tails.append(Tail(sheadX, sheadY, 16, 16))
#tails.append(tail.Tail(sheadX, sheadY, 16, 16, shead.get_movement()))
else:
tX, tY = tails[-1].get_pos()
tails.append(Tail(tX, tY, 16, 16))
reward = 100
print(tails)
for i in range(len(tails)):
try:
tX, tY = tails[i].get_pos()
#print("tx: ", tX, " ty: ", tY)
sX, sY = shead.get_pos()
#print("Sx: ", sX, " sy: ", sY)
if i != 0 and i != 1:
#print("more than 2 tails")
if tX == sX and tY == sY:
print("collision")
#collision
shead.restart(ScreenW, ScreenH)
tails.clear()
counter = 0
Display.blit(score, (10, 10))
pygame.display.flip()
pygame.display.update()
reward = -300
shead.gameover = True
print("lost-3")
except:
shead.restart(ScreenW, ScreenH)
tails.clear()
counter = 0
reward = -300
shead.gameover = True
print("lost-0")
sX, sY = shead.get_pos()
if sX < 0 or sX + 16 > ScreenW:
shead.restart(1280, 720)
counter = 0
Display.blit(score, (10, 10))
pygame.display.flip()
pygame.display.update()
tails.clear()
print("lost-1")
reward = -200
shead.gameover = True
#restart
elif sY < 0 or sY + 16 > ScreenH:
shead.restart(1280, 720)
counter = 0
Display.blit(score, (10, 10))
pygame.display.flip()
pygame.display.update()
tails.clear()
reward = -200
shead.gameover = True
print("lost-2")
#restart
for i in range(1, len(tails)):
tX, tY = tails[len(tails) - i - 1].get_pos() # y = b - x
tails[len(tails) - i].move(tX, tY)
if len(tails) > 0:
tX, tY = shead.get_pos()
tails[0].move(tX, tY)
shead.move(ScreenW, ScreenH)
shead.draw(Display)
Display.blit(score, (10, 10))
for tail in tails:
tail.draw(Display)
f.draw(Display)
pygame.display.flip()
pygame.display.update()
timer_clock.tick(Fps)
#observation, done
done = shead.gameover
hx, hy = shead.get_pos()
hx /= ScreenW
hy /= ScreenH
fx, fy = f.get_pos()
fx /= ScreenW
fy /= ScreenH
observation_[0] = abs(hx - fx)
observation_[1] = abs(hy - fy)
previous_distance = d
d = sqrt((fx - hx)**2 + (fy - hy)**2)
#print("distance: ", d)
observation_[2] = d
observation_[3] = 0
#print("observation_[4]: ", observation_[4])
observation_[4] = hx
observation_[5] = hy
c = 6
xlist = []
ylist = []
for t in tails:
tx, ty = t.get_pos()
tx /= 16
ty /= 16
xlist.append(tx)
ylist.append(ty)
l = int(sqrt(observation_space))
startX, startY = shead.get_pos()
startX /= 16
startY /= 16
m = (l-1)/2
#print("xlist:" , xlist)
#print("ylist:", ylist)
#print("startX: ", startX)
#print("startY: ", startY)
#print("m: ", m)
#print("l: ", l)
for x in range(l):
for y in range(l):
found = False
#print("position: (", int(startX) - m + x, ",", int(startY) - m + y, ")")
for i in range(len(xlist)):
"""print("i:", i)
print("pos: ", startX - m + x)
print("j: ", j)
print("pos: ", startY - m + y)
"""
#print("current iteration: (", int(xlist[i]), ",", int(ylist[i]), ")")
if int(xlist[i]) == int(startX) - m + x and int(ylist[i]) == int(startY) - m + y:
#print("found a match")
observation_[c] = 1
#print("c is: ", c)
#print("observation_[c] is: ", observation_[c])
found = True
break
if not found:
#print("set to 0")
observation_[c] = 0
#print("increasing c...")
c += 1
print("reward: ", reward)
print("c_reward: ", counter*10)
d_reward = 10 if d < previous_distance else - 100
print("d_reward: ", d_reward)
print(observation_, reward + d_reward + counter*10, done, 0)
return observation_, reward, done, 0

The reward function looks fine to me.
However, you say "I give a reward for getting closer to the fruit and give a GREATER negative reward for going farther away" but in the code it does not look like you use d_reward:
print("reward: ", reward)
print("c_reward: ", counter*10)
d_reward = 10 if d < previous_distance else - 100
print("d_reward: ", d_reward)
print(observation_, reward + d_reward + counter*10, done, 0)
return observation_, reward, done, 0
This is fine, as d_reward is definitely not necessary. Only giving positive reward for eating the apple, negative for dying and 0 otherwise is enough.
I suspect that the issue is in your state representation. Only by looking at your state, it is impossible for your agent to know which direction it should go, as the information of the apple position relative to the head is given with absolute values.
As an example lets say that your board is as follows:
[food, head, empty]
Your observation would be:
[1, 0, 1, 0, 1, 0]
But if your board is:
[empty, head, food]
The observation is the same:
[1, 0, 1, 0, 1, 0]
This is a problem. With a given input, the same action could be good or bad, whithout any way of knowing it. This makes learning impossible. In our example, for the input [1, 0, 1, 0, 1, 0], our network could move towards (or away) from both: left and right , never converging in any action.
This is because in your training data you will have examples of that input where moving to the left is good, others where it is neutral, others where it is bad, and examples of that input where right is good, neutral, bad etc.
I would recommend to encode more information in your state (or observation). I suggest something like this (which I took from a project of mine, you'll need to adapt it):
def get_state(self):
head = self.snake[0]
danger_top = head.y == self.board_dim.y - 1 or Point(head.x, head.y + 1) in self.snake
danger_bot = head.y == 0 or Point(head.x, head.y - 1) in self.snake
danger_right = head.x == self.board_dim.x - 1 or Point(head.x + 1, head.y) in self.snake
danger_left = head.x == 0 or Point(head.x - 1, head.y) in self.snake
apple_top = head.y < self.apple.y
apple_bot = head.y > self.apple.y
apple_right = head.x < self.apple.x
apple_left = head.x > self.apple.x
return np.array([
danger_top,
danger_bot,
danger_right,
danger_left,
apple_top,
apple_bot,
apple_right,
apple_left], dtype=int)
Please, let me know if I did miss some part of your code or if you have any doubt. Thank you in advance.

Related

Python treating all instances of an object as the same

I'm making a game with pygame and pymunk as a physics engine. I'm trying to kill a bullet whenever it hits a player or goes past its lifetime.
When I tried to space.remove(self.shape) and the second bullet hits the player, it gives me an "AssertionError: shape not in space, already removed. I simply changed it to teleport the bullets away, and then learned of the real error.
When I have more than one bullet in the space and a bullet hits the enemy player, all the current bullets teleport away, which means that when I tried to remove one bullet, it called the remove on all the bullets and thats why I had the initial error.
However the problem still remains that one bullet is being treated as every bullet.
Why is something that should be a non-static variable being called as a static variable?
I even tried to use deepcopy to see if that fixed it, but to no avail
This is my chunk of code, apologies since I don't know what is needed to understand it.
The key parts are most likely the Bullet class, the shoot() function in the Player class, and the drawBulletCollision() function
# PyGame template.
# Import modules.
import sys, random, math, time, copy
from typing import List
import pygame
from pygame.locals import *
from pygame import mixer
import pymunk
import pymunk.pygame_util
from pymunk.shapes import Segment
from pymunk.vec2d import Vec2d
pygame.mixer.pre_init(44110, -16, 2, 512)
mixer.init()
# Set up the window.
width, height = 1440, 640
screen = pygame.display.set_mode((width, height))
bg = pygame.image.load("space.png")
def draw_bg():
screen.blit(bg, (0, 0))
#load sounds
#death_fx = pygame.mixer.Sound("")
#death_fx.set_volume(0.25)
shoot_fx = mixer.Sound("shot.wav")
shoot_fx.set_volume(0.25)
#mixer.music.load("video.mp3")
#mixer.music.play()
#time.sleep(2)
#mixer.music.stop()
#gun_mode_fx = pygame.mixer.Sound("")
#gun_mode_fx.set_volume(0.25)
#thrust_mode_fx = pygame.mixer.Sound("")
#thrust_mode_fx.set_volume(0.25)
collision_fx = mixer.Sound("thump.wav")
collision_fx.set_volume(0.25)
ship_group = pygame.sprite.Group()
space = pymunk.Space()
space.gravity = 0, 0
space.damping = 0.6
draw_options = pymunk.pygame_util.DrawOptions(screen)
bulletList = []
playerList = []
environmentList = []
arbiterList = []
b0 = space.static_body
segmentBot = pymunk.Segment(b0, (0,height), (width, height), 4)
segmentTop = pymunk.Segment(b0, (0,0), (width, 0), 4)
segmentLef = pymunk.Segment(b0, (width,0), (width, height), 4)
segmentRit = pymunk.Segment(b0, (0,0), (0, height), 4)
walls = [segmentBot,segmentLef,segmentRit,segmentTop]
for i in walls:
i.elasticity = 1
i.friction = 0.5
i.color = (255,255,255,255)
environmentList.append(i)
class Player(object):
radius = 30
def __init__(self, position, space, color):
self.body = pymunk.Body(mass=5,moment=10)
self.mode = 0 # 0 is gun, 1 is thrust, ? 2 is shield
self.body.position = position
self.shape = pymunk.Circle(self.body, radius = self.radius)
#self.image
#self.shape.friction = 0.9
self.shape.elasticity= 0.2
space.add(self.body,self.shape)
self.angleGun = 0
self.angleThrust = 0
self.health = 100
self.speed = 500
self.gearAngle = 0
self.turningSpeed = 5
self.shape.body.damping = 1000
self.cooldown = 0
self.fireRate = 30
self.shape.collision_type = 1
self.shape.color = color
playerList.append(self)
def force(self,force):
self.shape.body.apply_force_at_local_point(force,(0,0))
def rocketForce(self):
radians = self.angleThrust * math.pi/180
self.shape.body.apply_force_at_local_point((-self.speed * math.cos(radians),-self.speed * math.sin(radians)),(0,0))
def draw(self):
gear = pygame.image.load("gear.png")
gearBox = gear.get_rect(center=self.shape.body.position)
gearRotated = pygame.transform.rotate(gear, self.gearAngle)
#gearRotated.rect.center=self.shape.body.position
x,y = self.shape.body.position
radianGun = self.angleGun * math.pi/180
radianThrust = self.angleThrust * math.pi/180
radiyus = 30 *(100-self.health)/100
screen.blit(gearRotated,gearBox)
self.gearAngle += 1
if radiyus == 30:
radiyus = 32
pygame.draw.circle(screen,self.shape.color,self.shape.body.position,radiyus,0)
pygame.draw.circle(screen,(0,0,0),self.shape.body.position,radiyus,0)
pygame.draw.line(
screen,(0,255,0),
(self.radius * math.cos(radianGun) * 1.5 + x,self.radius * math.sin(radianGun) * 1.5 + y),
(x,y), 5
)
pygame.draw.line(
screen,(200,200,0),
(self.radius * math.cos(radianThrust) * 1.5 + x,self.radius * math.sin(radianThrust) * 1.5 + y),
(x,y), 5
)
#more
def targetAngleGun(self,tAngle):
tempTAngle = tAngle - self.angleGun
tempTAngle = tempTAngle % 360
if(tempTAngle < 180 and not tempTAngle == 0):
self.angleGun -= self.turningSpeed
elif(tempTAngle >= 180 and not tempTAngle == 0):
self.angleGun += self.turningSpeed
self.angleGun = self.angleGun % 360
#print(tAngle, "target Angle")
#print(self.angleGun, "selfangleGun")
#print(tempTAngle, "tempTAngle")
def targetAngleThrust(self,tAngle):
tempTAngle = tAngle - self.angleThrust
tempTAngle = tempTAngle % 360
if(tempTAngle < 180 and not tempTAngle == 0):
self.angleThrust -= self.turningSpeed
elif(tempTAngle >= 180 and not tempTAngle == 0):
self.angleThrust += self.turningSpeed
self.angleThrust = self.angleThrust % 360
#print(tAngle, "target Angle")
#print(self.angleThrust, "selfangleGun")
#print(tempTAngle, "tempTAngle")
def targetAngle(self,tAngle):
if(self.mode == 0):
self.targetAngleGun(tAngle)
elif(self.mode == 1):
self.targetAngleThrust(tAngle)
def shoot(self):
if(self.cooldown == self.fireRate):
x,y = self.shape.body.position
radianGun = self.angleGun * math.pi/180
spawnSpot = (self.radius * math.cos(radianGun) * 1.5 + x,self.radius * math.sin(radianGun)*1.5+y)
self.shape.body.apply_impulse_at_local_point((-20 * math.cos(radianGun),-20 * math.sin(radianGun)),(0,0))
print(spawnSpot)
bT = Bullet(spawnSpot, 5, 50,self.shape.color)
b = copy.deepcopy(bT)
bulletList.append(b)
space.add(b.shape,b.shape.body)
b.getShot(self.angleGun)
self.cooldown = 0
print('pew')
shoot_fx.play()
# HEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEREEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
def tick(self):
self.draw()
if(self.cooldown < self.fireRate):
self.cooldown += 1
#for o in playerList:
# c = self.shape.shapes_collide(o.shape)
# if(len(c.points)>0):
# self.damage(c.points[0].distance/10)
for o in bulletList:
c = self.shape.shapes_collide(o.shape)
#print(c)
for o in walls:
c = self.shape.shapes_collide(o)
if(len(c.points)>0):
self.damage(c.points[0].distance * 3)
def damage(self, damage):
self.health -= abs(damage)
if self.health < 0:
self.health = 0
#maybe make it part of the player class
def drawWallCollision(arbiter, space, data):
for c in arbiter.contact_point_set.points:
r = max(3, abs(c.distance * 5))
r = int(r)
p = tuple(map(int, c.point_a))
pygame.draw.circle(data["surface"], pygame.Color("red"), p, r, 0)
print('magnitude', math.sqrt(arbiter.total_impulse[0]**2 + arbiter.total_impulse[1]**2))
#print('position', p)
#print(data)
print("its all arbitrary")
s1, s2 = arbiter.shapes
collision_fx.play()
def drawBulletCollision(arbiter, space, data):
s1, s2 = arbiter.shapes
for c in arbiter.contact_point_set.points:
magnitude = math.sqrt(arbiter.total_impulse[0]**2 + arbiter.total_impulse[1]**2)
for p in playerList:
avr = ((c.point_a[0] + c.point_b[0])/2, (c.point_a[1] + c.point_b[1])/2)
distance = (math.sqrt((avr[0] - p.shape.body.position[0]) **2 + (avr[1] - p.shape.body.position[1]) **2 ))
if(distance < Bullet.explosionRadius + Player.radius):
if not(s1.color == s2.color):
p.damage(magnitude)
for b in bulletList:
avr = ((c.point_a[0] + c.point_b[0])/2, (c.point_a[1] + c.point_b[1])/2)
distance = (math.sqrt((avr[0] - p.shape.body.position[0]) **2 + (avr[1] - p.shape.body.position[1]) **2 ))
if(distance < Bullet.explosionRadius + Player.radius):
if not(s1.color == s2.color):
b.damage(magnitude)
pygame.draw.circle(data["surface"], pygame.Color("red"), tuple(map(int, c.point_a)), 10, 0)
print('magnitude', magnitude)
#print('position', p)
#print(data)
print("its all arbitrary")
def drawArbitraryCollision(arbiter, space, data):
collision_fx.play()
class Ship(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("gear.png")
self.rect = self.image.get_rect()
self.rect.center = [x, y]
def rotate(self):
self.image = pygame.transform.rotate(self.image,1)
class Bullet(object):
damage = 2
explosionRadius = 5
def __init__(self, position, size, speed,color):
pts = [(-size, -size), (size, -size), (size, size), (-size, size)]
self.body = copy.deepcopy(pymunk.Body(mass=0.1,moment=1))
self.shape = copy.deepcopy(pymunk.Poly(self.body, pts))
self.shape.body.position = position
self.shape.friction = 0.5
self.shape.elasticity = 1
self.shape.color = color
self.speed = speed
self.size = size
self.shape.collision_type = 2
#space.add(self.body,self.shape)
#bulletList.append(self)
self.lifetime = 0
def getShot(self,angle):
radians = angle * math.pi/180
self.shape.body.apply_impulse_at_local_point((self.speed * math.cos(radians),self.speed * math.sin(radians)),(0,0))
def tick(self):
self.lifetime += 1
if(self.lifetime > 300):
self.shape.body.position = (10000,30)
def damage(self, damage):
self.lifetime = 300
#VELOCITY OF BULLET STARTS WITH VELOCITY OF PLAYER
#MAKE VOLUME OF SOUND DEPEND ON THE IMPULSE FOR THE IMPACTS
#error on purpose so you notice this
#INSTANCES NOT WORKING????
def runPyGame():
# Initialise PyGame.
pygame.init()
# Set up the clock. This will tick every frame and thus maintain a relatively constant framerate. Hopefully.
fps = 60.0
fpsClock = pygame.time.Clock()
running = True
font = pygame.font.SysFont("Arial", 16)
p1 = Player((240,240),space,(132, 66, 245,255))
p2 = Player((1200,400),space,(47, 247, 184,255))
space.add(segmentBot,segmentTop,segmentLef,segmentRit)
# Main game loop.
ch = space.add_collision_handler(1, 0)
ch.data["surface"] = screen
ch.post_solve = drawWallCollision
ch = space.add_collision_handler(1, 2)
ch.data["surface"] = screen
ch.post_solve = drawBulletCollision
ch = space.add_collision_handler(0, 2)
ch.data["surface"] = screen
ch.post_solve = drawArbitraryCollision
dt = 1/fps # dt is the time since last frame.
while True: # Loop forever!
keys = pygame.key.get_pressed()
for event in pygame.event.get():
# We need to handle these events. Initially the only one you'll want to care
# about is the QUIT event, because if you don't handle it, your game will crash
# whenever someone tries to exit.
if event.type == QUIT:
pygame.quit() # Opposite of pygame.init
sys.exit() # Not including this line crashes the script on Windows.
if event.type == KEYDOWN:
if event.key == pygame.K_s:
p1.mode = -(p1.mode - 0.5) + 0.5
print(p1.mode)
if (event.key == pygame.K_k and p1.mode == 0):
p1.shoot()
if event.key == pygame.K_KP_5:
p2.mode = -(p2.mode - 0.5) + 0.5
print(p2.mode)
if (event.key == pygame.K_m and p2.mode == 0):
p2.shoot()
#b = Bullet((200,200),51,51)
if(keys[K_w]):
p1.targetAngle(90)
if(keys[K_q]):
p1.targetAngle(45)
if(keys[K_a]):
p1.targetAngle(0)
if(keys[K_z]):
p1.targetAngle(315)
if(keys[K_x]):
p1.targetAngle(270)
if(keys[K_c]):
p1.targetAngle(225)
if(keys[K_d]):
p1.targetAngle(180)
if(keys[K_e]):
p1.targetAngle(135)
if(keys[K_k] and p1.mode == 1):
p1.rocketForce()
if(keys[K_KP_8]):
p2.targetAngle(90)
if(keys[K_KP_7]):
p2.targetAngle(45)
if(keys[K_KP_4]):
p2.targetAngle(0)
if(keys[K_KP_1]):
p2.targetAngle(315)
if(keys[K_KP_2]):
p2.targetAngle(270)
if(keys[K_KP_3]):
p2.targetAngle(225)
if(keys[K_KP_6]):
p2.targetAngle(180)
if(keys[K_KP_9]):
p2.targetAngle(135)
if(keys[K_m] and p2.mode == 1):
p2.rocketForce()
# Handle other events as you wish.
screen.fill((250, 250, 250)) # Fill the screen with black.
# Redraw screen here.
### Draw stuff
draw_bg()
space.debug_draw(draw_options)
for i in playerList:
i.tick()
screen.blit(
font.render("P1 Health: " + str(p1.health), True, pygame.Color("white")),
(50, 10),
)
screen.blit(
font.render("P2 Health: " + str(p2.health), True, pygame.Color("white")),
(50, 30),
)
for i in bulletList:
i.tick()
ship_group.draw(screen)
# Flip the display so that the things we drew actually show up.
pygame.display.update()
dt = fpsClock.tick(fps)
space.step(0.01)
pygame.display.update()
runPyGame()
I cant point to the exact error since the code is quite long and depends on files I dont have. But here is a general advice for troubleshooting:
Try to give a name to each shape when you create them, and then print it out. Also print out the name of each shape that you add or remove from the space. This should show which shape you are actually removing and will probably make it easy to understand whats wrong.
For example:
...
self.shape = pymunk.Circle(self.body, radius = self.radius)
self.shape.name = "circle 1"
print("Created", self.shape.name)
...
print("Adding", self.shape.name)
space.add(self.body,self.shape)
...
(Note that you need to reset the name of shapes you copy, since otherwise the copy will have the same name.)

How to generate trees or other structures over chunks in a 2D Minecraft-like game

I am trying to create a 2D Minecraft-like game, and I am running into a problem. I'm trying to generate trees on top of the terrain. The terrain is generated with simplex noise, and it is split up into 8x8 chunks, the game generates new chunks if necessary every time the player moves. I tried generating trees by randomly choosing a location on top of the terrain, and setting the blocks above it to the blocks I wanted, then I ran into another problem. The trees might go into neighboring chunks. I tried to solve this problem by storing the parts of a tree that are in other chunks in a dictionary, and generating them from the dictionary when the other chunk is generated, but there is another problem. Sometimes the chunk neighboring the chunk that contains most of the tree has already been generated, so I cannot overwrite it when the chunk that has the tree gets generated... I am slightly confused on how to get this to work.
This is the code for generating new chunks, where the parameters x and y are the location of the chunk to generate:
def generate_chunk(x, y):
chunk_data = {}
for y_pos in range(CHUNK_SIZE):
for x_pos in range(CHUNK_SIZE):
block = (x * CHUNK_SIZE + x_pos, y * CHUNK_SIZE + y_pos)
block_name = ""
height = int(noise.noise2d(block[0]*0.1, 0)*5)
if block[1] == 5-height:
block_name = "grass_block"
elif 5-height < block[1] < 10-height:
block_name = "dirt"
elif block[1] >= 10-height:
block_name = "stone"
if block_name != "":
chunk_data[block] = block_name
return chunk_data
Here is the main loop where the chunks near the player are generated and are temporarily deleted and saved when the player leaves:
running = True
while running:
dt = clock.tick(FPS) / 16
pygame.display.set_caption(f"2D Minecraft | FPS: {int(clock.get_fps())}")
for event in pygame.event.get():
if event.type == QUIT:
running = False
rendered_chunks = []
for y in range(int(HEIGHT/(CHUNK_SIZE*BLOCK_SIZE)+2)):
for x in range(int(WIDTH/(CHUNK_SIZE*BLOCK_SIZE)+2)):
chunk = (
x - 1 + int(round(camera.pos.x / (CHUNK_SIZE * BLOCK_SIZE))),
y - 1 + int(round(camera.pos.y / (CHUNK_SIZE * BLOCK_SIZE)))
)
rendered_chunks.append(chunk)
if chunk not in chunks:
chunks[chunk] = Chunk(chunk)
unrendered_chunks = []
for y in range(int(HEIGHT/(CHUNK_SIZE*BLOCK_SIZE)+4)):
for x in range(int(WIDTH/(CHUNK_SIZE*BLOCK_SIZE)+4)):
chunk = (
x - 2 + int(round(camera.pos.x / (CHUNK_SIZE * BLOCK_SIZE))),
y - 2 + int(round(camera.pos.y / (CHUNK_SIZE * BLOCK_SIZE)))
)
try: chunks[chunk]
except: pass
else:
if chunk not in rendered_chunks:
unrendered_chunks.append(chunk)
for chunk in unrendered_chunks:
for block in chunks[chunk].block_data:
if block in blocks:
blocks[block].kill()
del blocks[block]
camera.update()
player.update()
screen.fill((135, 206, 250))
for chunk in rendered_chunks:
chunks[chunk].render()
player.draw(screen)
pygame.display.flip()
Here are the Block class and the Chunk class:
class Block(pygame.sprite.Sprite):
def __init__(self, chunk, pos, name):
pygame.sprite.Sprite.__init__(self)
blocks[tuple(pos)] = self
self.name = name
self.chunk = chunk
self.coords = vec(pos)
self.pos = self.coords * BLOCK_SIZE
self.image = block_textures[self.name]
self.rect = self.image.get_rect()
def update(self):
self.rect.topleft = self.pos - camera.pos
def draw(self, screen):
screen.blit(self.image, self.rect.topleft)
class Chunk(object):
def __init__(self, pos):
self.pos = pos
self.block_data = generate_chunk(pos[0], pos[1])
for block in self.block_data:
blocks[block] = Block(self, block, self.block_data[block])
def render(self):
if self.pos in rendered_chunks:
for block in self.block_data:
try: blocks[block]
except:
blocks[block] = Block(self, block, self.block_data[block])
blocks[block].update()
blocks[block].draw(screen)
pygame.draw.rect(screen, (255, 255, 0), (self.pos[0]*CHUNK_SIZE*BLOCK_SIZE-camera.pos[0], self.pos[1]*CHUNK_SIZE*BLOCK_SIZE-camera.pos[1], CHUNK_SIZE*BLOCK_SIZE, CHUNK_SIZE*BLOCK_SIZE), width=1)
The minimal reproducible code, I think all the information needed would be above but just in case you need the rest:
import pygame
from pygame.locals import *
from random import *
from math import *
import json
import os
import opensimplex
FPS = 60
WIDTH, HEIGHT = 1200, 600
SCR_DIM = (WIDTH, HEIGHT)
GRAVITY = 0.5
SLIDE = 0.3
TERMINAL_VEL = 24
BLOCK_SIZE = 64
CHUNK_SIZE = 8
SEED = randint(-2147483648, 2147483647)
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT), HWSURFACE | DOUBLEBUF)
pygame.display.set_caption("2D Minecraft")
clock = pygame.time.Clock()
mixer = pygame.mixer.init()
vec = pygame.math.Vector2
noise = opensimplex.OpenSimplex(seed=SEED)
seed(SEED)
block_textures = {}
for img in os.listdir("res/textures/blocks/"):
block_textures[img[:-4]] = pygame.image.load("res/textures/blocks/"+img).convert_alpha()
for image in block_textures:
block_textures[image] = pygame.transform.scale(block_textures[image], (BLOCK_SIZE, BLOCK_SIZE))
def intv(vector):
return vec(int(vector.x), int(vector.y))
def inttup(tup):
return (int(tup[0]), int(tup[1]))
def block_collide(ax, ay, width, height, b):
a_rect = pygame.Rect(ax-camera.pos.x, ay-camera.pos.y, width, height)
b_rect = pygame.Rect(b.pos.x-camera.pos.x, b.pos.y-camera.pos.y, BLOCK_SIZE, BLOCK_SIZE)
if a_rect.colliderect(b_rect):
return True
return False
class Camera(pygame.sprite.Sprite):
def __init__(self, master):
self.master = master
self.pos = self.master.size / 2
self.pos = self.master.pos - self.pos - vec(SCR_DIM) / 2 + self.master.size / 2
def update(self):
tick_offset = self.master.pos - self.pos - vec(SCR_DIM) / 2 + self.master.size / 2
if -1 < tick_offset.x < 1:
tick_offset.x = 0
if -1 < tick_offset.y < 1:
tick_offset.y = 0
self.pos += tick_offset / 10
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.size = vec(0.225*BLOCK_SIZE, 1.8*BLOCK_SIZE)
self.width, self.height = self.size.x, self.size.y
self.start_pos = vec(0, 3) * BLOCK_SIZE
self.pos = vec(self.start_pos)
self.coords = self.pos // BLOCK_SIZE
self.vel = vec(0, 0)
self.max_speed = 5.306
self.jumping_max_speed = 6.6
self.rect = pygame.Rect((0, 0, 0.225*BLOCK_SIZE, 1.8*BLOCK_SIZE))
self.bottom_bar = pygame.Rect((self.rect.x+1, self.rect.bottom), (self.width-2, 1))
self.on_ground = False
def update(self):
keys = pygame.key.get_pressed()
if keys[K_a]:
if self.vel.x > -self.max_speed:
self.vel.x -= SLIDE
elif self.vel.x < 0:
self.vel.x += SLIDE
if keys[K_d]:
if self.vel.x < self.max_speed:
self.vel.x += SLIDE
elif self.vel.x > 0:
self.vel.x -= SLIDE
if keys[K_w] and self.on_ground:
self.vel.y = -9.2
self.vel.x *= 1.1
if self.vel.x > self.jumping_max_speed:
self.vel.x = self.jumping_max_speed
elif self.vel.x < -self.jumping_max_speed:
self.vel.x = -self.jumping_max_speed
if -SLIDE < self.vel.x < SLIDE:
self.vel.x = 0
self.vel.y += GRAVITY
if self.vel.y > TERMINAL_VEL:
self.vel.y = TERMINAL_VEL
self.move()
self.bottom_bar = pygame.Rect((self.rect.left+1, self.rect.bottom), (self.width-2, 1))
for block in blocks:
if self.bottom_bar.colliderect(blocks[block].rect):
self.on_ground = True
break
else:
self.on_ground = False
if self.on_ground:
self.vel.x *= 0.99
self.coords = self.pos // BLOCK_SIZE
self.chunk = self.coords // CHUNK_SIZE
self.rect.topleft = self.pos - camera.pos
def draw(self, screen):
pygame.draw.rect(screen, (0, 0, 0), self.rect)
def move(self):
for y in range(4):
for x in range(3):
try:
block = blocks[(int(self.coords.x-1+x), int(self.coords.y-1+y))]
except:
pass
else:
if self.vel.y < 0:
if block_collide(floor(self.pos.x), floor(self.pos.y+self.vel.y), self.width, self.height, block):
self.pos.y = floor(block.pos.y + BLOCK_SIZE)
self.vel.y = 0
elif self.vel.y >= 0:
if self.vel.x <= 0:
if block_collide(floor(self.pos.x), ceil(self.pos.y+self.vel.y), self.width, self.height, block):
self.pos.y = ceil(block.pos.y - self.height)
self.vel.y = 0
elif self.vel.x > 0:
if block_collide(ceil(self.pos.x), ceil(self.pos.y+self.vel.y), self.width, self.height, block):
self.pos.y = ceil(block.pos.y - self.height)
self.vel.y = 0
if self.vel.x < 0:
if block_collide(floor(self.pos.x+self.vel.x), floor(self.pos.y), self.width, self.height, block):
self.pos.x = floor(block.pos.x + BLOCK_SIZE)
self.vel.x = 0
elif self.vel.x >= 0:
if block_collide(ceil(self.pos.x+self.vel.x), ceil(self.pos.y), self.width, self.height, block):
self.pos.x = ceil(block.pos.x - self.width)
self.vel.x = 0
self.pos += self.vel
class Block(pygame.sprite.Sprite):
def __init__(self, chunk, pos, name):
pygame.sprite.Sprite.__init__(self)
blocks[tuple(pos)] = self
self.name = name
self.chunk = chunk
self.coords = vec(pos)
self.pos = self.coords * BLOCK_SIZE
self.image = block_textures[self.name]
self.rect = self.image.get_rect()
def update(self):
self.rect.topleft = self.pos - camera.pos
def draw(self, screen):
screen.blit(self.image, self.rect.topleft)
class Chunk(object):
def __init__(self, pos):
self.pos = pos
self.block_data = generate_chunk(pos[0], pos[1])
for block in self.block_data:
blocks[block] = Block(self, block, self.block_data[block])
def render(self):
if self.pos in rendered_chunks:
for block in self.block_data:
try: blocks[block]
except:
blocks[block] = Block(self, block, self.block_data[block])
blocks[block].update()
blocks[block].draw(screen)
pygame.draw.rect(screen, (255, 255, 0), (self.pos[0]*CHUNK_SIZE*BLOCK_SIZE-camera.pos[0], self.pos[1]*CHUNK_SIZE*BLOCK_SIZE-camera.pos[1], CHUNK_SIZE*BLOCK_SIZE, CHUNK_SIZE*BLOCK_SIZE), width=1)
def generate_chunk(x, y):
chunk_data = {}
for y_pos in range(CHUNK_SIZE):
for x_pos in range(CHUNK_SIZE):
block = (x * CHUNK_SIZE + x_pos, y * CHUNK_SIZE + y_pos)
block_name = ""
height = int(noise.noise2d(block[0]*0.1, 0)*5)
if block[1] == 5-height:
block_name = "grass_block"
elif 5-height < block[1] < 10-height:
block_name = "dirt"
elif block[1] >= 10-height:
block_name = "stone"
if block_name != "":
chunk_data[block] = block_name
return chunk_data
blocks = {}
chunks = {}
player = Player()
camera = Camera(player)
running = True
while running:
dt = clock.tick(FPS) / 16
pygame.display.set_caption(f"2D Minecraft | FPS: {int(clock.get_fps())}")
for event in pygame.event.get():
if event.type == QUIT:
running = False
rendered_chunks = []
for y in range(int(HEIGHT/(CHUNK_SIZE*BLOCK_SIZE)+2)):
for x in range(int(WIDTH/(CHUNK_SIZE*BLOCK_SIZE)+2)):
chunk = (
x - 1 + int(round(camera.pos.x / (CHUNK_SIZE * BLOCK_SIZE))),
y - 1 + int(round(camera.pos.y / (CHUNK_SIZE * BLOCK_SIZE)))
)
rendered_chunks.append(chunk)
if chunk not in chunks:
chunks[chunk] = Chunk(chunk)
unrendered_chunks = []
for y in range(int(HEIGHT/(CHUNK_SIZE*BLOCK_SIZE)+4)):
for x in range(int(WIDTH/(CHUNK_SIZE*BLOCK_SIZE)+4)):
chunk = (
x - 2 + int(round(camera.pos.x / (CHUNK_SIZE * BLOCK_SIZE))),
y - 2 + int(round(camera.pos.y / (CHUNK_SIZE * BLOCK_SIZE)))
)
try: chunks[chunk]
except: pass
else:
if chunk not in rendered_chunks:
unrendered_chunks.append(chunk)
for chunk in unrendered_chunks:
for block in chunks[chunk].block_data:
if block in blocks:
blocks[block].kill()
del blocks[block]
camera.update()
player.update()
screen.fill((135, 206, 250))
for chunk in rendered_chunks:
chunks[chunk].render()
player.draw(screen)
pygame.display.flip()
pygame.quit()
quit()
(btw the yellow lines are the chunk borders)
The general idea is to realize how big a structure (e.g. a Tree) can be, calculate how many chunks it can span and then check when generating chunk (x, y) all chunks around it. This could be done with something like this:
TREE_SHAPE = {
(0, 0): "oak_log",
(0, -1): "oak_log",
(0, -2): "oak_log",
(0, -3): "oak_log",
(0, -4): "oak_leaves",
(1, -4): "oak_leaves",
(2, -4): "oak_leaves",
(3, -4): "oak_leaves",
(-1, -4): "oak_leaves",
(-2, -4): "oak_leaves",
(-3, -4): "oak_leaves",
}
MAX_TREE_SIZE = (max(x for x, y in TREE_SHAPE) - min(x for x, y in TREE_SHAPE) + 1,
max(y for x, y in TREE_SHAPE) - min(y for x, y in TREE_SHAPE) + 1)
CHUNKS_TO_CHECK = int(ceil(MAX_TREE_SIZE[0] / CHUNK_SIZE)), int(ceil(MAX_TREE_SIZE[1] / CHUNK_SIZE))
def generate_tree(base):
return {(base[0] + offset[0], base[1] + offset[1]): block for offset, block in TREE_SHAPE.items()}
# Replace everything above with however you want to generate Trees.
# It might be worth factoring that out into a StructureGenerator class.
def get_trees(x, y):
out = []
seed(SEED + x * CHUNK_SIZE + y) # Make sure this function always produces the same output
for _ in range(CHUNK_SIZE // 8): # At most one Tree attempt per 4 blocks
block_x = x * CHUNK_SIZE + randrange(0, CHUNK_SIZE)
grass_y = int(5 - noise.noise2d(block_x * 0.1, 0) * 5) # Same as in generate_chunk
if not 0 <= grass_y - y * CHUNK_SIZE < CHUNK_SIZE: # Tree spot not in this chunk
continue
out.append(generate_tree((block_x, grass_y)))
return out
def generate_chunk(x, y):
chunk_data = {}
# ... Your old code
for ox in range(-CHUNKS_TO_CHECK[0], CHUNKS_TO_CHECK[0] + 1):
for oy in range(-CHUNKS_TO_CHECK[1], CHUNKS_TO_CHECK[1] + 1):
# For each Chunk around us (and ourself), check which trees there are.
trees = get_trees(x + ox, y + oy)
for tree in trees:
for block, block_name in tree.items():
if 0<=block[0]-x*CHUNK_SIZE<CHUNK_SIZE and 0<=block[0]-x*CHUNK_SIZE<CHUNK_SIZE:
# This block is in this chunk
chunk_data[block] = block_name
return chunk_data

Pygame bug, music will not play properly as it just loops the beginning part

I'm working on a basic shooter game in Pygame (I'm following a tutorial.) I decided to add background music to keep things nice.
Code (quite long!):
import pygame
from pygame import image as sprite
pygame.init()
dow = pygame.display.set_mode((1000, 700))
pygame.display.set_caption("PY.Touhou")
clock = pygame.time.Clock()
rightSprites = [sprite.load('ness-right1.png'),
pygame.image.load('ness-right2.png'),
pygame.image.load('ness-right3.png'),
pygame.image.load('ness-right4.png'),
pygame.image.load('ness-right5.png'),
pygame.image.load('ness-right6.png'),
pygame.image.load('ness-right7.png'),
pygame.image.load('ness-right8.png')
]
leftSprites = [
sprite.load('ness-left1.png'),
sprite.load('ness-left2.png'),
sprite.load('ness-left3.png'),
sprite.load('ness-left4.png'),
sprite.load('ness-left5.png'),
sprite.load('ness-left6.png'),
sprite.load('ness-left7.png'),
sprite.load('ness-left8.png')
]
scene = pygame.image.load('bg.png')
idle = pygame.image.load('ness-idle.png')
class Player(object):
def __init__(self, x_pos, y_pos, w, h):
self.x_pos = x_pos
self.y_pos = y_pos
self.w = w
self.h = h
self.velocity = 15
self.JUMPbool = False
self.atleft = False
self.atright = False
self.steps = 0
self.jumpheight = 12
self.hitbox = (self.x_pos + 50, self.y_pos, 73, 227)
self.still = True
def move(self):
if self.steps + 1 >= 24:
self.steps = 0
if not self.still:
if self.atleft:
dow.blit(leftSprites[self.steps // 4], (self.x_pos, self.y_pos))
self.steps += 1
elif self.atright:
dow.blit(rightSprites[self.steps // 4], (self.x_pos, self.y_pos))
self.steps += 1
else:
if self.atright:
dow.blit(rightSprites[1], (self.x_pos, self.y_pos))
else:
dow.blit(leftSprites[1], (self.x_pos, self.y_pos))
self.hitbox = (self.x_pos + 50, self.y_pos, 73, 227)
# pygame.draw.rect(dow, (0, 0, 255), self.hitbox, 2)
class IceBullet(object):
def __init__(self, x_pos, y_pos, radius, color, direction):
self.x_pos = x_pos
self.y_pos = y_pos
self.radius = radius
self.color = color
self.direction = direction
self.velocity = 8 * direction
def summon(self):
pygame.draw.circle(dow, self.color, (self.x_pos, self.y_pos), self.radius)
class EnemyCirno(object):
erightSprites = [sprite.load('e-right%s.png' % pic) for pic in range(1, 9)]
eleftSprites = [sprite.load('e-left%s.png' % pic) for pic in range(1, 9)]
def __init__(self, x_pos, y_pos, w, h, ending):
self.x_pos = x_pos
self.y_pos = y_pos
self.w = w
self.h = h
self.ending = ending
self.path = [self.x_pos, self.ending]
self.hitbox = (self.x_pos + 50, self.y_pos, 73, 227)
self.steps = 0
self.health = 20
self.isAlive = True
self.velocity = 4
def summon(self):
self.move()
if self.isAlive:
if self.steps + 1 >= 24:
self.steps = 0
if self.velocity > 0:
dow.blit(self.erightSprites[self.steps // 4], (self.x_pos, self.y_pos))
self.steps += 1
else:
dow.blit(self.eleftSprites[self.steps // 4], (self.x_pos, self.y_pos))
self.steps += 1
pygame.draw.rect(dow, (255, 10, 0), (100, 110, 400, 20))
pygame.draw.rect(dow, (5, 255, 10), (100, 110, 200 - (20 * (10 - self.health)), 20))
enemy_health = italicStyle.render('Enemy Health', 1, (255, 0, 0))
dow.blit(enemy_health, (100, 150))
self.hitbox = (self.x_pos + 50, self.y_pos, 73, 227)
# pygame.draw.rect(dow, (0, 0, 0), self.hitbox, 2)
def move(self):
if self.velocity > 0:
if self.x_pos + self.velocity < self.path[1]:
self.x_pos += self.velocity
else:
self.velocity = self.velocity * -1
self.steps = 0
else:
if self.x_pos - self.velocity > self.path[0]:
self.x_pos += self.velocity
else:
self.velocity = self.velocity * -1
self.steps = 0
def hit(self):
if self.health > 1:
self.health -= 0.50
hit = pygame.mixer.Sound('hit.wav')
hit.play()
else:
self.isAlive = False
dead = pygame.mixer.Sound('death_sound.wav')
dead.play()
# Main starting loop.
letterStyle = pygame.font.SysFont('Bookman Old Style', 50)
italicStyle = pygame.font.SysFont('Bookman Old Style', 30, False, True)
cirno = Player(300, 470, 160, 233)
score = 0
evilCirno = EnemyCirno(50, 470, 160, 233, 800)
maxShots = 0
bullets = []
running = True
while running is True:
clock.tick(24)
pygame.time.delay(10)
music = pygame.mixer.music.load('main_theme.wav') # And here is where the bug begins!
pygame.mixer.music.play()
for events in pygame.event.get():
if events.type == pygame.QUIT:
running = False
key = pygame.key.get_pressed()
def update_screen():
dow.blit(scene, (0, 0))
textdisplay = letterStyle.render('Score: ' + str(score), 1, (0, 255, 255))
won = italicStyle.render("Enemy defeated!", 1, (10, 255, 19))
dow.blit(textdisplay, (50, 30))
if not evilCirno.isAlive:
dow.blit(won, (100, 150))
cirno.move()
evilCirno.summon()
for shot in bullets:
shot.summon()
pygame.display.update()
if maxShots > 0:
maxShots += 1
if maxShots > 5:
maxShots = 0
for shot in bullets:
if evilCirno.isAlive:
if shot.y_pos - shot.radius < evilCirno.hitbox[1] + evilCirno.hitbox[3] and shot.y_pos + shot.radius > \
evilCirno.hitbox[1]:
if shot.x_pos + shot.radius > evilCirno.hitbox[0] and shot.x_pos - shot.radius < evilCirno.hitbox[0] + \
evilCirno.hitbox[2]:
evilCirno.hit()
score += 50
bullets.pop(bullets.index(shot))
if 1000 > shot.x_pos > 0:
shot.x_pos += shot.velocity
else:
bullets.pop(bullets.index(shot))
if key[pygame.K_z] and maxShots == 0:
shoot = pygame.mixer.Sound('bullet.wav')
shoot.play()
if cirno.atleft:
facing = -1
else:
facing = 1
if len(bullets) < 5:
bullets.append(
IceBullet(round(cirno.x_pos + cirno.w // 2), round(cirno.y_pos + cirno.h // 1.2), 20, (0, 0, 255),
facing))
maxShots = 1
if key[pygame.K_LEFT] and cirno.x_pos > cirno.velocity: # Will stop ASAP when reached
cirno.x_pos -= cirno.velocity
cirno.atleft = True
cirno.atright = False
cirno.still = False
elif key[
pygame.K_RIGHT] and cirno.x_pos < 1000 - cirno.velocity - cirno.w: # If it goes past this, it will stop ASAP, as well
cirno.x_pos += cirno.velocity
cirno.atright = True
cirno.atleft = False
cirno.still = False
else:
cirno.steps = 0
cirno.still = True
if not cirno.JUMPbool:
if key[pygame.K_UP]:
cirno.JUMPbool = True
cirno.steps = 0
else:
if cirno.jumpheight >= -12: # What to do if pygame.K_SPACE is pressed down.
cirno.y_pos -= (cirno.jumpheight * abs(cirno.jumpheight)) * 0.5
cirno.jumpheight -= 1
else:
cirno.jumpheight = 12
cirno.JUMPbool = False
update_screen()
pygame.quit()
So far, the music actually does play, as well as the sounds. Though the problem is that it just loops the beginning part of the music over and over (which is only the first few seconds), so what you get is just something that sounds like a broken MP3 player.
(for reference the music is 1:45. Even weirder is that I've seen people use it with no issues.)
Is there any way to fix this bug? Thanks for your help :)
Do you see the bug here?
while running is True:
[...]
music = pygame.mixer.music.load('main_theme.wav')
pygame.mixer.music.play()
It's re-loading and re-playing replaying the music inside your code main loop. That is every single updated frame of game-play,
Load and play music outside your main loop:
music = pygame.mixer.music.load('main_theme.wav')
pygame.mixer.music.play()
while running is True:
[...]
Your code can then call the mixer.get_busy() function too see if the music has stopped, and do something about it.
You probably also want to investigate PyGame Sound mixer channels, so your sound effects mix in with the background music properly.

Using NEAT can I get a better result for snake game?

I have created snake game in python and am using NEAT to create an Neural network to play the game. I have put a lot of time into playing around with the config file and fitness functions but the average fitness doesn't increase. I would really love if someone could give some advice.
I have attached the python file for the game and also the NEAT config file I used and commented the code for readability.
import os
import random
import pygame
import neat
from scipy.spatial import distance
gen = 0
snakes = []
snacks = []
rows = 20
def draw_grid(w, surface):
size_btwn = w // rows
x, y = 0, 0
for l in range(rows):
x = x + size_btwn
y = y + size_btwn
pygame.draw.line(surface, (255, 255, 255), (x, 0), (x, w))
pygame.draw.line(surface, (255, 255, 255), (0, y), (w, y))
def redraw_window(surface):
global rows, snakes, snacks
surface.fill((0, 0, 0))
for i, s1 in enumerate(snakes):
s1.draw(surface)
s1.snack.draw(surface)
draw_grid(width, surface)
pygame.display.update()
class Cube:
rows = 20
w = 500
def __init__(self, position, color=(255, 0, 0)):
self.pos = position
self.color = color
def draw(self, surface):
dis = self.w // self.rows
i = self.pos[0]
j = self.pos[1]
pygame.draw.rect(surface, self.color, (i * dis + 1, j * dis + 1, dis - 2, dis - 2))
class Snake:
def __init__(self, pos):
self.head = Cube(pos)
self.body = []
self.body.append(self.head)
self.dirnx = 0
self.dirny = 1
self.added_cube = False
self.snack = Cube(randomSnack(rows), color=(0, 255, 0))
self.time = 50
def change_dir(self, direction_x, direction_y):
self.dirnx = direction_x
self.dirny = direction_y
def move(self):
self.head = self.body[-1]
# new_x, new_y = (self.head.pos[0] + self.dirnx) % rows, (self.head.pos[1] + self.dirny) % rows # add this to stop death from wall hit
new_x, new_y = (self.head.pos[0] + self.dirnx), (self.head.pos[1] + self.dirny)
c1 = Cube([new_x, new_y])
self.body.append(c1)
if not self.added_cube:
del self.body[0]
self.added_cube = False
def add_cube(self):
self.added_cube = True
def draw(self, surface):
for i, cube in enumerate(self.body):
cube.draw(surface)
def randomSnack(rows):
x = random.randrange(rows)
y = random.randrange(rows)
return [x, y]
def check_dir_changed():
for event in pygame.event.get():
keys = pygame.key.get_pressed()
for snake in snakes:
for key in keys:
if keys[pygame.K_LEFT]:
snake.change_dir(-1, 0)
elif keys[pygame.K_RIGHT]:
snake.change_dir(1, 0)
elif keys[pygame.K_UP]:
snake.change_dir(0, -1)
elif keys[pygame.K_DOWN]:
snake.change_dir(0, 1)
def eval_genomes(genomes, config):
global width, rows, snakes, snacks, gen
gen += 1
width = 500
rows = 20
win = pygame.display.set_mode((width, width))
nets = []
snakes = []
snacks = []
ge = []
for genome_id, genome in genomes:
genome.fitness = 1 # start with fitness level of 1
net = neat.nn.FeedForwardNetwork.create(genome, config)
nets.append(net)
start_position_x, start_position_y = random.randrange(0, 20), random.randrange(0, 20)
snakes.append(Snake([start_position_x, start_position_y]))
ge.append(genome)
clock = pygame.time.Clock()
while True and len(snakes) > 0:
pygame.time.delay(50)
clock.tick(10)
check_dir_changed()
for i, snake in enumerate(snakes):
# send info and determine from network what direction to go
output = nets[snakes.index(snake)].activate(
(snake.dirnx, snake.dirny, snake.snack.pos[0], snake.snack.pos[1],
snake.head.pos[0], snake.head.pos[1], distance.euclidean(snake.snack.pos, snake.head.pos),
distance.euclidean(snake.snack.pos[0], snake.head.pos[0]),
distance.euclidean(snake.snack.pos[1], snake.head.pos[1])))
# get the right move to make
max_output = -2
best_output = 0
for j, out in enumerate(output):
if out > max_output:
max_output = out
best_output = j
# 0 is right, 1 is left, 2 is down, 3 is up
if best_output == 0:
snake.change_dir(1, 0)
elif best_output == 1:
snake.change_dir(-1, 0)
elif best_output == 2:
snake.change_dir(0, 1)
elif best_output == 3:
snake.change_dir(0, -1)
snake.move()
# take 1 from the current snake's time, this stops snakes running around forever
snake.time -= 1
# add fitness depending how close snake is to the snack
ge[snakes.index(snake)].fitness += 20 - distance.euclidean(snake.snack.pos, snake.head.pos)
# if snake head eats snack
if snake.body[-1].pos == snake.snack.pos:
ge[snakes.index(snake)].fitness += 1000
snake.time += 40 # give snake more time since they got a snack
snake.add_cube()
snake.snack = Cube(randomSnack(rows), color=(0, 255, 0))
# if snake ran out of time without getting snack
if snake.time < 1:
ge[snakes.index(snake)].fitness -= 5
nets.pop(snakes.index(snake))
ge.pop(snakes.index(snake))
snakes.pop(snakes.index(snake))
break
# if snake hits a wall
if snake.body[-1].pos[0] > 20 or snake.body[-1].pos[0] < 0 or snake.body[-1].pos[1] > 20 or snake.body[-1].pos[1] < 0:
ge[snakes.index(snake)].fitness -= 1000
nets.pop(snakes.index(snake))
ge.pop(snakes.index(snake))
snakes.pop(snakes.index(snake))
break
# if snake eats itself
for x in range(len(snake.body) - 1):
if snake.body[x].pos == snake.body[-1].pos:
ge[snakes.index(snake)].fitness -= 10
nets.pop(snakes.index(snake))
ge.pop(snakes.index(snake))
snakes.pop(snakes.index(snake))
print('Score: ', len(snake.body))
break
redraw_window(win)
pass
def run(config_file):
config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction,
neat.DefaultSpeciesSet, neat.DefaultStagnation,
config_file)
# Create the population, which is the top-level object for a NEAT run.
p = neat.Population(config)
# Add a stdout reporter to show progress in the terminal.
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
# p.add_reporter(neat.Checkpointer(5))
# Run for up to 50 generations.
winner = p.run(eval_genomes, 100)
# show final stats
print('\nBest genome:\n{!s}'.format(winner))
if __name__ == '__main__':
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, 'config-feedforward.txt')
run(config_path)
[NEAT]
fitness_criterion = max
fitness_threshold = 10000000
pop_size = 20
reset_on_extinction = True
[DefaultGenome]
# node activation options
activation_default = sigmoid
activation_mutate_rate = 0.05
activation_options = sigmoid gauss
#abs clamped cube exp gauss hat identity inv log relu sigmoid sin softplus square tanh
# node aggregation options
aggregation_default = random
aggregation_mutate_rate = 0.05
aggregation_options = sum product min max mean median maxabs
# node bias options
bias_init_mean = 0.05
bias_init_stdev = 1.0
bias_max_value = 30.0
bias_min_value = -30.0
bias_mutate_power = 0.5
bias_mutate_rate = 0.7
bias_replace_rate = 0.1
# genome compatibility options
compatibility_disjoint_coefficient = 1.0
compatibility_weight_coefficient = 0.5
# connection add/remove rates
conn_add_prob = 0.5
conn_delete_prob = 0.5
# connection enable options
enabled_default = True
enabled_mutate_rate = 0.5
feed_forward = False
#initial_connection = unconnected
initial_connection = partial_nodirect 0.5
# node add/remove rates
node_add_prob = 0.5
node_delete_prob = 0.2
# network parameters
num_hidden = 0
num_inputs = 9
num_outputs = 4
# node response options
response_init_mean = 1.0
response_init_stdev = 0.05
response_max_value = 30.0
response_min_value = -30.0
response_mutate_power = 0.1
response_mutate_rate = 0.75
response_replace_rate = 0.1
# connection weight options
weight_init_mean = 0.1
weight_init_stdev = 1.0
weight_max_value = 30
weight_min_value = -30
weight_mutate_power = 0.5
weight_mutate_rate = 0.8
weight_replace_rate = 0.1
[DefaultSpeciesSet]
compatibility_threshold = 2.5
[DefaultStagnation]
species_fitness_func = max
max_stagnation = 50
species_elitism = 0
[DefaultReproduction]
elitism = 3
survival_threshold = 0.3

How do I add a score tracker?

So I want it to count the score every time the snake eats a candy.
I haven't tried much, but I tried to find existing codes and adding them to mine but that just broke the game. I also tried to make my own score board by watching a tutorial, but I don't know where the code should go like at the beginning or end.
import pygame
import random
score = 0
welcome = ("Welcome to our snake game")
print(welcome)
class cube:
height = 20
w = 500
def __init__(movee,start,x=1,y=0,color=(0,0,0)):
movee.pos = start
movee.x = 1
movee.y = 0
movee.color = color
def move(movee, x, y):
movee.x = x
movee.y = y
movee.pos = (movee.pos[0] + movee.x, movee.pos[1] + movee.y)
def draw(movee, surface, eyes=False):
leng = movee.w // movee.height
i = movee.pos[0]
j = movee.pos[1]
pygame.draw.rect(surface, movee.color, (i*leng+1,j*leng+1, leng-2, leng-2))
class snake:
body = []
turns = {}
def __init__(movee, color, pos):
movee.color = color
movee.head = cube(pos)
movee.body.append(movee.head)
def move(movee):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
keys = pygame.key.get_pressed()
for key in keys:
if keys[pygame.K_LEFT]:
movee.x = -1
movee.y = 0
movee.turns[movee.head.pos[:]] = [movee.x, movee.y]
elif keys[pygame.K_RIGHT]:
movee.x = 1
movee.y = 0
movee.turns[movee.head.pos[:]] = [movee.x, movee.y]
elif keys[pygame.K_UP]:
movee.x = 0
movee.y = -1
movee.turns[movee.head.pos[:]] = [movee.x, movee.y]
elif keys[pygame.K_DOWN]:
movee.x = 0
movee.y = 1
movee.turns[movee.head.pos[:]] = [movee.x, movee.y]
for i, c in enumerate(movee.body):
p = c.pos[:]
if p in movee.turns:
turn = movee.turns[p]
c.move(turn[0],turn[1])
if i == len(movee.body)-1:
movee.turns.pop(p)
else:
if c.x == -1 and c.pos[0] <= 0: c.pos = (c.height-1, c.pos[1])
elif c.x == 1 and c.pos[0] >= c.height-1: c.pos = (0,c.pos[1])
elif c.y == 1 and c.pos[1] >= c.height-1: c.pos = (c.pos[0], 0)
elif c.y == -1 and c.pos[1] <= 0: c.pos = (c.pos[0],c.height-1)
else: c.move(c.x,c.y)
def add(movee):
tail = movee.body[-1]
dx, dy = tail.x, tail.y
if dx == 1 and dy == 0:
movee.body.append(cube((tail.pos[0]-1,tail.pos[1])))
elif dx == -1 and dy == 0:
movee.body.append(cube((tail.pos[0]+1,tail.pos[1])))
elif dx == 0 and dy == 1:
movee.body.append(cube((tail.pos[0],tail.pos[1]-1)))
elif dx == 0 and dy == -1:
movee.body.append(cube((tail.pos[0],tail.pos[1]+1)))
movee.body[-1].x = dx
movee.body[-1].y = dy
def draw(movee, surface):
for i, c in enumerate(movee.body):
if i ==0:
c.draw(surface, True)
else:
c.draw(surface)
def drawingAGrid(w, height, surface):
sizein = w // height
x = 0
y = 0
for l in range(height):
x = x + sizein
y = y + sizein
def redrawGrid(surface):
global height, width, s, snack
surface.fill((255,255,255))
s.draw(surface)
snack.draw(surface)
drawingAGrid(width, height, surface)
pygame.display.update()
def Candy(height, item):
positions = item.body
while True:
x = random.randrange(height)
y = random.randrange(height)
if len(list(filter(lambda z:z.pos == (x,y), positions))) > 0:
continue
else:
break
return (x,y)
def gameloop():
global width, height, s, snack, x_pos, y_pos, reset
width = 500
height = 20
win = pygame.display.set_mode((width, width))
s = snake((255, 0, 0), (10, 10))
snack = cube(Candy(height, s), color=(0, 0, 0))
flag = True
clock = pygame.time.Clock()
x_pos, y_pos = s.body[0].pos
while flag:
pygame.time.delay(50)
clock.tick(7)
s.move()
x, y = s.body[0].pos
if not -1 <= x - x_pos <= 1 or not -1 <= y - y_pos <= 1:
movee.reset((10,10))
x_pos, y_pos = s.body[0].pos
if s.body[0].pos == snack.pos:
s.add()
snack = cube(Candy(height, s), color=(0, 0, 0))
redrawGrid(win)
gameloop()
I just want like a scoreboard in any of the corners counting the score.
Use pygame.freetype to render text. e,g, crated a pygame.freetype.SysFont object:
import pygame.freetype
pygame.init()
font = pygame.freetype.SysFont('Times New Roman', 30)
The score is the number of body parts. Use str to convert a number to a text and .render() to render a text to a pygame.Surface object:
score = len(s.body)
text_surf, text_rect = font.render(str(score), (255, 0, 0), size=30)
Define a margin to the border of the window, calculate the text position (e.g. bottom right) and .blit the text to the window surfrace:
margin = 10
text_pos = (width - text_rect.width - margin, width - text_rect.height - margin)
surface.blit(text_surf, text_pos)
Function redrawGrid:
def redrawGrid(surface):
global height, width, s, snack
surface.fill((255,255,255))
s.draw(surface)
snack.draw(surface)
drawingAGrid(width, height, surface)
score = len(s.body)
text_surf, text_rect = font.render(str(score), (255, 0, 0), size=30)
margin = 10
text_pos = (width - text_rect.width - margin, width - text_rect.height - margin)
surface.blit(text_surf, text_pos)
pygame.display.update()

Categories

Resources