Pathfinding Visualizer in pygame [duplicate] - python

This question already has answers here:
tips on Adding/creating a drop down selection box in pygame
(1 answer)
trying creating dropdown menu pygame, but got stuck
(1 answer)
Closed 1 year ago.
I am still learning how to code and wanted to create a pathfinding visualizer by following this video:
https://www.youtube.com/watch?v=JtiK0DOeI4A&ab_channel=TechWithTim
I think i understand the algorithm and the whole code more or less, but i want to expand the program. I want to implement more algorithms, some buttons and a file dialog to import and export labyrinths and stop the time the algorithm takes and some stuff like that. I know how to implement another algorithm, but i am new to Pygame, so i have no idea how to display that. I tried to combined it with PyQt5 and searched for different solutions with Pygame itself, but nothing really worked.
How can i add some bar or something like that above where i can choose the algorithm i want to use with a dropdown menu or similar and import the labyrinth and all that stuff?
This is the corresponding code:
import pygame
import math
from queue import PriorityQueue
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
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)
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_closed(self):
self.color = RED
def make_open(self):
self.color = GREEN
def make_barrier(self):
self.color = BLACK
def make_start(self):
self.color = ORANGE
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.neightbors = []
if self.row < self.total_rows -1 and not grid[self.row + 1][self.col].is_barrier(): # DOWN
self.neightbors.append(grid[self.row + 1][self.col])
if self.row > 0 and not grid[self.row - 1][self.col].is_barrier(): # UP
self.neightbors.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.neightbors.append(grid[self.row][self.col + 1])
if self.col > 0 and not grid[self.row][self.col - 1].is_barrier(): # LEFT
self.neightbors.append(grid[self.row][self.col - 1])
def __lt__(self, other):
return (False)
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: # draws the path
reconstruct_path(came_from, end, draw)
end.make_end()
return True
for neighbor in current.neightbors:
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():
width = 800
win = pygame.display.set_mode((width, width))
pygame.display.set_caption("A* Path Finding Algorithm")
ROWS = 50
grid = make_grid(ROWS, width)
start = None
end = None
run = True
while run:
draw(win, grid, ROWS, width)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if pygame.mouse.get_pressed()[0]:
pos = pygame.mouse.get_pos()
row, col = get_clicked_pos(pos, ROWS, width)
spot = grid [row][col]
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()
elif pygame.mouse.get_pressed()[2]:
pos = pygame.mouse.get_pos()
row, col = get_clicked_pos(pos, ROWS, width)
spot = grid [row][col]
spot.reset()
if spot == start:
start = None
elif spot == end:
end = None
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and start and end:
for row in grid:
for spot in row:
spot.update_neighbors(grid)
algorithm(lambda: draw(win, grid, ROWS, width), grid, start, end)
if event.key == pygame.K_c:
start = None
end = None
grid = make_grid(ROWS, width)
pygame.quit()
if __name__ == "__main__":
main()

Related

How to generate my maze instantly so I don't have to watch it Generate?

