I'm working on creating an AI to play the classic game Snake, and have implemented it somewhat successfully, however every so often the program crashes upon reaching its target. I'm using a modified version of A* for pathfinding and pygame for the graphics.
import numpy as np
import pygame
import random
# Set constants for program
GRID_WIDTH = 25
GRID_HEIGHT = 25
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
displayX = 500
displayY = 500
SCALEX = displayX/GRID_WIDTH
SCALEY = displayY/GRID_HEIGHT
# Setyp pygame
gameDisplay = pygame.display.set_mode((displayX,displayY))
pygame.display.set_caption('Snake')
clock = pygame.time.Clock()
# Mainly stores values at each point in the game area
class Block():
def __init__(self, x, y, val):
self.x = x
self.y = y
self.val = val # Stores what type this block is e.g. snake or fruit
self.f = 0 # Values for A-Star Algorithm
self.g = 0
self.h = 0
def get_value(self):
return self.val
def set_value(self, new_val):
self.val = new_val
def set_cost(self, g, h):
self.g = g
self.h = h
self.f = self.h - self.g
def set_parent(self, parent):
self.parent = parent # Used for retracing the path
class Snake():
def __init__(self, x, y):
self.x = x
self.y = y
self.L = 4 # Length of snake
self.body = [] # List of all parts in snake
self.body.append([x, y])
self.path = None
def move(self, board):
print(self.path)
if self.path == None or self.path == []: # Check if a path exists
self.A_Star(board) # If not calculate path
else:
self.x = self.path[0][0] # Move to next point in path then remove it from list
self.y = self.path[0][1]
self.path.pop(0)
self.body.append([self.x, self.y]) # Add body part
self.body = self.body[-self.L:]
def get_snake(self):
return self.body[-self.L:]
def get_head(self):
return [self.x, self.y]
def eat(self): # Increase length and reset path
self.L += 1
self.path = None
def A_Star(self, board): # Modified version of A Star to prioritise moving away from target
start = board.get_grid()[self.x, self.y]
end = board.get_fruit()
if start != None and end != None:
open_list = []
closed_list = []
current = start
open_list.append(current)
while open_list != []:
current = open_list[0]
for block in open_list:
if block.f > current.f:
current = block
open_list.remove(current)
closed_list.append(current)
if current == end:
path = self.retrace_path(start, end)
return True
neighbours = board.get_node_neighbours(current)
for neighbour in neighbours:
if neighbour.get_value() != "body" and not neighbour in closed_list:
if not neighbour in open_list:
neighbour.set_cost(neighbour.g, board.get_distance(current, end))
neighbour.set_parent(current)
if not neighbour in open_list:
open_list.append(neighbour)
return False
def retrace_path(self, start, end):
current = end
path = []
while current != start:
path.append([current.x, current.y])
current = current.parent
self.path = path
self.path.reverse()
'''def survive(self, board):
neighbours = board.get_node_neighbours(board.get_grid()[self.x, self.y])
for neighbour in neighbours:
if neighbour.val == "empty":
self.path = [neighbour.x, neighbour.y]
break'''
class Board():
def __init__(self, snake):
self.grid = np.empty((GRID_WIDTH, GRID_HEIGHT), dtype=object)
for x in range(0, GRID_WIDTH):
for y in range(0, GRID_HEIGHT):
self.grid[x,y] = Block(x, y, "empty") # 2D Array containing all blocks
self.fruit = self.new_fruit(snake) # Generate new fruit
self.score = 0
def check_fruit(self, snake): # Check collision between snake and fruit
snake_head = snake.get_head()
if snake_head[0] == self.fruit[0] and snake_head[1] == self.fruit[1]:
snake.eat()
self.score += 1
self.fruit = self.new_fruit(snake)
def check_death(self, snake): # Check to see if snake is dead
snake_head = snake.get_head()
snake_body = snake.get_snake()
if snake_head[0] >= GRID_WIDTH or snake_head[0] < 0 or snake_head[1] >= GRID_HEIGHT or snake_head[1] < 0:
return True
collisions = 0
for part in snake_body:
if snake_head == part:
collisions += 1
if collisions == 2:
return True
def draw(self, snake): # Draw everything to screen
self.grid = np.empty((GRID_WIDTH, GRID_HEIGHT), dtype=object)
for x in range(0, GRID_WIDTH):
for y in range(0, GRID_HEIGHT):
self.grid[x,y] = Block(x, y, "empty")
for part in snake.get_snake():
self.grid[part[0], part[1]].set_value("body")
self.grid[snake.get_head()[0], snake.get_head()[1]].set_value("head")
self.grid[self.fruit[0], self.fruit[1]].set_value("fruit")
for x in range(0, GRID_WIDTH):
for y in range(0, GRID_HEIGHT):
if self.grid[x, y].get_value() == "fruit":
pygame.draw.rect(gameDisplay,RED,(x*SCALEX,y*SCALEY,SCALEX,SCALEY))
elif self.grid[x, y].get_value() == "body" or self.grid[x, y].get_value() == "head":
pygame.draw.rect(gameDisplay,WHITE,(x*SCALEX,y*SCALEY,SCALEX,SCALEY))
def new_fruit(self, snake): # Generate a new fruit location
complete = False
fail = False
while not complete:
fruit_loc = [random.randint(0, GRID_WIDTH-1), random.randint(0, GRID_HEIGHT-1)]
for part in snake.get_snake(): # Check that fruit is not in snake
if part == fruit_loc:
fail = True
if not fail:
complete = True
return fruit_loc
def get_node_neighbours(self, block): # Get surrounding blocks from block
neighbours = []
options = [[-1, 0], [1, 0], [0, -1], [0, 1]]
for option in options:
checkX = block.x + option[0]
checkY = block.y + option[1]
if checkX >= 0 and checkX < GRID_WIDTH and checkY >= 0 and checkY < GRID_HEIGHT:
neighbours.append(self.grid[checkX,checkY])
return neighbours
def get_distance(self, start, end): # Get distance between two points
dx = abs(start.x - end.x)
dy = abs(start.y - end.y)
return dx + dy - 1
def get_grid(self):
return self.grid
def get_fruit(self):
return self.grid[self.fruit[0], self.fruit[1]]
def main():
snake = Snake(0, 0)
board = Board(snake)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
snake.move(board) # Move snake
if board.check_death(snake): # End program if snake is dead
running = False
break
board.check_fruit(snake) # Call fruit check
gameDisplay.fill(BLACK) # Draw to screen
board.draw(snake)
pygame.display.update()
clock.tick(100)
main()
pygame.quit()
No error messages appear and the only way to tell that the program has stopped is that pygame no longer allows you to quit out and crashes instead. As far as I can tell the point at which it happens if completely random but occurs on most rounds. The snake will reach the fruit but will stop right before eating it.
The issue is the method new_fruit in the class Board. You've generated and endless loop. Actually if the random position fails at the first try, fail will stay True forever.
fail = False has to be set in the outer loop, rather than before the loop:
class Board():
# [...]
def new_fruit(self, snake): # Generate a new fruit location
complete = False
# fail = False
while not complete:
fail = False # <----
fruit_loc = [random.randint(0, GRID_WIDTH-1), random.randint(0, GRID_HEIGHT-1)]
for part in snake.get_snake(): # Check that fruit is not in snake
if part == fruit_loc:
fail = True
if not fail:
complete = True
return fruit_loc
Replace all your (x*SCALEX,y*SCALEY,SCALEX,SCALEY) to (int(x*SCALEX),int(y*SCALEY),int(SCALEX),int(SCALEY))
Related
So I tried creating Conway's game of life in python with pygame. I made this without watching any tutorials, which is probably why it is so broken. It seems to be working fine, but when I creates a glider it seems to just break after a few generations. I looked at some other posts about my problem and added their solutions but that didn't make it work either. I know this is a lot to ask for, but can someone at least identify the problem.
Here is my code. I expected the glider to function as do they are supposed to, but it ended up just breaking in a few generations
Code:
main.py:
from utils import *
from grid import Grid
running = True
t = Grid(30)
while running:
pygame.display.set_caption(f'Conways Game of Life <Gen {t.generations}>')
clock.tick(200)
screen.fill(background_colour)
if not t.started:
t.EditMode()
else:
t.Update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()`
grid.py:
import cell
from utils import *
class Grid:
def __init__(self, size):
self.cells = []
self.cellSize = size
self.generations = 0
self.tick = 1
self.started = False
self.GenerateGrid()
def GenerateGrid(self):
x, y = 0, 0
while y < screen.get_height():
while x < screen.get_width():
c = cell.Cell(self, (x,y), self.cellSize)
self.cells.append(c)
x+=self.cellSize
x = 0
y+=self.cellSize
def EditMode(self):
self.Draw()
if self.started:
return
for cell in self.cells:
if pygame.mouse.get_pressed()[0]:
if cell.rect.collidepoint(pygame.mouse.get_pos()):
cell.state = 1
if pygame.mouse.get_pressed()[2]:
if cell.rect.collidepoint(pygame.mouse.get_pos()):
cell.state = 0
keys = pygame.key.get_pressed()
if keys[pygame.K_RETURN]:
self.started = True
def Draw(self):
for cell in self.cells:
cell.Draw()
def Update(self):
self.Draw()
self.tick -= 0.05
if self.tick < 0:
for cell in self.cells:
cell.UpdateState()
for cell in self.cells:
cell.state = cell.nextState
self.tick = 1
self.generations+=1
cell.py
from utils import *
class Cell:
def __init__(self, grid, position:tuple, size):
self.grid = grid
self.size = size
self.position = pygame.Vector2(position[0], position[1])
self.rect = pygame.Rect(self.position.x, self.position.y, self.size, self.size)
self.state = 0
self.nextState = self.state
def Draw(self):
pygame.draw.rect(screen, (0,0,0), self.rect)
if self.state == 0:
pygame.draw.rect(screen, (23,23,23), (self.position.x+4, self.position.y+4, self.size-4, self.size-4))
else:
pygame.draw.rect(screen, (255,255,255), (self.position.x+4, self.position.y+4, self.size-4, self.size-4))
def UpdateState(self):
rect = pygame.Rect(self.position.x-self.size, self.position.y-self.size, self.size*3, self.size*3)
pygame.draw.rect(screen, (0,0,0), rect)
targetCells = []
for c in self.grid.cells:
if rect.colliderect(c.rect):
targetCells.append(c)
livingAmt = 0
for c in targetCells:
if c.rect.x == self.rect.x and c.rect.y == self.rect.y:
continue
if c.state == 1:
livingAmt+=1
if self.state == 1:
if livingAmt > 3 or livingAmt <2:
self.nextState = 0
if self.state ==0:
if livingAmt == 3:
self.nextState =1
utils.py
import pygame
background_colour = (23, 23, 23)
screen = pygame.display.set_mode((900, 900))
clock = pygame.time.Clock()
running = True
Your function UpdateState both counts a cell's neighbors and updates the cell's state. Since you call that function in a loop, both are done together, which does not work, as explained here. You must split the "count" phase from the "update state" phase.
I have built an A* Pathfinding algorithm that finds the best route from Point A to Point B, there is a timer that starts and ends post execute of the algorithm and the path is draw, this is parsed to a global variable. so it is accessable when i run the alogrithm more than once (to gain an average time).
the global variable gets added to a list, except when i run the algorithm 5 times, only 4 values get added (I can see 5 times being recorded as the algorithm prints the time after completion). when displaying the list it always misses the first time, and only has times 2,3,4,5 if i run the algorithm 5 times. here is main.py
import astar
import pygame
def main():
timing_list = []
WIDTH = 800
WIN = pygame.display.set_mode((WIDTH, WIDTH))
for x in range(0, 4):
astar.main(WIN, WIDTH)
timing_list.insert(x, astar.full_time)
print(timing_list)
if __name__ == "__main__":
main()
and
astar.py
from queue import PriorityQueue
from random import randint
import pygame
from timing import Timing
WIDTH = 800
WIN = pygame.display.set_mode((WIDTH, WIDTH))
pygame.display.set_caption("A* Path Finding Algorithm")
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 255, 0)
YELLOW = (255, 255, 0)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
PURPLE = (128, 0, 128)
ORANGE = (255, 165, 0)
GREY = (128, 128, 128)
TURQUOISE = (64, 224, 208)
global full_time
class Spot:
def __init__(self, row, col, width, total_rows):
self.row = row
self.col = col
self.x = row * width
self.y = col * width
self.color = WHITE
self.neighbors = []
self.width = width
self.total_rows = total_rows
def get_pos(self):
return self.row, self.col
def is_closed(self):
return self.color == RED
def is_open(self):
return self.color == GREEN
def is_barrier(self):
return self.color == BLACK
def is_start(self):
return self.color == ORANGE
def is_end(self):
return self.color == TURQUOISE
def reset(self):
self.color = WHITE
def make_start(self):
self.color = ORANGE
def make_closed(self):
self.color = RED
def make_open(self):
self.color = GREEN
def make_barrier(self):
self.color = BLACK
def make_end(self):
self.color = TURQUOISE
def make_path(self):
self.color = PURPLE
def draw(self, win):
pygame.draw.rect(win, self.color, (self.x, self.y, self.width, self.width))
def update_neighbors(self, grid):
self.neighbors = []
if self.row < self.total_rows - 1 and not grid[self.row + 1][self.col].is_barrier(): # DOWN
self.neighbors.append(grid[self.row + 1][self.col])
if self.row > 0 and not grid[self.row - 1][self.col].is_barrier(): # UP
self.neighbors.append(grid[self.row - 1][self.col])
if self.col < self.total_rows - 1 and not grid[self.row][self.col + 1].is_barrier(): # RIGHT
self.neighbors.append(grid[self.row][self.col + 1])
if self.col > 0 and not grid[self.row][self.col - 1].is_barrier(): # LEFT
self.neighbors.append(grid[self.row][self.col - 1])
def __lt__(self, other):
return False
def generate_num(x, y):
return randint(x, y)
def h(p1, p2):
x1, y1 = p1
x2, y2 = p2
return abs(x1 - x2) + abs(y1 - y2)
def reconstruct_path(came_from, current, draw):
while current in came_from:
current = came_from[current]
current.make_path()
draw()
def algorithm(draw, grid, start, end):
count = 0
open_set = PriorityQueue()
open_set.put((0, count, start))
came_from = {}
g_score = {spot: float("inf") for row in grid for spot in row}
g_score[start] = 0
f_score = {spot: float("inf") for row in grid for spot in row}
f_score[start] = h(start.get_pos(), end.get_pos())
open_set_hash = {start}
while not open_set.empty():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
current = open_set.get()[2]
open_set_hash.remove(current)
if current == end:
reconstruct_path(came_from, end, draw)
end.make_end()
return True
for neighbor in current.neighbors:
temp_g_score = g_score[current] + 1
if temp_g_score < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = temp_g_score
f_score[neighbor] = temp_g_score + h(neighbor.get_pos(), end.get_pos())
if neighbor not in open_set_hash:
count += 1
open_set.put((f_score[neighbor], count, neighbor))
open_set_hash.add(neighbor)
neighbor.make_open()
draw()
if current != start:
current.make_closed()
return False
def make_grid(rows, width):
grid = []
gap = width // rows
for i in range(rows):
grid.append([])
for j in range(rows):
spot = Spot(i, j, gap, rows)
grid[i].append(spot)
return grid
def draw_grid(win, rows, width):
gap = width // rows
for i in range(rows):
pygame.draw.line(win, GREY, (0, i * gap), (width, i * gap))
for j in range(rows):
pygame.draw.line(win, GREY, (j * gap, 0), (j * gap, width))
def draw(win, grid, rows, width):
win.fill(WHITE)
for row in grid:
for spot in row:
spot.draw(win)
draw_grid(win, rows, width)
pygame.display.update()
def get_clicked_pos(pos, rows, width):
gap = width // rows
y, x = pos
row = y // gap
col = x // gap
return row, col
def main(win, width):
rows = 50
grid = make_grid(rows, width)
start = None
end = None
t = Timing()
run = True
setup_config = True
while run:
draw(win, grid, rows, width)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
setup_config = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
run = False
setup_config = False
while setup_config:
for x in range(0, 800):
row_pos = generate_num(0, rows - 1)
col_pos = generate_num(0, rows - 1)
spot = grid[row_pos][col_pos]
if not start and spot != end:
start = spot
start.make_start()
elif not end and spot != start:
end = spot
end.make_end()
elif spot != end and spot != start:
spot.make_barrier()
t.start()
for row in grid:
for spot in row:
spot.update_neighbors(grid)
algorithm(lambda: draw(win, grid, rows, width), grid, start, end)
global full_time
full_time = t.stop()
setup_config = False
run = False
pygame.QUIT
main(WIN, WIDTH)
and timing.py my timer class
import time
class TimerError(Exception):
"""A custom exception used to report errors in use of Timer class"""
class Timing:
def __init__(self):
self._start_time = None
def start(self):
"""Start a new timer"""
if self._start_time is not None:
raise TimerError(f"Timer is running. Use .stop() to stop it")
self._start_time = time.perf_counter()
def stop(self):
"""Stop the timer, and report the elapsed time"""
if self._start_time is None:
raise TimerError(f"Timer is not running. Use .start() to start it")
elapsed_time = time.perf_counter() - self._start_time
self._start_time = None
print(f"Elapsed time: {elapsed_time:0.4f} seconds")
return elapsed_time
EDIT:
the misterious 5th print is coming from this line, of course
full_time = t.stop()
With 4 iteractions of your main loop, you reach at this line 5 times. That's the reason you have 5 prints.
You are very right on your comment, when you import astar with the command
import astar
on your main.py file, you are executing the main(WIN, WIDTH) that you have at the end of astar.py. Thus resulting in 5 prints.
>>> for i in range(0, 4):
... print(i)
...
0
1
2
3
>>>
Your main for loop iterates 4 times, each time inserting one element to your empty list. By the way, I would to it like this rather than the cumbersome insert.
list.append(astar.full_time) # append inserts at the end of the list
I am surprised that you have 5 of the output of the format
print(f"Elapsed time: {elapsed_time:0.4f} seconds")
By the way, I am also wondering why do you have these statements:
run = True
while run:
run = False
and a similar statement with a boolean named setup_config and a while. You could get yourself a cleaner code by suppressing this while, since it only executes once. Please, correct me if I am wrong or I am missing something from your context.
Notice that you can also remove the ifs related to the pass commands.
def main(win, width):
rows = 50
grid = make_grid(rows, width)
start = None
end = None
t = Timing()
draw(win, grid, rows, width)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pass
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pass
for x in range(0, 800):
row_pos = generate_num(0, rows - 1)
col_pos = generate_num(0, rows - 1)
spot = grid[row_pos][col_pos]
if not start and spot != end:
start = spot
start.make_start()
elif not end and spot != start:
end = spot
end.make_end()
elif spot != end and spot != start:
spot.make_barrier()
t.start()
for row in grid:
for spot in row:
spot.update_neighbors(grid)
algorithm(lambda: draw(win, grid, rows, width), grid, start, end)
global full_time
full_time = t.stop()
pygame.QUIT
The extra print is mostly like coming from a surprising extra iteraction of this for loop
for event in pygame.event.get():
Check the value of event, by printing it and check if there's something unusual there.
I'm building a visualization tool for A* Search in Python and have come across the following error when trying to calculate the g-score of my nodes.
line 115, in g
x1, y1 = s
TypeError: cannot unpack non-iterable method object
See code below:
from graphics import *
import pygame, sys
import math
import numpy as np
from queue import PriorityQueue
pygame.init()
# #set the screen size and captions
size = (500, 500)
rows = 50
margin = 1
screen = pygame.display.set_mode(size)
pygame.display.set_caption("A* Pathfinding")
screen.fill((173, 172, 166))
#define colors
aqua = (0, 255, 255)
black = (0, 0, 0)
blue = (0, 0, 255)
green = (0, 128, 0)
red = (255, 0, 0)
white = (255, 255, 255)
purple = (128, 0, 128)
#initialize PriorityQueue
q = PriorityQueue()
#define all possible states of each node
class Node:
def __init__(self, row, col, width):
self.row = row
self.col = col
self.x = row*width
self.y = col*width
self.color = white
self.width = width
self.neighbors = [(col+1, row), (col-1, row), (col, row+1), (col, row-1)]
#self.neighbors = []
def get_pos(self):
return self.row, self.col
def is_closed(self):
return self.color == red
def is_open(self):
return self.color == aqua
def is_barrier(self):
return self.color == black
def is_start(self):
return self.color == green
def is_end(self):
return self.color == blue
def reset(self):
return self.color == white
def close(self):
self.color = red
def open_node(self):
self.color = blue
def barrier(self):
self.color = black
def start(self):
self.color = green
def end(self):
self.color = aqua
def path(self):
self.color = purple
def init_grid():
board = []
for i in range(rows):
board.append([])
for j in range(rows):
node = Node(i, j, size[0]/rows)
board[i].append(node)
return board
#heuristic
def h(c, end):
x1, y1 = c
x2, y2 = end
return abs(x1 - x2) + abs(y1 - y2)
#distance between current node and start node
def g(c, s):
x1, y1 = s
x2, y2 = c
return abs(x1 - x2) + abs(y1 - y2)
board = init_grid()
barrier = []
frontier = {}
path = {}
#starting conditions using Node class and associated methods
start = board[5][5]
goal = board[30][35]
current = start
count = 0
q.put(0, (start))
#main game loop
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#allows user to draw barriers
elif pygame.mouse.get_pressed()[0]:
pos = pygame.mouse.get_pos()
y_pos = int(pos[0] // (board[0][0].width + margin))
x_pos = int(pos[1] // (board[0][0].width + margin))
board[x_pos][y_pos].barrier()
barrier.append(board[x_pos][y_pos].get_pos())
#draw the grid
for i in range(rows):
for j in range(rows):
color = board[i][j].color
#print(board[i][j].color)
if board[i][j] == start:
board[i][j].start()
elif board[i][j] == goal:
board[i][j].end()
pygame.draw.rect(screen, color, [j*(board[0][0].width + margin), i*(board[0][0].width + margin), board[0][0].width, board[0][0].width])
#game loop
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
while not q.empty():
for neighbor in current.neighbors:
g_temp = g(current.get_pos, start.get_pos) + 1
if g_temp < g(neighbor, start.get_pos()):
f = g_temp + h(current.get_pos(), goal.get_pos())
if neighbor not in frontier:
q.put((f, neighbor))
frontier.add(neighbor)
board[neighbor[0]][neighbor[1]].open_node()
if current != goal:
new = q.get()[1]
path.add(current)
current = board[new[0]][new[1]]
if current == goal:
break
pygame.display.flip()
pygame.quit()
I have tried debugging this issue but I don't understand why it is giving me this error. The function should take in two tuple arguments and return an integer as the g-score, which I believe is what I am passing through in the game loop.
Please let me know if there is some obvious error that I missed here, I am still new to programming. Thank you so much for your time and help!
you have a long piece of code.
It seems you don't pass a tuple to g().
I spotted at least one error: You have to add the "()" after a method call, otherwise you pass the method name instead of the method value to g().
example from your code:
g_temp = g(current.get_pos, start.get_pos) + 1
should be:
g_temp = g(current.get_pos(), start.get_pos()) + 1
This is probably the problem.
I haven't checked all the other lines as this code is very long ;)
Test and come back tell us.
I imagine this is a simple fix and have seen similar questions, but this is really frustrating me. Basically when you run the game, it only advances the simulation on a keypress, and not at a set framerate.
https://pastebin.com/aP6LsMMA
The main code is:
pg.init()
clock = pg.time.Clock()
FPS = 10
# ...
Game = Control()
while not Game.done:
Game.main_loop()
pg.display.update()
clock.tick(FPS)
pg.quit()
and the event handler method - within the Control class - is:
def event_handler(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.KEYDOWN:
self.scene.process_event(event)
The weird thing to me is that, at the bottom of the pastebin, is my old testing code that is not done with classes for Scenes/Control. However, to my eye it should work exactly the same. I've tried putting in the clock both in and out of the Control class to no avail.
Any help (and general tips!) greatly appreciated.
Thanks
process_event() should only change variables which depend on events but it shouldn't update other values which should be updated in every frame. You have to move some elements to new method update() and execute it in every loop.
More or less
class Scene:
def process_event(self, event):
pass
def update(self):
pass
class GamePlayState(Scene):
def process_event(self, event):
self.snake.get_key(event)
def update(self):
self.snake.update()
self.snake.food_check(self.apple)
self.snake.collision_check()
if self.snake.alive == False:
print("GAME OVER")
print(self.snake.points)
self.done = True
class Control:
def update(self):
self.scene.update()
def main_loop(self):
self.event_handler()
self.update()
self.scene_checker()
self.draw()
Full working code
import pygame as pg
import sys
import random
import queue
# TODO: Walls, queue for keypress, scene stuff, 2 player, difficulty(?)
""" ######################
PREAMBLE
###################### """
""" Dictionaries for direction/velocity mapping - stolen from https://github.com/Mekire """
DIRECT_DICT = {"left" : (-1, 0), "right" : (1, 0),
"up" : (0,-1), "down" : (0, 1)}
KEY_MAPPING = {pg.K_LEFT : "left", pg.K_RIGHT : "right",
pg.K_UP : "up", pg.K_DOWN : "down"}
OPPOSITES = {"left" : "right", "right" : "left",
"up" : "down", "down" : "up"}
""" Colour Mapping """
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
DARK_GREY = (70, 70, 70)
GREY = (211, 211, 211)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
COLOUR_MAP = {"snake": GREEN, "apple": RED, "wall": BLACK, "surface": GREY, "background": DARK_GREY }
""" ################
CLASSES
################ """
""" ####################### Object Classes ########################## """
class Square:
""" All other objects in the game will be built up from this """
def __init__(self, pos, colour, length):
self.xi, self.yi = pos # i for index, p for pixel
self.colour = colour
self.length = length
def display(self):
xp, yp = self.sq_to_pixs(self.xi, self.yi) # (x = left side, y = top edge)
pg.draw.rect(screen, self.colour, (xp, yp, self.length, self.length), 0)
def sq_to_pixs(self, x, y):
# Converts index of square to pixel coords
px = (x+1)*(2*MARGIN + SQUARE_SIZE) - MARGIN - SQUARE_SIZE
py = (y+1)*(2*MARGIN + SQUARE_SIZE) - MARGIN
return (px, py)
def index_coords(self): # TODO - remove for direct ref?
return (self.xi, self.yi)
class Arena:
""" A grid within which the game takes place """
def __init__(self, size, square_length, colour):
self.size = size # i.e. number of squares = size**2 for square arena
self.length = square_length # i.e. per square dimension
self.colour = colour
self.squares = [ [] for i in range(self.size) ]
for y in range(self.size):
for x in range(self.size):
self.squares[y].append(Square((x,y), self.colour, self.length))
def display(self):
for y in self.squares:
for square in y:
square.display()
class Snake:
""" Class for the agent(s) """
def __init__(self, pos, colour, square_length):
self.xi, self.yi = pos
self.colour = colour
self.size = 3
self.length = square_length
self.direction = "right"
self.direction_queue = queue.Queue(4) # TODO
self.points = 0
self.growing = False
self.alive = True
self.squares = []
for x in range(self.size): # horizontal initial orientation
self.squares.append(Square((self.xi - x, self.yi), self.colour, self.length))
def display(self):
for square in self.squares:
square.display()
def food_check(self, apple):
if self.squares[0].index_coords() == apple.square.index_coords():
self.growing = True
self.points += apple.points_value
apple.respawn([self])
def collision_check(self, walls = None):
xh, yh = self.squares[0].index_coords()
body = self.squares[-1:0:-1] # going backwards thru array as forwards [0:-1:1] didnt work...
def _collide(obstacles):
for sq in obstacles:
_x, _y = sq.index_coords()
if (_x == xh) and (_y == yh):
self.alive = False
_collide(body)
if walls is not None:
_collide(walls)
def update(self):
# Add new head based on velocity and old head
velocity = DIRECT_DICT[self.direction]
head_coords = [ (self.squares[0].index_coords()[i] + velocity[i]) for i in (0,1) ]
# Wrap around screen if reach the end
for i in (0, 1):
if head_coords[i] < 0:
head_coords[i] = SQUARES_PER_ARENA_SIDE - 1
elif head_coords[i] > SQUARES_PER_ARENA_SIDE - 1:
head_coords[i] = 0
self.squares.insert(0, Square(head_coords, self.colour, self.length))
if self.growing:
self.growing = False
else:
del self.squares[-1]
"""
def queue_key_press(self, key):
for keys in KEY_MAPPING:
if key in keys:
try:
self.direction_queue.put(KEY_MAPPING[keys], block=False)
break
except queue.Full:
pass
"""
class Player(Snake):
""" Human controlled snake via arrow keys """
def __init__(self, pos, colour, size):
Snake.__init__(self, pos, colour, size)
def get_key(self, event):
if event.type == pg.KEYDOWN and event.key in KEY_MAPPING:
new_direction = KEY_MAPPING[event.key]
if new_direction != OPPOSITES[self.direction]:
self.direction = new_direction
class Apple:
""" Food our (veggie) snake is greedily after """
def __init__(self, colour, length, points_value, snake):
self.colour = colour
self.length = length
self.xi, self.yi = self._rand_coords()
self.points_value = points_value
self.square = Square((self.xi, self.yi), self.colour, self.length)
def _rand_coords(self):
rand_num = lambda x: random.randint(0, x)
_x = rand_num(SQUARES_PER_ARENA_SIDE-1)
_y = rand_num(SQUARES_PER_ARENA_SIDE-1)
return _x, _y
def respawn(self, obstacles):
_x, _y = self._rand_coords()
for ob in obstacles:
for sq in ob.squares:
while sq.index_coords() == (_x, _y):
_x, _y = self._rand_coords()
self.square.xi, self.square.yi = _x, _y
def display(self):
self.square.display()
""" ################ SCENES ####################### """
class Scene:
""" Overload most of this - barebones structure
A bit pointless in current state but easily expanded """
def __init__(self):
self.done = False
def when_activated(self):
pass
def reset(self):
self.done = False
def render(self):
pass
def process_event(self, event):
pass
def update(self):
pass
class StartUp(Scene):
def __init__(self):
Scene.__init__(self)
def render(self):
# test placeholder
pass
def when_activated(self):
print("Press any key to continue")
def process_event(self, event):
if event.type == pg.KEYDOWN:
self.done = True
class GamePlayState(Scene):
def __init__(self):
Scene.__init__(self)
self.arena = Arena(SQUARES_PER_ARENA_SIDE, SQUARE_SIZE, COLOUR_MAP["surface"])
self.snake = Player(SNAKE_START, COLOUR_MAP["snake"], SQUARE_SIZE)
self.apple = Apple(COLOUR_MAP["apple"], SQUARE_SIZE, 1, self.snake)
self.font = pg.font.SysFont("courier new", 50)
def render(self):
screen.fill(COLOUR_MAP["background"])
self.arena.display()
self.apple.display()
self.snake.display()
text = self.font.render(str(self.snake.points), True, [255,255,255])
screen.blit(text, (500, 400))
def process_event(self, event):
self.snake.get_key(event)
def update(self):
self.snake.update()
self.snake.food_check(self.apple)
self.snake.collision_check()
if self.snake.alive == False:
print("GAME OVER")
print(self.snake.points)
self.done = True
""" ################## CONTROL CLASS #########################"""
class Control:
def __init__(self):
#self.clock = pg.time.Clock()
#self.fps = FPS
self.done = False
self.scene_array = [StartUp(), GamePlayState()]
self.scene_index = 0 # dirty way whilst dict method needs tinkering
#self.scene_dict = {"START": StartUp(), "GAME": GamePlayState()} #TODO
self.scene = self.scene_array[self.scene_index]
#self.scene = self.scene_dict["START"]
self.scene.when_activated()
def event_handler(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.KEYDOWN:
self.scene.process_event(event)
def scene_checker(self):
if self.scene.done:
self.scene.reset() # for reuse - TODO
self.scene_index = (self.scene_index + 1) % len(self.scene_array)
self.scene = self.scene_array[self.scene_index]
self.scene.when_activated()
#self.scene = self.scene_dict[self.scene.next]
def update(self):
self.scene.update()
def draw(self):
self.scene.render()
def main_loop(self):
self.event_handler()
self.update()
self.scene_checker()
self.draw()
""" ################ RUN GAME ################ """
""" Game paramaters """
SQUARE_SIZE = 20 # pixels
SQUARES_PER_ARENA_SIDE = 20 # squares
MARGIN = 2 # pixels
SNAKE_START = (int(SQUARES_PER_ARENA_SIDE/2), int(SQUARES_PER_ARENA_SIDE/2)) # square coords
pg.init()
clock = pg.time.Clock()
# Square.display() and a few others need a direct reference to "screen" TODO implement better
w, h = 620, 620
SCREEN_SIZE = [w, h]
FPS = 10
screen = pg.display.set_mode(SCREEN_SIZE)
Game = Control()
while not Game.done:
Game.main_loop()
pg.display.update()
clock.tick(FPS)
pg.quit()
I am making a dnd application for fun, the char class is what each character is, but I can't seem to get to work
I have tried commenting out sections of code, yet it seems to be a problem with the init function
This is my code:
global mouseY
global x
x = 0
chars =[]
def setup():
global tsize
tsize = 50
def draw():
global x
if x == 0:
def mouseon(x,y,xs,ys):
return(mouseX <= xs and mouseX >= x and mouseY <= ys and mouseY >= y)
class char:
def __init__(self, name, HP, img, CLASS, SIZE, id):
self.xp = 0
self.yp = 0
self.SIZE = SIZE
self.name = name
self.HP = HP
self.img = img
self.CLASS = CLASS
self.id = id
self.Hover = False
chars.append(self)
dog = char("john", 100, "x.png", "archer", 50, 0)
print("l")
x = 2
else:
fill(255)
background(255)
stroke(0)
strokeWeight(1)
for i in range(height/50):
for j in range(width/50):
rect(i*50,j*50,49.5,49.5)
for i in chars:
def mouseClicked():
if mouseon(i.xp,i.yp,i.xp+i.SIZE,i.yp+i.SIZE):
i.Hover = True
print('h')
else:
print("n")
i.Hover = False
def mouseReleased():
print("o")
i.Hover = False
if i.Hover:
fill(0)
rect(i.xp+1, i.yp+1, i.xp+i.SIZE+1, i.yp+i.SIZE+1)
fill(0,255,0)
strokeWeight(0)
rect(i.xp, i.yp, i.xp+i.SIZE, i.yp+i.SIZE)
expected result:
when I press the mouse, I should get h or n printed to the console, and when I release it, I should get o
Actual results:
nothing
EDIT:
to clarify,
Draw is called once every frame
setup once at the beginning
mouseClicked when the mouse is clicked
and mouseReleased when the mouse is released
okay, so I had to put the mouse clicked and released functions at the bottom and put the for loops in those, but it solved my problem
the updated code is
global mouseX
global mouseY
global x
x = 0
chars =[]
def setup():
global tsize
tsize = 50
def draw():
global x
if x == 0:
global mouseon
def mouseon(x,y,xs,ys):
return(mouseX <= xs and mouseX >= x and mouseY <= ys and mouseY >= y)
class char:
def __init__(self, name, HP, img, CLASS, SIZE, id):
self.xp = 0
self.yp = 0
self.SIZE = SIZE
self.name = name
self.HP = HP
self.img = img
self.CLASS = CLASS
self.id = id
self.Hover = False
chars.append(self)
dog = char("john", 100, "x.png", "archer", 50, 0)
print("l")
x = 2
else:
fill(255)
background(255)
stroke(0)
strokeWeight(1)
for i in range(height/50):
for j in range(width/50):
rect(i*50,j*50,49.5,49.5)
for i in chars:
if i.Hover:
fill(0)
rect(i.xp+1, i.yp+1, i.xp+i.SIZE+1, i.yp+i.SIZE+1)
fill(0,255,0)
strokeWeight(0)
rect(i.xp, i.yp, i.xp+i.SIZE, i.yp+i.SIZE)
def mouseClicked():
for i in chars:
if mouseon(i.xp,i.yp,i.xp+i.SIZE,i.yp+i.SIZE):
i.Hover = True
print('h')
else:
print("n")
i.Hover = False
def mouseReleased():
for i in chars:
print("o")
i.Hover = False