I have an image that looks like this:
I've converted it into a 2D list. What's the best way to iterate through this list in a 'spiral', starting at the top left and ending in the centre. The goal is to read in all non-black pixels.
Here is code I have that progresses through spirals, starting in the upper left and going clockwise. It is naive (doesn't make use of that we know there are spirals) and simply prints out the coordinates, but I hope you can modify it to what you need.
I've checked several edge cases, because you need to make sure (0,1,2,3 mod 4) x (0,1,2,3 mod 4) all work. And wide spirals and squarish spirals need to be considered.
def do_stuff_with(my_array, x, y):
print("Do stuff with", x, ",", y) #obviously, you will want to return or manipulate something. But this code is just about traversing the spiral.
def spiral(my_array, width, height):
x_left = 0
x_right = width
y_top = 0
y_bottom = height
x_c = y_c = 0
print("Bounding box {0},{1} -> {2},{3}".format(x_left, y_top, x_right, y_bottom))
if x_left >= x_right or y_top >= y_bottom:
print("Invalid spiral range.")
return
while x_left < x_right and y_top < y_bottom:
#print("Going right")
for i in range(x_left, x_right):
do_stuff_with(my_array, i, y_top)
#print("Going down")
for i in range(y_top + 1, y_bottom):
do_stuff_with(my_array, x_right - 1, i)
if y_bottom - y_top > 1: # this just prevents us from revisiting a square in, say, a 5x7 spiral
#print("Going left")
for i in range(x_right - 2, x_left, -1):
do_stuff_with(my_array, i, y_bottom - 1)
if x_right - x_left > 1: # this just prevents us from revisiting a square in, say, a 7x5 spiral
#print("Going up")
for i in range(y_bottom - 1, y_top + 1, -1):
do_stuff_with(my_array, x_left, i)
# we need to fill in the square that links an outer spiral with an inner spiral.
if x_right - x_left > 2 and y_bottom - y_top > 4:
do_stuff_with(my_array, x_left + 1, y_top + 2)
x_left += 2
x_right -= 2
y_top += 2
y_bottom -= 2
print("Top/bottom overlap", y_top >= y_bottom)
print("Left/right overlap", x_left >= x_right)
def test_spirals(xi, yi, xf, yf):
'''an exhaustive test to make sure different rectangles/spirals work'''
for x in range(xi, xf):
for y in range(yi, yf):
print(x, y, "spiral test")
my_array = []
for z in range(0, y):
my_array.append([0] * x)
spiral(my_array, x, y)
# squarish tests: it seems like the main cases are (0/1/2/3 mod 4, 0/1/2/3 mod 4) so these 16 should knock everything out
test_spirals(4, 4, 8, 8)
# rectangular tests--yes, this overlaps the above case with 5x(6/7) but we want to try all possibilities mod 4 without having too much data to read.
#test_spirals(2, 6, 6, 10)
Let me know if you need or want clarifications.
ETA: here is some pseudocode for if you know you are reading in a spiral, but I think it is a big assumption. Also, this pseudocode is untested. But the idea is: go right til you hit a wall or black square, then down, then left, then up. Then repeat. Also, check for unnecessary trackbacks that may cause you to loop at the innermost line near the end.
def traverse_known_spiral(myary, width, length):
do_stuff(0, 0)
x_c = 0
y_c = 0
while True:
x_c_i = x_c
y_c_i = y_c
while x_c < width - 1 and myary[x_c+1][y_c] == WHITE:
do_stuff(x_c+1, y_c)
x_c += 1
while y_c < height - 1 and myary[x_c][y_c+1] == WHITE:
do_stuff(x_c, y_c+1)
y_c += 1
if x_c_i == x_c and y_c_i == y_c: break # if we did not move right or down, then there is nothing more to do
x_c_i = x_c
y_c_i = y_c
if y_c_i != y_c: # if we didn't go down, then we'd be going backwards here
while x_c > 0 and myary[x_c-1][y_c] == WHITE:
do_stuff(x_c-1, y_c)
x_c -= 1
if x_c_i != x_c: # if we didn't go right, then we'd be going backwards here
while y_c > 0 and myary[x_c-1][y_c-1] == WHITE:
do_stuff(x_c, y_c-1)
y_c -= 1
if x_c_i == x_c and y_c_i == y_c: break # if we did not move left or up, then there is nothing more to do
Related
I'm trying to recreate the classic snake game using Python. Whenever I have a train of 3 or more blocks, they display properly when moving either left or right but only two (the first one/head of the snake and the most recently added block) when it is moving up or down.
This section of code adds the new blocks to an empty list(snake_body):
new_body = trtl.Turtle()
new_body.penup()
new_body.shape("square")
new_body.color(rand.choice(colors))
new_body.shapesize(1.8)
snake_body.append(new_body)
And this section is what moves the entire snake:
for index in range(len(snake_body)):
x = snake_body[index - 1].xcor()
y = snake.ycor()
if (snake.heading() == 0):
snake_body[index].goto(x - offset, y)
elif (snake.heading() == 180):
snake_body[index].goto(x + offset, y)
elif (snake.heading() == 90):
snake_body[index].goto(x - x_offset, y - y_offset)
for index in range(len(snake_body) - 1):
snake_body[index].goto(x + x_offset, y + y_offset)
y_offset += 15
x_offset += 5
elif (snake.heading() == 270):
snake_body[index].goto(x + x_offset, y + y_offset)
for index in range(len(snake_body) - 1):
snake_body[index].goto(x + x_offset, y + y_offset)
y_offset -= 15
x_offset += 5
snake_body[index].speed(0)
x_offset += 5
y_offset = y_offset*index
I tried to use a for loop so that it would adjust all blocks in the snake, but that did not affect anything. I have tried it without the for loop nested in the elif statements and changing the y_offset variable significantly each time, but that also changed nothing. I am unsure why all of the blocks except for the first and last ones disappear while they are moving vertically.
I was trying to recreate Conway's Game of Life in python using Tkinter, but I got stuck while checking for the neighbors. I can't seem to find the solution to my problem.
If a cell is alive it is stored using numpy, 0 for the dead cells and 1 for the living cells. The function "get_neighbours" checks if the cell is not in a corner or next to a side and it returns the ammount of neighbours each cell has. The function "recalculate" basically makes a new board with 0s and 1s which then replaces the original board and redraws everything. But when compared to an already existing game the progress is different.
import tkinter as tk
import numpy as np
win = tk.Tk()
WIDTH = 500
HEIGHT = 500
vs = 10
absvs = vs
cells = np.zeros((WIDTH//vs, HEIGHT//vs), dtype=int)
cells_new = np.zeros((WIDTH//vs, HEIGHT//vs), dtype=int)
def get_neighbours(x, y):
total = 0
if x > 0:
total += cells[x - 1, y]
if x > 0 and y > 0:
total += cells[x - 1, y - 1]
if y > 0:
total += cells[x, y - 1]
if x > 0 and y < (HEIGHT // absvs - 1):
total += cells[x - 1, y + 1]
if x < (WIDTH // absvs - 1):
total += cells[x + 1, y]
if x < (WIDTH // absvs - 1) and y < (HEIGHT // absvs - 1):
total += cells[x + 1, y + 1]
if y < (HEIGHT // absvs - 1):
total += cells[x, y + 1]
if x > 0 and y < (HEIGHT // absvs - 1):
total += cells[x - 1, y + 1]
return total
def recalculate():
global cells, cells_new
for y in range(HEIGHT//absvs):
for x in range(WIDTH//absvs):
temp = get_neighbours(x, y)
if (temp == 2 and cells[x, y] == 1) or (temp == 3 and cells[x, y] == 1):
cells_new[x, y] = 1
elif temp == 3 and cells[x, y] == 0:
cells_new[x, y] = 1
elif temp < 2 or temp > 3:
cells_new[x, y] = 0
cells = cells_new
canvas.delete("all")
create_stage()
redraw_cell()
def slider_changer(e):
global vs
canvas.delete("all")
vs = w.get()
create_stage()
redraw_cell()
def create_cell(e):
global cells
tx = e.x // vs
ty = e.y // vs
x = tx * vs
y = ty * vs
canvas.create_rectangle(x, y, x + vs, y + vs, fill="gray")
cells[tx, ty] = 1
print(get_neighbours(tx, ty))
def redraw_cell():
for x in range(WIDTH//vs):
for y in range(HEIGHT//vs):
if cells[x, y] == 1:
canvas.create_rectangle(x * vs, y * vs, x * vs + vs, y * vs + vs, fill="gray")
def create_stage():
for x in range(WIDTH//vs):
canvas.create_line(x*vs, 0, x*vs, HEIGHT)
for y in range(HEIGHT//vs):
canvas.create_line(0, y*vs, WIDTH, y*vs)
canvas = tk.Canvas(width = WIDTH, height = HEIGHT, bg = "white")
canvas.pack()
w = tk.Scale(win, from_=10, to=50, orient="horizontal", command = slider_changer, length = 500)
w.pack()
w2 = tk.Button(win, text = "PRESS ME!!!", command = recalculate)
w2.pack()
create_stage()
canvas.bind("<Button-1>", create_cell)
win.mainloop()
There are these issues in your code:
In get_neighbours the last if block is the same as the fourth if block, and so there is a neighbor that is counted twice and another neighbor that isn't counted at all (the one at x + 1 and y - 1). So replace this:
if x > 0 and y < (HEIGHT // absvs - 1):
total += cells[x - 1, y + 1]
with:
if x < (WIDTH // absvs - 1) and y > 0:
total += cells[x + 1, y - 1]
In recalculate the assignment to cells at the end is making you lose one of the two arrays. Now both cells and new_cells reference the very same array1. This means the next iteration will not be calculated correctly. Instead, you should copy the content of cells_new into cells. So replace:
cells = cells_new
with:
cells = cells_new.copy()
In recalculate the if...elif..elif construct (inside the double loop) does not deal with the case where temp == 2 and cells[x, y] == 1 and so cells_new[x, y] might not be reset to 0 when it should. In fact, the last part of this construct should not have a condition; it should be a catch-all for all states that the previous checks did not deal with. So replace:
elif temp < 2 or temp > 3:
cells_new[x, y] = 0
with:
else:
cells_new[x, y] = 0
1 It is not helping that several websites, including https://www.geeksforgeeks.org/how-to-copy-numpy-array-into-another-array/ and https://www.askpython.com/python-modules/numpy/numpy-copy, wrongly assert that an assignment of one numpy array to another makes a copy. That is not true.
In a game I'm writing with Pygame, I have a 2D point (x,y) in a box from (0,0) to (1,1) (perfect square with side length 1).
I want to calculate the euclidean distance from the point to the box boundary in a direction alpha, where alpha is an azimuth direction measured in radians counter-clockwise, starting from alpha=0 as the x-axis direction.
I wrote a python code that calculates this distance, but I'm not sure it's the most efficient/cleanest way:
import numpy as np
def get_distance_to_boundary(x, y, angle):
cos_angle, sin_angle = np.cos(angle), np.sin(angle)
if cos_angle == 0:
return y if sin_angle < 0 else 1 - y
if sin_angle == 0:
return x if cos_angle < 0 else 1 - x
x_target = 1 if cos_angle > 0 else 0
y_target = y + (x_target - x) * np.tan(angle)
if y_target > 1:
return np.fabs((1 - y) / sin_angle)
if y_target < 0:
return np.fabs(y / sin_angle)
return np.sqrt((x_target - x) ** 2 + (y_target - y) ** 2)
Any idea for a better approach?
Illustration:
This method is a little more efficient/cleaner because you don't need tan, fabs, sqrt or **2:
def distance(x,y,angle):
cos_angle, sin_angle = np.cos(angle), np.sin(angle)
if cos_angle == 0:
return y if sin_angle < 0 else 1 - y
if sin_angle == 0:
return x if cos_angle < 0 else 1 - x
distance_EW = (1-x)/cos_angle if cos_angle>0 else -x/cos_angle
distance_NS = (1-y)/sin_angle if sin_angle>0 else -y/sin_angle
return min(distance_EW, distance_NS)
I define distance_EW as the distance in the case where the target is on the East wall (if cos_angle>0) or on the West wall (if cos_angle<0). Similarly, define distance_NS for the North or South wall.
...
WARNING: My distance function will sometimes produce different results than your function because of rounding errors!! This is especially problematic when your starting point is at the border of the box and angle is close to a multiple of π/2.
I would suggest you set some sort of tolerance like if abs(sin_angle) < 1e-12:, instead of if sin_angle == 0:. That way, sin_angle = np.sin(np.pi) will be accepted in the if condition, even though it is not exactly equal to 0 (because np.sin(np.pi) is 1.2e-16 with python).
I'm trying traverse all the cells that a line goes through. I've found the Fast Voxel Traversal Algorithm that seems to fit my needs, but I'm currently finding to be inaccurate. Below is a graph with a red line and points as voxel coordinates that the algorithm gives. As you can see it is almost correct except for the (4, 7) point, as it should be (5,6). I'm not sure if i'm implementing the algorithm correctly either so I've included it in Python. So i guess my question is my implementation correct or is there a better algo to this?
Thanks
def getVoxelTraversalPts(strPt, endPt, geom):
Max_Delta = 1000000.0
#origin
x0 = geom[0]
y0 = geom[3]
(sX, sY) = (strPt[0], strPt[1])
(eX, eY) = (endPt[0], endPt[1])
dx = geom[1]
dy = geom[5]
sXIndex = ((sX - x0) / dx)
sYIndex = ((sY - y0) / dy)
eXIndex = ((eX - sXIndex) / dx) + sXIndex
eYIndex = ((eY - sYIndex) / dy) + sYIndex
deltaX = float(eXIndex - sXIndex)
deltaXSign = 1 if deltaX > 0 else -1 if deltaX < 0 else 0
stepX = deltaXSign
tDeltaX = min((deltaXSign / deltaX), Max_Delta) if deltaXSign != 0 else Max_Delta
maxX = tDeltaX * (1 - sXIndex + int(sXIndex)) if deltaXSign > 0 else tDeltaX * (sXIndex - int(sXIndex))
deltaY = float(eYIndex - sYIndex)
deltaYSign = 1 if deltaY > 0 else -1 if deltaY < 0 else 0
stepY = deltaYSign
tDeltaY = min(deltaYSign / deltaY, Max_Delta) if deltaYSign != 0 else Max_Delta
maxY = tDeltaY * (1 - sYIndex + int(sYIndex)) if deltaYSign > 0 else tDeltaY * (sYIndex - int(sYIndex))
x = sXIndex
y = sYIndex
ptsIndexes = []
pt = [round(x), round(y)]
ptsIndexes.append(pt)
prevPt = pt
while True:
if maxX < maxY:
maxX += tDeltaX
x += deltaXSign
else:
maxY += tDeltaY
y += deltaYSign
pt = [round(x), round(y)]
if pt != prevPt:
#print pt
ptsIndexes.append(pt)
prevPt = pt
if maxX > 1 and maxY > 1:
break
return (ptsIndexes)
The voxels that you are walking start at 0.0, i.e. the first voxel spans space from 0.0 to 1.0, a not from -0.5 to 0.5 as you seem to be assuming. In other words, they are the ones marked with dashed line, and not the solid one.
If you want voxels to be your way, you will have to fix initial maxX and maxY calculations.
Ain't nobody got time to read the paper you posted and figure out if you've implemented it correctly.
Here's a question, though. Is the algorithm you've used (a) actually meant to determine all the cells that a line passes through or (b) form a decent voxel approximation of a straight line between two points?
I'm more familiar with Bresenham's line algorithm which performs (b). Here's a picture of it in action:
Note that the choice of cells is "aesthetic", but omits certain cells the line passes through. Including these would make the line "uglier".
I suspect a similar thing is going on with your voxel line algorithm. However, looking at your data and the Bresenham image suggests a simple solution. Walk along the line of discovered cells, but, whenever you have to make a diagonal step, consider the two intermediate cells. You can then use a line-rectangle intersection algorithm (see here) to determine which of the candidate cells should have, but wasn't, included.
I guess just to be complete, I decided to use a different algo. the one referenced here dtb's answer on another question.
here's the implementation
def getIntersectPts(strPt, endPt, geom=[0,1,0,0,0,1]):
'''
Find intersections pts for every half cell size
** cell size has only been tested with 1
Returns cell coordinates that the line passes through
'''
x0 = geom[0]
y0 = geom[3]
(sX, sY) = (strPt[0], strPt[1])
(eX, eY) = (endPt[0], endPt[1])
xSpace = geom[1]
ySpace = geom[5]
sXIndex = ((sX - x0) / xSpace)
sYIndex = ((sY - y0) / ySpace)
eXIndex = ((eX - sXIndex) / xSpace) + sXIndex
eYIndex = ((eY - sYIndex) / ySpace) + sYIndex
dx = (eXIndex - sXIndex)
dy = (eYIndex - sYIndex)
xHeading = 1.0 if dx > 0 else -1.0 if dx < 0 else 0.0
yHeading = 1.0 if dy > 0 else -1.0 if dy < 0 else 0.0
xOffset = (1 - (math.modf(sXIndex)[0]))
yOffset = (1 - (math.modf(sYIndex)[0]))
ptsIndexes = []
x = sXIndex
y = sYIndex
pt = (x, y) #1st pt
if dx != 0:
m = (float(dy) / float(dx))
b = float(sY - sX * m )
dx = abs(int(dx))
dy = abs(int(dy))
if dx == 0:
for h in range(0, dy + 1):
pt = (x, y + (yHeading *h))
ptsIndexes.append(pt)
return ptsIndexes
#print("m {}, dx {}, dy {}, b {}, xdir {}, ydir {}".format(m, dx, dy, b, xHeading, yHeading))
#print("x {}, y {}, {} {}".format(sXIndex, sYIndex, eXIndex, eYIndex))
#snap to half a cell size so we can find intersections on cell boundaries
sXIdxSp = round(2.0 * sXIndex) / 2.0
sYIdxSp = round(2.0 * sYIndex) / 2.0
eXIdxSp = round(2.0 * eXIndex) / 2.0
eYIdxSp = round(2.0 * eYIndex) / 2.0
# ptsIndexes.append(pt)
prevPt = False
#advance half grid size
for w in range(0, dx * 4):
x = xHeading * (w / 2.0) + sXIdxSp
y = (x * m + b)
if xHeading < 0:
if x < eXIdxSp:
break
else:
if x > eXIdxSp:
break
pt = (round(x), round(y)) #snapToGrid
# print(w, x, y)
if prevPt != pt:
ptsIndexes.append(pt)
prevPt = pt
#advance half grid size
for h in range(0, dy * 4):
y = yHeading * (h / 2.0) + sYIdxSp
x = ((y - b) / m)
if yHeading < 0:
if y < eYIdxSp:
break
else:
if y > eYIdxSp:
break
pt = (round(x), round(y)) # snapToGrid
# print(h, x, y)
if prevPt != pt:
ptsIndexes.append(pt)
prevPt = pt
return set(ptsIndexes) #elminate duplicates
I have attempted to make Conway's game of life in python, and then save the output into a picture, but I think there is something wrong with the logic as most of the pictures don't look quite correct. (see picture)
game of life pic:
import PIL.Image, random
WIDTH = 1366
HEIGHT = 768
ROUNDS = 10
DEAD = (0, 0, 0)
ALIVE = (0, 64, 255)
print("Creating image")
img = PIL.Image.new("RGB", (WIDTH, HEIGHT))
data = img.load()
print("Creating grid")
grid = []
for y in range(HEIGHT):
grid.append([])
for x in range(WIDTH):
grid[y].append(random.randint(0, 1))
for i in range(ROUNDS):
print("Starting round", i + 1, "of", ROUNDS)
for y in range(HEIGHT):
for x in range(WIDTH):
n = 0
for y2 in range(-1, 2):
for x2 in range(- 1, 2):
if x2 != 0 and y2 != 0 and grid[(y + y2) % HEIGHT][(x + x2) % WIDTH] == 1:
n += 1
if n < 2:
grid[y][x] = 0
elif n > 3:
grid[y][x] = 0
elif grid[y][x] == 1 and n > 1 and n < 4:
grid[y][x] = 1
elif grid[y][x] == 0 and n == 3:
grid[y][x] = 1
print("Rendering image")
for y in range(HEIGHT):
for x in range(WIDTH):
if grid[y][x] == 1:
data[x, y] = ALIVE
else:
data[x, y] = DEAD
print("Saving image")
img.save("gofl.png")
Your program cannot work correctly in its current state, because you compute the next generation in the same grid where the last generation is stored. You need a new (empty) grid to store the next generation. In your implementation you overwrite the last generation already while computing the next generation.