So I'm creating a game and I'm using Recursive backtracking algorithm to create the maze, however, I don't want it to show the maze generation and just to instantly generate the maze. I'm unsure of how to actually do this though so any help would be appreciated, I've already tried not drawing the generated white part but that then doesn't create the maze.
import pygame
import random
import time
class Cell(object):
def __init__(self, x, y, cell_size, screen, black, white, red, blue):
# position in matrix
self.x = x
self.y = y
# keeps track of which walls are still visible
self.walls = [True, True, True, True]
# checks if cell has been visited during generation
self.generated = False
# checks if cell is on path during solving
self.on_path = False
# checks if cell has been visited during solving
self.visited = False
self.cell_size = cell_size
self.screen = screen
self.black = black
self.white = white
self.red = red
self.blue = blue
def draw_cell(self):
# coordinates on screen
x = self.x * self.cell_size
y = self.y * self.cell_size
# draws a wall if it still exists
if self.walls[0]:
pygame.draw.line(self.screen, self.black, (x, y), (x + self.cell_size, y), 5)
if self.walls[1]:
pygame.draw.line(self.screen, self.black,
(x, y + self.cell_size), (x + self.cell_size, y + self.cell_size), 5)
if self.walls[2]:
pygame.draw.line(self.screen, self.black,
(x + self.cell_size, y), (x + self.cell_size, y + self.cell_size), 5)
if self.walls[3]:
pygame.draw.line(self.screen, self.black, (x, y), (x, y + self.cell_size), 5)
# marks out white if generated during generation
if self.generated:
pygame.draw.rect(self.screen, self.white, (x, y, self.cell_size, self.cell_size))
class Maze:
def __init__(self, screen, cell_size, rows, cols, white, black, red, blue):
self.screen = screen
self.cell_size = cell_size
self.rows = rows
self.cols = cols
self.state = None
self.maze = []
self.stack = []
self.current_x = 0
self.current_y = 0
self.row = []
self.neighbours = []
self.black = black
self.white = white
self.red = red
self.blue = blue
self.cell = None
def on_start(self):
# maintains the current state
# maze matrix of cell instances
self.maze = []
# stack of current cells on path
self.stack = []
self.current_x, self.current_y = 0, 0
self.maze.clear()
self.stack.clear()
for x in range(self.cols):
self.row = []
for y in range(self.rows):
self.cell = Cell(x, y, self.cell_size, self.screen, self.black, self.white, self.red, self.blue)
self.row.append(self.cell)
self.maze.append(self.row)
def in_bounds(self, x, y):
return 0 <= x < self.cols and 0 <= y < self.rows
def find_next_cell(self, x, y):
# keeps track of valid neighbors
self.neighbours = []
# loop through these two arrays to find all 4 neighbor cells
dx, dy = [1, -1, 0, 0], [0, 0, 1, -1]
for d in range(4):
# add cell to neighbor list if it is in bounds and not generated
if self.in_bounds(x + dx[d], y + dy[d]):
if not self.maze[x + dx[d]][y + dy[d]].generated:
self.neighbours.append((x + dx[d], y + dy[d]))
# returns a random cell in the neighbors list, or -1 -1 otherwise
if len(self.neighbours) > 0:
return self.neighbours[random.randint(0, len(self.neighbours) - 1)]
else:
return -1, -1
def remove_wall(self, x1, y1, x2, y2):
# x distance between original cell and neighbor cell
xd = self.maze[x1][y1].x - self.maze[x2][y2].x
# to the bottom
if xd == 1:
self.maze[x1][y1].walls[3] = False
self.maze[x2][y2].walls[1] = False
# to the top
elif xd == -1:
self.maze[x1][y1].walls[1] = False
self.maze[x2][y2].walls[3] = False
# y distance between original cell and neighbor cell
xy = self.maze[x1][y1].y - self.maze[x2][y2].y
# to the right
if xy == 1:
self.maze[x1][y1].walls[0] = False
self.maze[x2][y2].walls[2] = False
# to the left
elif xy == -1:
self.maze[x1][y1].walls[2] = False
self.maze[x2][y2].walls[0] = False
def create_maze(self):
self.maze[self.current_x][self.current_y].generated = True
# self.maze[self.current_x][self.current_y].draw_current()
next_cell = self.find_next_cell(self.current_x, self.current_y)
# checks if a neighbor was returned
if next_cell[0] >= 0 and next_cell[1] >= 0:
self.stack.append((self.current_x, self.current_y))
self.remove_wall(self.current_x, self.current_y, next_cell[0], next_cell[1])
self.current_x = next_cell[0]
self.current_y = next_cell[1]
# no neighbor, so go to the previous cell in the stack
elif len(self.stack) > 0:
previous = self.stack.pop()
self.current_x = previous[0]
self.current_y = previous[1]
def main():
WIDTH, HEIGHT = 800, 800
CELL_SIZE = 40
ROWS, COLUMNS = int(HEIGHT / CELL_SIZE), int(WIDTH / CELL_SIZE)
# color variables
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
# initialize pygame
pygame.init()
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
SCREEN.fill(WHITE)
pygame.display.set_caption("Maze Gen")
CLOCK = pygame.time.Clock()
FPS = 60
m = Maze(SCREEN, CELL_SIZE, ROWS, COLUMNS, WHITE, BLACK, RED, BLUE)
m.on_start()
running = True
while running:
CLOCK.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
for i in range(m.cols):
for j in range(m.rows):
m.maze[i][j].draw_cell()
m.create_maze()
pygame.display.flip()
if __name__ == "__main__":
main()
pygame.quit()
Call m.create_maze() in a loop before the application loop. Terminate the loop when len(m.stack) == 0:
def main():
# [...]
m = Maze(SCREEN, CELL_SIZE, ROWS, COLUMNS, WHITE, BLACK, RED, BLUE)
m.on_start()
while True:
m.create_maze()
if len(m.stack) == 0:
break
running = True
while running:
CLOCK.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
for i in range(m.cols):
for j in range(m.rows):
m.maze[i][j].draw_cell()
pygame.display.flip()

