I was doing a pathfinding visualizer in pygame and I pretty much finished but there's still one thing that I do not like about the algorithm part of it and it's the fact that when you press the visualize algorithm button it shows you the shortest path in yellow and all of the nodes the algorithm has visited ever in light blue but it shows you instantaneously and I want it to color the nodes accordingly step by step to actually reach the effect of visualizing (like in here https://clementmihailescu.github.io/Pathfinding-Visualizer/#), I tried to write some code in the function that seemed like it would have worked as intended but it didn't, here is the code:
# Breadth First Search Algorithm
def bfs(graph, start, goal):
explored = []
# Queue for traversing the
# graph in the BFS
queue = [[start]]
# If the desired node is
# reached
if start == goal:
return
# Loop to traverse the graph
# with the help of the queue
while queue:
path = queue.pop(0)
node = path[-1]
y, x = node
# Codition to check if the
# current node is not visited
if node not in explored and nodes_rows[x][y].color is not BLACK:
nodes_rows[x][y].color = LIGHT_BLUE
neighbours = graph[node]
# Loop to iterate over the
# neighbours of the node
for neighbour in neighbours:
new_path = list(path)
new_path.append(neighbour)
queue.append(new_path)
# Condition to check if the
# neighbour node is the goal
if neighbour == goal:
new_path.remove(start)
new_path.remove(goal)
return new_path
explored.append(node)
return None
The nodes_rows[x][y].color == color_name is the code that is responsible for coloring nodes on the grid which is represented by a dictionary(I provided it so it's gonna be easier for you to understand how coloring works in general in my program). The problem with that implementation is when I do add the coloring part at if statement to color all the neighbors it does it instantly on the grid without showing a kind of an animation that shows the coloring process node by node, my question is can I do it so it colors them each iteration rather than all at once by adding something to this code and not writing a new one and if I do need to write a new one that what is the instructions how can I do so?
Here is what I mean by coloring all at once like it does for now:
https://cdn.discordapp.com/attachments/772816508015083552/832303260911272046/PowerPoint_-_1_2021-04-15_20-13-35_Trim.mp4
Edit:
try:
while True:
if not ticks or pygame.time.get_ticks() - ticks >= 500:
ticks = pygame.time.get_ticks()
nodes = next(algorithm)
if nodes_rows[nodes[-1][1]][nodes[-1][0]].color != BLUE:
nodes_rows[nodes[-1][1]][nodes[-1][0]].color = LIGHT_BLUE
pygame.display.update()
except StopIteration:
pass
Tried doing it with yield and if I print it it does yield a list every half a second with a new explored node at the end like intended but it updates it all at once after waiting total amount of ticks I tried playing with the indent of display.update() but didn't work either I don't even know what to do at this point
Thanks to everyone contributing to help <3
Per the comments above, here is a simple example of a generator to help you grasp the idea.
def counter(x):
for i in range(x):
yield i
c = counter(3)
In: next(c)
Out: 0
In: next(c)
Out: 1
In: next(c)
Out: 2
What's happening is that every time you call next, the function will continue to run until it reaches the next yield, at which point it will return the yielded value.
It will also remember where it left off, so the next time you call next, the function will continue where it left off.
In your application, this could be used to yield the list of explored locations, then draw those locations in whatever color you like, call next(bfs) to step forward and yield the next list of explored locations, and so on until of course you find the solution and run out of items to yield.
One more example, perhaps a little more closely related to what you are trying to do:
def make_path():
path = []
i = 0
while True:
path.append(i)
i += 1
yield path
c = make_path()
for _ in range(6):
print(next(c))
Out: [0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]
i think the problem is that you do not update the canvas in your while queue loop.
the program will execute your bfs algorithm and then it will update the canvas.
i honestly don't use pygame very regularly, but i think to force the canvas to repaint you need to stick a pygame.display.update() inside your while loop.
Pygame is not my forte but, I think there are some missing things in your project:
1.- You don't really control when colors are shown on your screen. This can be achieved with this:
pygame.display.update()
Documentation: https://devdocs.io/pygame/ref/display#pygame.display.update
Usually when working with graphics, you add a color, and when then next screen painting update comes you see it, what you must do is to force the painting so you can animate safely.
2.- Coloring animations are not being done by default.
I couldn't help noticing this in your initial question: "it does it instantly on the grid without showing a kind of an animation that shows the coloring process node by node". If you want an animation like the example linked above (Pathfinding visualizer), I'm afraid you have to do it manually. Try creating a test with a white square and start painting growing circles inside interpolating colors, until you reach the walls and paint the full square. This should be similar to what are you trying to accomplish.
3.- Perhaps you should add some stops when you are animating your canvas.
The refresh rate of your hardware is way too faster than your human eye.
To properly see a coloring animation you should try adding execution stops, when interpolating colors.
nodes_rows[x][y].color = Color(r, g, b) # Start with a color and interpolate incrementing or decrementing r, g and b until your reach a final color.
pygame.display.update() # Update your screen
pygame.time.wait(1000) # In milliseconds, add some time wait so screen can be painted, if time is long enough perhaps screen updates meanwhile and you do not need to update the screen.
4.- (Optional) Parallel painting: Using threads.
If this does not work right for you, add threads.
As I stated before, pygame is not my forte, but consider adding threads to animate each square color. I've found a post here explaining this.
using threading in pygame
I hope this helps you and have a happy coding experience! :D
Related
I need to save each and every pixels the turtle moves through as it moves from say(-305,305) to (205,305).
I want to save each and every of those coordinates on the path in a variable. Is there anyway to do it?
I.
You need some kind of mechanism for intervention to the drawing process,
similar to the update function in the Tkinter library and the callback functions of the OpenGL library.
There is an ontimer function in the Turtle library, which can perform some action
(in your case - insertion into a list) after a given time period.
This method is shown below. It works but have some limitations:
1. it doesn't work if drawing speed is fastest ( turtle.speed('fastest') ),
2. it doesn't want to insert a very first pair of coordinates in a list - you have to do this insertion by yourself,
3. Some parts of a drawing may have gaps. When replaying from a list, which stores coordinates only, such situation is not processed (you will get a drawing without gaps).
import turtle as tr
from turtle import *
time = 3 # 3 milliseconds period
status = False
coord=[] # List of coordinates
sx, sy = -305, 305 # Start point
ex, ey = 205, 305 # End point
def func(): # Timer callback function
if status:
coord.append([tr.xcor(),tr.ycor()]) # list of lists
# coord.append(pos()) # list of tuples
ontimer(func, t = time)
def redraw(): # Replay function
tr.up()
tr.goto(coord[0][0], coord[0][1])
tr.down()
for i in range(1, len(coord)):
tr.goto(coord[i][0], coord[i][1])
def restart2(): # Keyboard callback function
tr.reset()
redraw()
tr.screensize(768, 512) # Preliminary actions
tr.setup(width = 1.0, height = 1.0)
tr.onkeypress(restart2)
tr.listen()
tr.up()
tr.goto(sx,sy)
coord.append([sx, sy]) # Manual recording of the first point - list of lists case
#coord.append(tr.pos()) # list of tuples case
status = True
tr.ontimer(func, t = time) # Start timer
tr.down()
tr.goto(ex, ey) # Main drawing process
status = False # "Stop" timer (timer pause for this example)
tr.up()
tr.home()
coord.append([tr.xcor(), tr.ycor()]) # Manual recording of the first point after gap
status = True
tr.ontimer(func, t = time) # Continue timer
tr.down()
while True: # Examlpe from the Turtle library docs
tr.forward(200)
tr.left(170)
if abs(tr.pos()) < 1:
break
tr.home()
tr.circle(50)
tr.circle(120, 180)
status = False # "Stop" timer
print(coord) # print to console
tr.done()
Press any key for replay path from the list.
II.
A lot of drawings have analytical solutions. It is much simpler then store a lot of coordinates.
It is probably simpler (but not faster?) to store the coordinates of the beginning and the end of drawing parts and to calculate desired intermediate points analytically.
So I'm learning python in a virtual programming class and I have an assignment where I have to create a picture using Turtle Graphics. I'm using things like for loops and programmer-defined functions in the program but I'm having a problem with something I want to do in my picture. I'm drawing clouds in the sky and I'm trying to get the clouds to be drawn at different positions. I'm increasing the x coordinate by increments of 40 but I'm trying to get the cloud to be placed higher and lower as it goes across. It's at the end of this list of code:
import turtle
def backFill(b, c, x, y, l, h):
b.penup()
b.setpos(x, y)
b.color(c)
b.pendown()
b.begin_fill()
for side in range(2):
b.forward(l)
b.left(90)
b.forward(h)
b.left(90)
b.end_fill()
def drawCloud(c, x, y):
c.penup()
c.setpos(x, y)
c.pendown()
c.color("grey")
c.begin_fill()
for side in range(5):
c.circle(10)
c.left(80)
c.end_fill()
def main():
print("Cars")
joe = turtle.Turtle()
joe.speed(0)
backFill(joe,"green",-200,-100,400,25)
backFill(joe,"black",-200,-75,400,75)
backFill(joe,"green",-200,0,400,25)
backFill(joe,"sky blue",-200,25,400,110)
x=-192.5
for side in range(10):
backFill(joe,"yellow",x,-40,25,5)
x=x+40
x=-180
y=100
for side in range(15):
drawCloud(joe,x,y)
x=x+40
y=y-10
main()
Currently the clouds slowly descend as each one is drawn but what I'm trying to do is make clouds at different heights like one cloud is at 100 the next 90 then back to 100, etc. I tried things like y=y-10, y+10 to see if the first time it repeated it would go down 10 then the next time it would go up 10.
https://gyazo.com/3ad5268231b3217b81636cc070573b75
tl;dr/simpler explanation: I'm trying to move a looped picture up and down so it's not simply in a straight line. How would I move it down one time then up another time within a for loop?
You could check whether side is even or odd to decide whether to increment or decrement your cloud location. Like so:
for side in range(15):
drawCloud(joe,x,y)
x=x+40
if(side%2==0):
y=y-10
else:
y=y+10
for side in range(5):
drawCloud(joe,x,y)
x=x+40
y=y-10
drawCloud(joe,x,y)
x=x+40
y=y+10
I am fairly new to the python language and psychopy. I am practicing it by creating dummy experiments. Here, I am trying to create an experiment about bayesian brain. Non-vertical lines will be presented to the participant while no respond is expected from the participants, just exposure. Then for the last trial (it stays on the monitor for longer period of time to be responded), it is expected from the participant to judge whether the last line trial is vertical or not? (after exposing to non-vertical lines, I am expecting to see a change in perception of verticality).
However, there are so many things that I couldn't learn from the web. I am pretty sure you guys can help me easily.
My primary problem is; how to set up the orientation of the line? I found out the stim.ori but not sure how to use it on 'line' stimuli. Below I've attached the codes that I made so far. Also, I have added some extra questions with #.
I tried to be clear as much as I can. Sorry for my bad english.
Thank you!
from psychopy import visual, core, event #import some libraries from PsychoPy
import random
#create a window
mywin = visual.Window([800,600],monitor="testMonitor", units="deg")
#stimuli
lineo = visual.Line(mywin, start=(-5, -1), end=(-5, 1))
fixation = visual.GratingStim(mywin, size=0.2, pos=[0,0], color = 'black')
#draw the stimuli and update the window
n = 5 # trial number
i = 0
while i < n:
#fixation
fixation.draw()
mywin.flip()
presses = event.waitKeys(1)
# stimulus
orientationlist = [20,30,40,50,60] # I want to draw the orientation info from this list
x = random.choice(orientationlist)
lineo.ori((x)) #
lineo.draw()
mywin.flip()
presses= event.waitKeys(2)
i +=1
if i == 5: # how do I change the number into the length of the trial; len(int(n) didnt work.
lineo.draw()
mywin.flip()
presses = event.waitKeys(4)
#quiting
# I dont know how to command psychopy for quiting the
# experiment when 'escape' is pressed.
#cleanup
mywin.close()
core.quit()
There's a few things that you would want to do differently. I've updated your code and marked changes with the comment "CHANGE". Changing the orientation of a stimulus is pretty consistent in psychopy, so it's no different for Line than any other visual stimulus type.
from psychopy import visual, core, event #import some libraries from PsychoPy
import random
#create a window
mywin = visual.Window([800,600],monitor="testMonitor", units="deg")
#stimuli
lineo = visual.Line(mywin, start=(-5, -1), end=(-5, 1))
fixation = visual.GratingStim(mywin, size=0.2, pos=[0,0], color = 'black')
orientationlist = [20,30,40,50,60] # CHANGED. No need to redefine on every iteration of the while-loop.
#draw the stimuli and update the window
n = 5 # trial number
for i in range(n): # CHANGED. This is much neater in your case than a while loop. No need to "manually" increment i.
#fixation
fixation.draw()
mywin.flip()
event.waitKeys(1) # CHANGED. No need to assign output to anything if it isn't used.
# stimulus
lineo.ori = random.choice(orientationlist) # CHANGED. Alternative: lineo.setOri(random.choice(orientationlist)).
lineo.draw()
mywin.flip()
event.waitKeys(2)
# At this point, all the stimuli have been shown. So no need to do an if-statement within the loop. The following code will run at the appropriate time
lineo.draw()
mywin.flip()
event.waitKeys(keyList=['escape']) # CHANGED. Listen for escape, do not assign to variable
# CHANGED. No need to call core.quit() or myWin.close() here since python automatically cleans everything up on script end.
Im writing an algorithm in Python which plays this game.
The current state of the board of tiles in the game is a dictionary in the form of:
{
<tile_id>: {
'counters': <number of counters on tile or None>,
'player': <player id of the player who holds the tile or None>,
'neighbours': <list of ids of neighbouring tile>
},
...
}
I have another dictionary which stores all of my tiles which are 'full' (i.e. a tile which has one less counter than its number of neighbours and where the player is me) This dictionary, full_tiles, is in the same form as the board dictionary above.
I am now trying to create a list, chains, where each element in the list is a dictionary of my full tiles that are neighbouring at least one other full tile (i.e a chain of full tiles). So this will be a list of all my seperate chains on the board.
Here is my code so far:
for tile_id, tile in full_tiles.items(): #iterates through all full tiles
current_tile = {tile_id : tile} #temporarily stores current tile
if not chains: #if chains list is empty
chains.append(current_tile) #begin list
else: #if list is not empty
for index, chain in enumerate(chains): #iterate though list of chains
if not (current_tile in chain): #if current tile is not in current chain
for tile_id2, tile2 in chain.items(): #iterate through tiles in current chain
for neighbour in tile2["neighbours"]: #iterate through each neighbour of current tile
#neighbour is in the form of tile_id
if neighbour in chain: #if current tile's neighbour is in chain
chain[tile_id] = tile #add tile to chain
It is very difficult for me to test and debug my code and check if it is working correctly as the code can only be run in an application that simulates the game. As you can see, there is quite a lot going on in this block of code with all of the nested loops which are difficult to follow. I cannot seem to think straight at the minute and so I cannot determine if this mess, in all honesty, will function as I hope.
While I am writing this, I have just realised that - on line 7 of this code - I am only checking if the current tile is not in the current chain and so there will be intersecting chains which, of course, will be a mess. Instead of this, I need to first check if the current tile is in not in any of the chains, not just one.
Apart from this error, will my code achieve what I am attempting? Or can you recommend a simpler, neater way to do it? (There has to be!)
Also, let me know if I have not given enough information on how the code is run or if I need to explain anything further, such as the contents of the board dictionary.
Thank you for any help in advance.
EDIT: Unfortunately, I was under a time constraint to complete this project, and as it was my first time ever working with Python, I currently lack the knowledge in the language to optimise my solution using the sources given below. Here is my final extremely ugly and messy solution to this problem which, in the end, worked fine and wasn't terribly inefficient given the small data set that the code works on.
for x in range(0, len(my_hexplode_chains) - 1):
match_found = False
for y in range(x + 1, len(my_hexplode_chains)):
for tile_id_x, tile_x in my_hexplode_chains[x].items(): #compare each chain in list
for tile_id_y, tile_y in my_hexplode_chains[y].items(): #to every other chain
for neighbour in tile_x["neighbours"]: #if tiles in different lists
if neighbour == tile_id_y: #are neighbours
match_found = True
my_hexplode_chains[x].update(my_hexplode_chains[y]) #append one chain to the other
del my_hexplode_chains[y] #delete appended chain
if match_found: #continue loop at next chain
break #very ugly way to do this
if match_found:
break
if match_found:
break
if match_found:
break
How about this optimization?
def find_match (my_hexplode_chains):
x = 0
len_chain = len(my_hexplode_chains)
while x <= len_chain:
y = x + 1
for tile_id_x, tile_x in my_hexplode_chains[x].items():
for tile_id_y, tile_y in my_hexplode_chains[y].items():
if tile_id_y in tile_x["neighbours"]:
my_hexplode_chains[x].update(my_hexplode_chains[y])
del my_hexplode_chains[y]
return True
x += 1
return False
You could pass this function after each move in your game and trace the output.
These seem to be the relevant portions of a slightly larger program. These are essentially the collision detection functions. bubbles is the original list of circles. I want the circle collided with to disappear on impact. I'm fairly sure my problem lies down there in "if collide ==" conditional.
def getDistance(point1,point2):
a= point1.getX()
b= point2.getX()
c= point1.getY()
d= point2.getY()
distance= math.sqrt((b-a)**2 + ((d-c)**2))
return distance
def balloonBubbleCollide(balloon,bubble):
point1 = balloon.getCenter()
point2= bubble.getCenter()
distance= getDistance(point1, point2)
if distance <= 30:
return True
def check(balloon, bubbles, window):
for bubble in bubbles:
collide = balloonBubbleCollide(balloon, bubble)
if collide == True:
bubbles.remove(bubble)
Assume main is running them in proper order. Just don't want to bog the post down with code.
You should not modify a list using remove while iterating over it.
To filter out the colliding bubbles use instead something like:
def check(balloon, bubbles, window):
bubbles[:] = [bubble for bubble in bubbles
if not baloonBubbleCollide(balloon, bubble)]
the code shown will create a first new list of bubble objects to keep (using a list comprehension) and then replace the current content of the bubbles list with it at once.