Algorithm Visualizer DFS Implementation Error

The algorithm searches straight upwards and when it reaches the barrier, it just stops, instead of going to the right
Here.
I am new to graph-algorithms and have just learned about them, so I am clueless when it comes to implementing them.
My code is not optimized, however, I will optimize it later. Here is my code:
import pygame
import queue
WIDTH = 720
pygame.init()
WIN = pygame.display.set_mode((WIDTH, WIDTH))
pygame.display.set_caption("Pathfinding Visualizer")
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)
class Node:
def __init__(self, row, col, width, total_rows):
self.row = row
self.col = col
self.width = width
self.x = row * width
self.y = col * width
self.color = WHITE
self.neighbours = []
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 == PURPLE
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_neighbours(self, grid):
self.neighbours = []
if self.row < self.total_rows - 1 and not grid[self.row + 1][self.col].is_barrier(): # DOWN
self.neighbours.append(grid[self.row + 1][self.col])
if self.row > 0 and not grid[self.row - 1][self.col].is_barrier(): # UP
self.neighbours.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.neighbours.append(grid[self.row][self.col + 1])
if self.col > 0 and not grid[self.row][self.col - 1].is_barrier(): # LEFT
self.neighbours.append(grid[self.row][self.col - 1])
def __lt__(self, other):
return False
def h(p1, p2):
x1, y1 = p1
x2, y2 = p2
return abs(x1 - x2) + abs(y1 - y2)
def reconstruct_path(came_from, current, draw):
print(came_from)
while current in came_from:
current = came_from[current]
current.make_path()
draw()
def dfs(draw, grid, start, end):
open_set = queue.LifoQueue()
open_set.put(start)
came_from = {}
g = {node: float("inf") for row in grid for node in row}
g[start] = 0
open_set_hash = {start}
while not open_set.empty():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
return False
current = open_set.get()
open_set_hash.remove(current)
if current == end:
reconstruct_path(came_from, end, draw)
end.make_end()
start.make_start()
return True, g
for n in current.neighbours:
g[n] = g[current] + 1
came_from[n] = current
if n not in open_set_hash:
open_set.put(n)
open_set_hash.add(n)
draw()
if current != start:
current.make_closed()
return False
def astar(draw, grid, start, end):
count = 0
open_set = queue.PriorityQueue()
open_set.put(item=(0, count, start))
came_from = {}
g = {node: float("inf") for row in grid for node in row}
g[start] = 0
f = {node: float("inf") for row in grid for node in row}
f[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()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
return False
current = open_set.get()[2]
open_set_hash.remove(current)
if current == end:
reconstruct_path(came_from, end, draw)
end.make_end()
start.make_start()
return True, g
for neighbour in current.neighbours:
temp_g = g[current] + 1
if temp_g < g[neighbour]:
came_from[neighbour] = current
g[neighbour] = temp_g
f[neighbour] = temp_g + h(neighbour.get_pos(), end.get_pos())
if neighbour not in open_set_hash:
count += 1
open_set.put((f[neighbour], count, neighbour))
open_set_hash.add(neighbour)
neighbour.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):
# If rows = 5, then grid would be [[], [], [], [], []]
grid.append([])
for j in range(rows):
node = Node(i, j, gap, rows)
if i == 0 or j == 0 or i == 59 or j == 59:
node.make_barrier()
grid[i].append(node)
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_text(win):
font = pygame.font.Font(None, 20)
text1 = font.render("DepthFirstSearch - D", 1, (255, 255, 255))
text2 = font.render("BreadthFirstSearch - B", 1, (255, 255, 255))
text3 = font.render("AStar - A", 1, (255, 255, 255))
textpos1 = text1.get_rect()
textpos2 = text2.get_rect()
textpos3 = text3.get_rect()
textpos1.centerx = win.get_rect().centerx-290
textpos2.centerx = win.get_rect().centerx-125
textpos3.centerx = win.get_rect().centerx+10
win.blit(text1, textpos1)
win.blit(text2, textpos2)
win.blit(text3, textpos3)
def draw(win, grid, rows, width):
win.fill(WHITE)
for row in grid:
for node in row:
node.draw(win)
draw_grid(win, rows, width)
draw_text(win)
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 = 60
grid = make_grid(ROWS, width)
start = None
end = None
run = True
started = False
while run:
draw(win, grid, ROWS, width)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if started:
continue
if pygame.mouse.get_pressed()[0]: # LEFT
pos = pygame.mouse.get_pos()
row, col = get_clicked_pos(pos, ROWS, width)
node = grid[row][col]
if not start and node != end and node.is_barrier() == False:
start = node
start.make_start()
elif not end and node != start and node.is_barrier() == False:
end = node
end.make_end()
elif node != end and node != start:
node.make_barrier()
elif pygame.mouse.get_pressed()[2]: # RIGHT
pos = pygame.mouse.get_pos()
row, col = get_clicked_pos(pos, ROWS, width)
node = grid[row][col]
node.reset()
if node == start:
start = None
elif node == end:
end = None
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a and start and end:
for row in grid:
for node in row:
node.update_neighbours(grid)
astar(lambda: draw(win, grid, ROWS, width), grid, start, end)
elif event.key == pygame.K_d and start and end:
for row in grid:
for node in row:
node.update_neighbours(grid)
dfs(lambda: draw(win, grid, ROWS, width), grid, start, end)
elif event.key == pygame.K_b and start and end:
for row in grid:
for node in row:
node.update_neighbours(grid)
# bfs(lambda: draw(win, grid, ROWS, width), grid, start, end)
if event.key == pygame.K_c:
start = None
end = None
grid = make_grid(ROWS, width)
pygame.quit()
main(WIN, WIDTH)
I commented out your open_set_hash.remove(current). I don't see why you remove the current from the visited list? Also moved the came_from[n] = current inside the if condition. This was to get the reconstruct_path function to work properly.
if n not in open_set_hash:
came_from[n] = current
Looks like it works just fine after this. BTW. nice visualization you made!

Testing if two sprite overlap while using matrices

Right now, I have a ball that moves around the screen in a random diagonal direction and bounces off the wall when it collides with it.
What I want to do:
If the ball goes over a cell/box in the grid, have the cell/box turn red and then keep changing colors until the amount of times that specific spot is 10. If the value is equal to 10, the program ends.
Let me give you a scenario: let's say I have a 3x3 grid and each cell in that grid has a value of 0. Every time the ball goes over a cell, that value goes up by one until it hits 3. Every time the value goes up, the color for that cell changes. 0=white, 1=red, 2=green, and 3=blue. That's what I wanted the output to be.
What I have so far:
The entire screen turns red instead of just the individual cell.
I am just testing it for one color at the moment, but then want to add a multitude of colors.
for row in range(GRIDY):
for column in range(GRIDX):
rect = [(MARGIN + WIDTH) * column + MARGIN, (MARGIN + HEIGHT) * row + MARGIN, WIDTH, HEIGHT]
color = WHITE
if GRIDY or GRIDX == 1:
color = RED
pg.draw.rect(screen, color, rect)
Here's the rest of the code for reference:
import sys
import math
from random import randrange
import pygame as pg
# define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
# define measurements
WIDTH, HEIGHT, MARGIN = 10, 10, 1
GRIDX, GRIDY = 91, 36
class GridObject(pg.sprite.Sprite):
def __init__(self, pos, grid, *groups):
super().__init__(groups)
# create image from grid
self.grid = grid
self.gridsize = (len(grid[0]), len(grid))
imgsize = self.gridsize[0]*(WIDTH+MARGIN), self.gridsize[1]*(HEIGHT+MARGIN)
self.image = pg.Surface(imgsize, flags=pg.SRCALPHA)
self.image.fill((0, 0, 0, 0))
col = (235, 175, 76)
for c in range(self.gridsize[0]):
for r in range(self.gridsize[1]):
if self.grid[r][c] == 1:
rect = [(MARGIN + WIDTH) * c + MARGIN, (MARGIN + HEIGHT) * r + MARGIN, WIDTH, HEIGHT]
pg.draw.rect(self.image, col, rect)
self.rect = self.image.get_rect(center=pos)
self.vel = pg.math.Vector2(8, 0).rotate(randrange(360))
self.pos = pg.math.Vector2(pos)
def update(self, boundrect):
self.pos += self.vel
self.rect.center = self.pos
if self.rect.left <= boundrect.left or self.rect.right >= boundrect.right:
self.vel.x *= -1
if self.rect.top <= boundrect.top or self.rect.bottom >= boundrect.bottom:
self.vel.y *= -1
# align rect to grid
gridpos = round(self.rect.x / (WIDTH+MARGIN)), round(self.rect.y / (HEIGHT+MARGIN))
self.rect.topleft = gridpos[0] * (WIDTH+MARGIN), gridpos[1] * (HEIGHT+MARGIN)
ballGrid = [[0, 1, 1, 1, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 1, 1, 1, 0]]
def main():
#overlap = False
screen = pg.display.set_mode((GRIDX * (WIDTH+MARGIN) + MARGIN, GRIDY * (HEIGHT+MARGIN)))
# Set title of screen
pg.display.set_caption("Ball With Grid")
clock = pg.time.Clock()
sprite_group = pg.sprite.Group()
ball = GridObject((495, 193), ballGrid, sprite_group)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
# Used to track the grid coordinates
if event.type == pg.MOUSEBUTTONDOWN:
# Get the position is screen is clicked
pos = pg.mouse.get_pos()
# Change the x/y screen coordinates to grid coordinates
column = pos[0] // (WIDTH + MARGIN)
row = pos[1] // (HEIGHT + MARGIN)
# Set that location to one
grid[row][column] = 1
print("Click ", pos, "Grid coordinates: ", row, column)
screen.fill((0, 0, 0))
# Draw the grid and add values to the cells
for row in range(GRIDY):
for column in range(GRIDX):
rect = [(MARGIN + WIDTH) * column + MARGIN, (MARGIN + HEIGHT) * row + MARGIN, WIDTH, HEIGHT]
color = WHITE
if GRIDY or GRIDX == 1:
color = RED
pg.draw.rect(screen, color, rect)
sprite_group.update(screen.get_rect())
sprite_group.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()
btw if this looks similar, my other account posted this question: Using a matrix as a sprite and testing if two sprites overlap
Link to the images:
https://imgur.com/gallery/UicBs6q
let's say I have a 3x3 grid and each cell in that grid has a value of 0. Every time the ball goes over a cell, that value goes up by one until it hits 3. Every time the value goes up, the color for that cell changes. 0=white, 1=red, 2=green, and 3=blue. That's what I wanted the output to be.
You have how often the ball is on a cell, for each cell. Since the ball covers more than 1 cell (the size of the ball is greater than 1 cell), the cell counter would be incremented multiple times in consecutive frames, when the ball rolls over the cell.
So you have to ensure that a ball has left a cell, before its counter is allowed to be incremented again.
Create a 2 dimensional array (nested list) of integral values, initialized by 0 with the size of the field. The filed sores the hit counters for the cells. Furthermore, create a list (hitList) which stores the indices of the cells which have been hit in a frame.
hitGrid = [[0 for i in range(GRIDX)] for j in range(GRIDY)]
hitList = []
The grid (hitGrid) and the list (hitList) have to be passed to the update method of the class GridObject. If the ball touches a field and the field was not touched in the previous frame, then the corresponding entry in the gird is has to be incremented. Further more the function can set a global variable max_hit, wiht the highest fit count in the current frame:
class GridObject(pg.sprite.Sprite):
# [...]
def update(self, boundrect, gridHit):
# [...]
# increment touched filed
global max_hit
max_hit = 0
oldHitList = hitList[:]
hitList.clear()
for c in range(self.gridsize[0]):
for r in range(self.gridsize[1]):
p = gridpos[1] + r, gridpos[0] + c
if p in oldHitList:
hitList.append(p)
elif self.grid[r][c] == 1:
if p[0] < len(hitGrid) and p[1] < len(hitGrid[p[0]]):
hitList.append(p)
if p not in oldHitList:
hitGrid[p[0]][p[1]] +=1
max_hit = max(max_hit, hitGrid[p[0]][p[1]])
Evaluate max_hit after invoke update to the GridObject obejcts:
sprite_group.update(screen.get_rect(), hitGrid, hitList)
if max_hit >= 4:
print("game over")
done = True
Tint the color of a field dependent on the value in gridHit:
for row in range(GRIDY):
for column in range(GRIDX):
rect = [(MARGIN + WIDTH) * column + MARGIN, (MARGIN + HEIGHT) * row + MARGIN, WIDTH, HEIGHT]
colorlist = [WHITE, RED, GREEN, BLUE]
color = colorlist[min(len(colorlist)-1, hitGrid[row][column])]
pg.draw.rect(screen, color, rect)
See the example:
from random import randrange
import pygame as pg
# define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# define measurements
WIDTH, HEIGHT, MARGIN = 10, 10, 1
GRIDX, GRIDY = 36, 36
class GridObject(pg.sprite.Sprite):
def __init__(self, pos, grid, *groups):
super().__init__(groups)
# create image from grid
self.grid = grid
self.gridsize = (len(grid[0]), len(grid))
imgsize = self.gridsize[0]*(WIDTH+MARGIN), self.gridsize[1]*(HEIGHT+MARGIN)
self.image = pg.Surface(imgsize, flags=pg.SRCALPHA)
self.image.fill((0, 0, 0, 0))
col = (235, 175, 76)
for c in range(self.gridsize[0]):
for r in range(self.gridsize[1]):
if self.grid[r][c] == 1:
rect = [(MARGIN + WIDTH) * c + MARGIN, (MARGIN + HEIGHT) * r + MARGIN, WIDTH, HEIGHT]
pg.draw.rect(self.image, col, rect)
self.rect = self.image.get_rect(center=pos)
self.vel = pg.math.Vector2(8, 0).rotate(randrange(360))
self.pos = pg.math.Vector2(pos)
def update(self, boundrect, hitGrid, hitList):
self.pos += self.vel
self.rect.center = self.pos
if self.rect.left <= boundrect.left or self.rect.right >= boundrect.right:
self.vel.x *= -1
if self.rect.top <= boundrect.top or self.rect.bottom >= boundrect.bottom:
self.vel.y *= -1
# align rect to grid
gridpos = round(self.rect.x / (WIDTH+MARGIN)), round(self.rect.y / (HEIGHT+MARGIN))
self.rect.topleft = gridpos[0] * (WIDTH+MARGIN), gridpos[1] * (HEIGHT+MARGIN)
# increment touched filed
global max_hit
max_hit = 0
oldHitList = hitList[:]
hitList.clear()
for c in range(self.gridsize[0]):
for r in range(self.gridsize[1]):
p = gridpos[1] + r, gridpos[0] + c
if p in oldHitList:
hitList.append(p)
elif self.grid[r][c] == 1:
if p[0] < len(hitGrid) and p[1] < len(hitGrid[p[0]]):
hitList.append(p)
if p not in oldHitList:
hitGrid[p[0]][p[1]] +=1
max_hit = max(max_hit, hitGrid[p[0]][p[1]])
ballGrid = [[0, 1, 1, 1, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 1, 1, 1, 0]]
def main():
#overlap = False
screen = pg.display.set_mode((GRIDX * (WIDTH+MARGIN) + MARGIN, GRIDY * (HEIGHT+MARGIN)))
# Set title of screen
pg.display.set_caption("Ball With Grid")
clock = pg.time.Clock()
sprite_group = pg.sprite.Group()
ball = GridObject((screen.get_width()//2, screen.get_height()//2), ballGrid, sprite_group)
hitGrid = [[0 for i in range(GRIDX)] for j in range(GRIDY)]
hitList = []
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if event.type == pg.KEYDOWN and event.key == pg.K_SPACE:
hitGrid = [[0 for i in range(GRIDX)] for j in range(GRIDY)]
screen.fill((0, 0, 0))
# Draw the grid and add values to the cells
for row in range(GRIDY):
for column in range(GRIDX):
rect = [(MARGIN + WIDTH) * column + MARGIN, (MARGIN + HEIGHT) * row + MARGIN, WIDTH, HEIGHT]
colorlist = [WHITE, RED, GREEN, BLUE]
color = colorlist[min(len(colorlist)-1, hitGrid[row][column])]
pg.draw.rect(screen, color, rect)
sprite_group.update(screen.get_rect(), hitGrid, hitList)
if max_hit >= 4:
print("game over")
done = True
sprite_group.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()

Putting the scrolling camera in a mini-window in pygame

I'm trying to create a game where the action is shown in a little box within the main screen object, freeing up the surrounding space for text and menus and what-not. Since the map is larger than the allotted window, I coded a basic "camera" that follows the player around. It mostly works, but I'm having trouble "trimming off" the area outside of this window.
Here's the relevant bits of code (EDITED to provide Working Example):
import pygame, os, sys
from pygame.locals import *
pygame.init()
RIGHT = 'RIGHT'
LEFT = 'LEFT'
UP = 'UP'
DOWN = 'DOWN'
class Camera():
def __init__(self, screen, x_ratio = 1, y_ratio = 1, x_offset = 0, y_offset = 0):
self.screen = screen.copy()
self.rec = self.screen.get_rect()
self.rec.width *= x_ratio
self.rec.height *= y_ratio
self.x_offset = x_offset
self.y_offset = y_offset
def get_pos(self):
return (self.x_offset - self.rec.x, self.y_offset - self.rec.y)
def get_window(self):
w = pygame.Rect(self.rec)
w.topleft = (0 - self.rec.x, 0 - self.rec.y)
return w
def move(self, x, y):
"""Move camera into new position"""
self.rec.x = x
self.rec.y = y
def track(self, obj):
while obj.rec.left < self.rec.left:
self.rec.x -= 1
while obj.rec.right > self.rec.right:
self.rec.x += 1
while obj.rec.top < self.rec.top:
self.rec.y -= 1
while obj.rec.bottom > self.rec.bottom:
self.rec.y += 1
class Map:
def __init__(self, width, height):
self.width = width
self.height = height
self.rec = pygame.Rect(0,0,self.width,self.height)
def draw(self, screen):
pygame.draw.rect(screen, (200,200,200), self.rec)
class Obj:
def __init__(self, char, x = 0, y = 0, width = 0, height = 0):
self.width = width
self.height = height
self.rec = pygame.Rect(x, y, width, height)
self.cur_map = None
self.timers = {}
#Dummying in chars for sprites
self.char = char
self.x_dir = 1
self.y_dir = 1
self.speed = 1
self.moving = False
def move(self):
if self.x_dir != 0 or self.y_dir != 0:
new_x = self.rec.x + (self.x_dir*self.speed)
new_y = self.rec.y + (self.y_dir*self.speed)
new_rec = pygame.Rect(new_x, new_y, self.width, self.height)
#Keep movement within bounds of map
while new_rec.left < self.cur_map.rec.left:
new_rec.x += 1
while new_rec.right > self.cur_map.rec.right:
new_rec.x -= 1
while new_rec.top < self.cur_map.rec.top:
new_rec.y += 1
while new_rec.bottom > self.cur_map.rec.bottom:
new_rec.y -= 1
self.rec = new_rec
def set_dir(self, d):
self.x_dir = 0
self.y_dir = 0
if d == LEFT:
self.x_dir = -1
elif d == RIGHT:
self.x_dir = 1
elif d == UP:
self.y_dir = -1
elif d == DOWN:
self.y_dir = 1
def set_moving(self, val = True):
self.moving = val
class Game:
def __init__(self):
self.screen_size = (800, 600)
self.screen = pygame.display.set_mode(self.screen_size)
self.map_screen = self.screen.copy()
self.title = 'RPG'
pygame.display.set_caption(self.title)
self.camera = Camera(self.screen, 0.75, 0.75)#, 10, 75)
self.fps = 80
self.clock = pygame.time.Clock()
self.debug = False
self.bg_color = (255,255,255)
self.text_size = 18
self.text_font = 'Arial'
self.text_style = pygame.font.SysFont(self.text_font, self.text_size)
self.key_binds = {LEFT : [K_LEFT, K_a], RIGHT : [K_RIGHT, K_d], UP : [K_UP, K_w], DOWN : [K_DOWN, K_s],
'interact' : [K_RETURN, K_z], 'inventory' : [K_i, K_SPACE], 'quit' : [K_ESCAPE]}
self.player = Obj('p', 0, 0, 10, self.text_size)
def draw(self, obj):
char = obj.char
self.draw_text(char, obj.rec.x, obj.rec.y, screen = self.map_screen)
def draw_text(self, text, x, y, color = (0,0,0), screen = None):
textobj = self.text_style.render(text, 1, color)
textrect = textobj.get_rect()
textrect.x = x
textrect.y = y
if screen == None:
"""Use default screen"""
self.screen.blit(textobj, textrect)
else:
screen.blit(textobj, textrect)
def play(self):
done = False
cur_map = Map(800, 800)
self.map_screen = pygame.Surface((cur_map.width, cur_map.height))
self.map_screen.fill(self.bg_color)
bg = pygame.Surface((cur_map.width, cur_map.height))
cur_map.draw(bg)
self.player.cur_map = cur_map
while not done:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key in self.key_binds[LEFT]:
self.player.set_dir(LEFT)
self.player.set_moving()
elif event.key in self.key_binds[RIGHT]:
self.player.set_dir(RIGHT)
self.player.set_moving()
elif event.key in self.key_binds[UP]:
self.player.set_dir(UP)
self.player.set_moving()
elif event.key in self.key_binds[DOWN]:
self.player.set_dir(DOWN)
self.player.set_moving()
elif event.type == KEYUP:
self.player.set_moving(False)
if self.player.moving:
self.player.move()
self.camera.track(self.player)
self.clock.tick()
self.screen.fill(self.bg_color)
self.map_screen.blit(bg, (0,0))
self.draw(self.player)
pygame.draw.rect(self.map_screen, (0,0,0), self.camera.rec, 1)
#self.screen.blit(self.map_screen, (0,0), [0 - self.camera.rec.x, 0 - self.camera.rec.y, self.camera.rec.width, self.camera.rec.height])
self.screen.blit(self.map_screen, self.camera.get_pos(), self.camera.get_window())
pygame.display.flip()
game = Game()
game.play()
Moving the player past past the bounds of the camera's window causes the window to roll up completely and disappear. I tried adjusting the blitting coordinates, as advised earlier, but it seems to only change the direction in which the window rolls up.
From your updated code, the blitting coordinates for self.screen.blit(...) are still changing: self.camera.get_window() changes value because rec.x and rec.y are values referring to the player position within the map. Hence you should define a constant minimap coordinate, this should be the same as the camera offset.
self.screen.blit(self.map_screen, (self.camera.x_offset,self.camera.y_offset), (*self.camera.get_pos(), self.camera.rec.width, self.camera.rec.height))
Change the Camera().get_pos() to:
def get_pos(self):
return (self.rec.x, self.rec.y)
I believe I only changed the self.screen.blit(...) and stopped using or rewrote your Camera functions as you're confusing yourself with all the rec variables.
To illustrate it working amend the Map().draw(screen) to:
def draw(self, screen):
pygame.draw.rect(screen, (200,200,200), self.rec)
pygame.draw.circle(screen, (255, 255, 255), (50, 50), 20, 2)
One tip as well don't draw the entire map at each loop, just the part that will be visible.

percolation in hexagonal lattice with 0.5 probability

I am trying to draw a (10,000 x 10,000) hexagonal lattice which is randomly half black and half white.I don't know how to fill hexagons of this lattice randomly to black and white([this is a sample of what i really want from this code but I couldn't make it.])1.here is my code(written in python language):
from __future__ import division, print_function
import math
import pygame
import random
pygame.init()
window_size = window_width, window_height = 1360, 768
rows, columns = 100, 100
fps = 10000
black = (0, 0, 0)
white = (255, 255, 255)
std_color = white
background_color =white
edge_color = black
color = black
class Hexagon(object):
"""
row: int - the row of the hexagon in the grid
col: int - the column of the hexagon in the grid
"""
def __init__(self, row, col, rows, cols, width=8):
"""
Constructs a new hexagon.
Arguments:
row: int - the row of the hexagon in the grid
col: int - the column of the hexagon in the grid
"""
self.row, self.col = row, col
self.width = width
alpha = 2*math.pi/3
self.a = width / (2*math.cos(alpha/2) + 1)
self.h = math.cos(alpha/2)*self.a
self.b = math.sin(alpha/2)*self.a*2
if row % 2 == 0:
self.x = self.a + self.h
else:
self.x = 0.0
self.x += self.col*(self.a + width)
self.y = (self.row + 1)*self.b/2
self.index = (self.row, self.col)
self.points = [
(self.x, self.y),
(self.x + self.h, self.y - self.b/2),
(self.x + self.h + self.a, self.y - self.b/2),
(self.x + width, self.y),
(self.x + self.h + self.a, self.y + self.b/2),
(self.x + self.h, self.y + self.b/2)
]
rel_indices = [
(1,0),
(2,0),
(-1,0),
(-2,0),
]
if self.row % 2 == 0:
rel_indices += [(1, 1), (-1, 1)]
else:
rel_indices += [(-1, -1), (1, -1)]
self.neighbor_indices = [((self.row + drow) % rows, (self.col + dcol) % cols)
for drow, dcol in rel_indices]
def draw(self, window, color):
pygame.draw.lines(window, edge_color, True, self.points)
grid=[Hexagon(row, col, rows, columns) for row in range(rows) for col in range(columns)]
window = pygame.display.set_mode(window_size)
pygame.display.set_caption("Hexgrid")
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
window.fill(background_color)
for hexagon in grid:
hexagon.draw(window, std_color)
pygame.display.flip()
clock.tick(fps)
How should I do this?

Categories