How to keep random walk scenario from going outside graphics window - python

I have created a random walk scenario where it takes one step in a random direction for a specific number of times. The one thing that I have run in to is that sometimes it will go off of the graphics window that I have set up and I can no longer see where it is at.
Here is the code:
from random import *
from graphics import *
from math import *
def walker():
win = GraphWin('Random Walk', 800, 800)
win.setCoords(-50, -50, 50, 50)
center = Point(0, 0)
x = center.getX()
y = center.getY()
while True:
try:
steps = int(input('How many steps do you want to take? (Positive integer only) '))
if steps > 0:
break
else:
print('Please enter a positive number')
except ValueError:
print('ERROR... Try again')
for i in range(steps):
angle = random() * 2 * pi
newX = x + cos(angle)
newY = y + sin(angle)
newpoint = Point(newX, newY).draw(win)
Line(Point(x, y), newpoint).draw(win)
x = newX
y = newY
walker()
My question is, Is there a way that I can set parameters on the graphics window so that the walker can not go outside the window? And if it tries to, it would just turn around and try another direction?

Try defining upper and lower bounds for x and y. Then use a while loop that keeps trying random points until the next one is in bounds.
from random import *
from graphics import *
from math import *
def walker():
win = GraphWin('Random Walk', 800, 800)
win.setCoords(-50, -50, 50, 50)
center = Point(0, 0)
x = center.getX()
y = center.getY()
while True:
try:
steps = int(input('How many steps do you want to take? (Positive integer only) '))
if steps > 0:
break
else:
print('Please enter a positive number')
except ValueError:
print('ERROR... Try again')
# set upper and lower bounds for next point
upper_X_bound = 50.0
lower_X_bound = -50.0
upper_Y_bound = 50.0
lower_Y_bound = -50.0
for i in range(steps):
point_drawn = 0 # initialize point not drawn yet
while point_drawn == 0: # do until point is drawn
drawpoint = 1 # assume in bounds
angle = random() * 2 * pi
newX = x + cos(angle)
newY = y + sin(angle)
if newX > upper_X_bound or newX < lower_X_bound:
drawpoint = 0 # do not draw, x out of bounds
if newY > upper_Y_bound or newY < lower_Y_bound:
drawpoint = 0 # do not draw, y out of bounds
if drawpoint == 1: # only draw points that are in bounds
newpoint = Point(newX, newY).draw(win)
Line(Point(x, y), newpoint).draw(win)
x = newX
y = newY
point_drawn = 1 # set this to exit while loop
walker()

Related

Particles attracting each other not working properly

I'm trying to make particles attract each other in Python. It works a bit but they always move to the top-left corner (0;0).
A year ago, CodeParade released a video about a game of life he made with particles. I thought it was cool and wanted to recreate it myself in Python. It wasn't that difficult but I have a problem. Every time some particles are near enough to attract each other, they get a bit closer but at the same time they "run" to the upper-left corner which happens to be (0;0). I first thought I wasn't applying the attraction effect correctly but after re-reading it multiple times I haven't found any errors. Does somebody have any idea why it doesn't work as expected ?
/ Here is the code /
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygame, random, time
import numpy as np
attraction = [ [-2.6,8.8,10.2,0.7],
[4.1,-3.3,-3.1,4.4],
[0.6,3.7,-0.4,5.1],
[-7.8,0.3,0.3,0.0]]
minR = [[100.0,100.0,100.0,100.0],
[100.0,100.0,100.0,100.0],
[100.0,100.0,100.0,100.0],
[100.0,100.0,100.0,100.0]]
maxR = [[41.7,16.4,22.1,15.0],
[16.4,41.7,32.0,75.1],
[22.1,32.0,55.7,69.9],
[15.0,75.1,69.9,39.5]]
colors = [ (200,50,50),
(200,100,200),
(100,255,100),
(50,100,100)]
#Rouge
#Violet
#Vert
#Cyan
particles = []
#Number of particles
numberParticles = 5
#Width
w = 500
#Height
h = 500
#Radius of particles
r = 4
#Rendering speed
speed = 0.05
#Attraction speed factor
speedFactor = 0.01
#Min distance factor
minRFactor = 0.1
#Max distance factor
maxRFactor = 2
#Attraction factor
attractionFactor = 0.01
def distance(ax, ay, bx, by):
return intg((ax - bx)**2 + (ay - by)**2)
def intg(x):
return int(round(x))
def display(plan):
#Fill with black
#Momentarily moved to main
#pygame.Surface.fill(plan,(0,0,0))
#For each particle, draw it
for particle in particles:
pygame.draw.circle(plan,colors[particle[0]],(particle[1],particle[2]),r)
#Update display
pygame.display.flip()
def update(particles):
newParticles = []
for particleIndex in xrange(len(particles)):
typeId, x, y = particles[particleIndex]
othersX = [[],[],[],[]]
othersY = [[],[],[],[]]
#For every other particles
for otherParticle in particles[0:particleIndex]+particles[particleIndex+1:]:
otherTypeId, otherX, otherY = otherParticle
"""
#Draw minR and maxR of attraction for each color
pygame.draw.circle(screen,colors[otherTypeId],(x,y),intg(minR[typeId][otherTypeId] * minRFactor),1)
pygame.draw.circle(screen,colors[otherTypeId],(x,y),intg(maxR[typeId][otherTypeId] * maxRFactor),1)
"""
#If otherParticle is between minR and maxR from (x;y)
if (minR[typeId][otherTypeId] * minRFactor)**2 <= distance(x,y,otherX,otherY) <= (maxR[typeId][otherTypeId] * maxRFactor)**2:
#Append otherParticle's coordinates to othersX and othersY respectively
othersX[otherTypeId].append(otherX)
othersY[otherTypeId].append(otherY)
#Take the average attractions for each color
othersX = [np.mean(othersX[i]) * attraction[typeId][i] * attractionFactor for i in xrange(len(othersX)) if othersX[i] != []]
othersY = [np.mean(othersY[i]) * attraction[typeId][i] * attractionFactor for i in xrange(len(othersY)) if othersY[i] != []]
#If not attracted, stay in place
if othersX == []:
newX = x
else:
#Take the average attraction
avgX = np.mean(othersX)
#Determine the new x position
newX = x - (x - avgX) * speedFactor
#If out of screen, warp
if newX > w:
newX -= w
elif newX < 0:
newX += w
#If not attracted, stay in place
if othersY == []:
newY = y
else:
#Take the average attraction
avgY = np.mean(othersY)
#Determine the new y position
newY = y - (y - avgY) * speedFactor
#If out of screen, warp
if newY > h:
newY -= h
elif newY < 0:
newY += h
#Append updated particle to newParticles
newParticles.append([typeId,intg(newX),intg(newY)])
return newParticles
if __name__ == "__main__":
#Initialize pygame screen
pygame.init()
screen = pygame.display.set_mode([w,h])
#Particle = [type,posX,posY]
#Create randomly placed particles of random type
for x in xrange(numberParticles):
particles.append([random.randint(0,3),random.randint(0,w),random.randint(0,h)])
display(screen)
#Wait a bit
time.sleep(1)
while True:
#raw_input()
#Fill the screen with black
pygame.Surface.fill(screen,(0,0,0))
#Update particles
particles = update(particles)
#Display particles
display(screen)
#Wait a bit
time.sleep(speed)
The issue is in the lines:
othersX = [np.mean(othersX[i]) * attraction[typeId][i] * attractionFactor for i in range(len(othersX)) if othersX[i] != []]
othersY = [np.mean(othersY[i]) * attraction[typeId][i] * attractionFactor for i in range(len(othersY)) if othersY[i] != []]
othersX and othersY should be positions, but since the coordinates are multiplied by attraction[typeId][i] * attractionFactor, the coordinates are shifted to top left.
This can be evaluated with ease, by omitting the factors:
othersX = [np.mean(othersX[i]) for i in range(len(othersX)) if othersX[i] != []]
othersY = [np.mean(othersY[i]) for i in range(len(othersY)) if othersY[i] != []]
An option is to use vectors form (x, y) to (otherX, otherY) rather than positions:
for otherParticle in particles[0:particleIndex]+particles[particleIndex+1:]:
otherTypeId, otherX, otherY = otherParticle
if (minR[typeId][otherTypeId] * minRFactor)**2 <= distance(x,y,otherX,otherY) <= (maxR[typeId][otherTypeId] * maxRFactor)**2:
# Append otherParticle's coordinates to othersX and othersY respectively
othersX[otherTypeId].append(otherX - x)
othersY[otherTypeId].append(otherY - y)
othersX = [np.mean(othersX[i]) * attraction[typeId][i] * attractionFactor for i in range(len(othersX)) if othersX[i] != []]
othersY = [np.mean(othersY[i]) * attraction[typeId][i] * attractionFactor for i in range(len(othersY)) if othersY[i] != []]
Of course you've to adapt the calculation of the new positions, too:
avgX = np.mean(othersX)
newX = x + avgX * speedFactor
avgY = np.mean(othersY)
newY = y + avgY * speedFactor
As mentioned in the other answer, you should use floating point numbers for the calculations:
def distance(ax, ay, bx, by):
# return intg((ax - bx)**2 + (ay - by)**2)
return (ax - bx)**2 + (ay - by)**2
# newParticles.append([typeId,intg(newX),intg(newY)])
newParticles.append([typeId, newX, newY])
But round to integral coordinates when you draw the circles:
for particle in particles:
# pygame.draw.circle(plan,colors[particle[0]],(particle[1],particle[2]),r)
pygame.draw.circle(plan,colors[particle[0]],(intg(particle[1]),intg(particle[2])),r)
It might be this line here:
newParticles.append([typeId,intg(newX),intg(newY)])
You calculated the position of you particles with high precision earlier, but then the intg() will round all of those numbers down towards 0 before you save it to newparticles. Over time this will skew things towards [0,0].
How I would fix this is by keeping the data in your particles and newparticles as floating point precision, only do the rounding when you have to put things on screen. This way the high accuracy you use will be kept from one timestep to the next.

making poisson spheres distribution on python but cannot figure out where is the bug

I am new to programming, so I hope my stupid questions do not bug you.
I am now trying to calculate the poisson sphere distribution(a 3D version of the poisson disk) using python and then plug in the result to POV-RAY so that I can generate some random distributed packing rocks.
I am following these two links:
[https://github.com/CodingTrain/Rainbow-Code/blob/master/CodingChallenges/CC_33_poisson_disc/sketch.js#L13]
[https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf]
tl;dr
0.Create an n-dimensional grid array and cell size = r/sqrt(n) where r is the minimum distance between each sphere. All arrays are set to be default -1 which stands for 'without point'
1.Create an initial sample. (it should be placed randomly but I choose to put it in the middle). Put it in the grid array. Also, intialize an active array. Put the initial sample in the active array.
2.While the active list is not empty, pick a random index. Generate points near it and make sure the points are not overlapping with nearby points(only test with the nearby arrays). If no sample can be created near the 'random index', kick the 'random index' out. Loop the process.
And here is my code:
import math
from random import uniform
import numpy
import random
radius = 1 #you can change the size of each sphere
mindis = 2 * radius
maxx = 10 #you can change the size of the container
maxy = 10
maxz = 10
k = 30
cellsize = mindis / math.sqrt(3)
nrofx = math.floor(maxx / cellsize)
nrofy = math.floor(maxy / cellsize)
nrofz = math.floor(maxz / cellsize)
grid = []
active = []
default = numpy.array((-1, -1, -1))
for fillindex in range(nrofx * nrofy * nrofz):
grid.append(default)
x = uniform(0, maxx)
y = uniform(0, maxy)
z = uniform(0, maxz)
firstpos = numpy.array((x, y, z))
firsti = maxx // 2
firstj = maxy // 2
firstk = maxz // 2
grid[firsti + nrofx * (firstj + nrofy * firstk)] = firstpos
active.append(firstpos)
while (len(active) > 0) :
randindex = math.floor(uniform(0,len(active)))
pos = active[randindex]
found = False
for attempt in range(k):
offsetx = uniform(mindis, 2 * mindis)
offsety = uniform(mindis, 2 * mindis)
offsetz = uniform(mindis, 2 * mindis)
samplex = offsetx * random.choice([1,-1])
sampley = offsety * random.choice([1,-1])
samplez = offsetz * random.choice([1,-1])
sample = numpy.array((samplex, sampley, samplez))
sample = numpy.add(sample, pos)
xcoor = math.floor(sample.item(0) / cellsize)
ycoor = math.floor(sample.item(1) / cellsize)
zcoor = math.floor(sample.item(2) / cellsize)
attemptindex = xcoor + nrofx * (ycoor + nrofy * zcoor)
if attemptindex >= 0 and attemptindex < nrofx * nrofy * nrofz and numpy.all([sample, default]) == True and xcoor > 0 and ycoor > 0 and zcoor > 0 :
test = True
for testx in range(-1,2):
for testy in range(-1, 2):
for testz in range(-1, 2):
testindex = (xcoor + testx) + nrofx * ((ycoor + testy) + nrofy * (zcoor + testz))
if testindex >=0 and testindex < nrofx * nrofy * nrofz :
neighbour = grid[testindex]
if numpy.all([neighbour, sample]) == False:
if numpy.all([neighbour, default]) == False:
distance = numpy.linalg.norm(sample - neighbour)
if distance > mindis:
test = False
if test == True and len(active)<len(grid):
found = True
grid[attemptindex] = sample
active.append(sample)
if found == False:
del active[randindex]
for printout in range(len(grid)):
print("<" + str(active[printout][0]) + "," + str(active[printout][1]) + "," + str(active[printout][2]) + ">")
print(len(grid))
My code seems to run forever.
Therefore I tried to add a print(len(active)) in the last of the while loop.
Surprisingly, I think I discovered the bug as the length of the active list just keep increasing! (It is supposed to be the same length as the grid) I think the problem is caused by the active.append(), but I can't figure out where is the problem as the code is literally the 90% the same as the one made by Mr.Shiffman.
I don't want to free ride this but I have already checked again and again while correcting again and again for this code :(. Still, I don't know where the bug is. (why do the active[] keep appending!?)
Thank you for the precious time.

How I can make my Mandelbrot plotter faster?

How I can make my Python program faster? This program calculates the Mandelbrot set and draws it with turtle. I think the problem is in the for loop. Maybe the steps are taking too much time.
import numpy as np
import turtle
turtle.ht()
turtle.pu()
turtle.speed(0)
turtle.delay(0) turtle.colormode(255)
i= int(input("iteration = "))
g = int(input("accuracy = "))
xmin = float(input("X-min: "))
xmax = float(input("X-max: "))
ymin = float(input("Y-min: "))
ymax = float(input("Y-max: "))
cmode = int(255/i)
input("PRESS TO START")
for x in np.arange(xmin,xmax,1/g):
for y in np.arange(ymin,ymax,1/g):
c = x + y * 1j
z = 0
t = 1
for e in range(i):
z = z * z + c
if abs(z) > 3:
turtle.setx(g*c.real)
turtle.sety(g*c.imag)
turtle.dot(2,e*cmode,e*cmode,e*cmode)
t = 0
if t == 1:
turtle.setx(g*c.real)
turtle.sety(g*c.imag)
turtle.dot(2,"black")
input("Calculated!")
turtle.mainloop()
Here is an example
The following rework should be a hundred times faster than your original:
import numpy as np
import turtle
i = int(input("iteration = "))
g = int(input("accuracy = "))
xmin = float(input("X-min: "))
xmax = float(input("X-max: "))
ymin = float(input("Y-min: "))
ymax = float(input("Y-max: "))
cmode = int(255 / i)
input("PRESS TO START")
turtle.hideturtle()
turtle.penup()
turtle.speed('fastest')
turtle.colormode(255)
turtle.setundobuffer(None) # turn off saving undo information
turtle.tracer(0, 0)
for x in np.arange(xmin, xmax, 1 / g):
for y in np.arange(ymin, ymax, 1 / g):
c = x + y * 1j
z = 0
t = True
for e in range(i):
z = z * z + c
if abs(z) > 3.0:
turtle.setposition(g * c.real, g * c.imag)
rgb = e * cmode
turtle.dot(2, rgb, rgb, rgb)
t = False
break
if t:
turtle.setposition(g * c.real, g * c.imag)
turtle.dot(2, "black")
turtle.update()
print("Calculated!")
turtle.mainloop()
The significant change is the use of the combination of tracer() and update() to avoid visually plotting every dot for the user and just drawing as each vertical column completes.

How to make objects not touch in Python

So, I'm making a program and the program makes 250 dots in random colors and prints them on a window. But they cannot touch each other.
Here is my code
from graphics import *
from random import *
import math
def main():
win = GraphWin("Dots", 1100, 650)
dots = []
points = []
for x in range(0,250):
drawCircle(win, dots, points)
checkOverLap(dots, points)
drawAllCircles(win, dots)
def drawCircle(win, array, points):
p1 = randint(15,1085)
p2 = randint(15,635)
dot = Circle(Point(p1, p2), 15)
r = lambda: randint(0,255)
dot.setFill('#%02X%02X%02X' % (r(),r(),r()))
array.append(dot)
points.append(Point(p1, p2))
def checkOverLap(array, points):
count = 0
for x in range(0, 250):
for y in range(0, 250):
if x != y:
if math.hypot(points[y].getX() - points[x].getX(), points[y].getY() - points[x].getY()) < 30:
dist = math.hypot(points[y].getX() - points[x].getX(), points[y].getY() - points[x].getY())
newCircle = Circle(Point(points[x].getX() + (abs(dist - 31)), points[x].getY() + (abs(dist - 31))), 15)
r = lambda: randint(0,255)
newCircle.setFill('#%02X%02X%02X' % (r(),r(),r()))
array[x] = newCircle
def drawAllCircles(win, array):
for x in range(0, 250):
array[x].draw(win)
main()
Any help would be great!
Thanks!
from graphics import *
from random import *
import math
def main():
win = GraphWin("Dots", 1100, 650)
dots = []
for x in xrange(250):
#Create a random circle
circle = getdrawCircle(15, 635, 15, 1085, 15, 15)
#Repeat while overlap with other circles
while checkOverLap(circle, dots):
#Create a new random circle
circle = getdrawCircle(15, 635, 15, 1085, 15, 15)
#The new circle isn't overlap, then append to list dots
dots.append(circle)
drawAllCircles(win, dots)
def getdrawCircle(min_height, max_height, min_width, max_width, min_radius, max_radius):
x = randint(min_height, max_height)
y = randint(min_width, max_width)
dot = Circle(Point(y, x), randint(min_radius, max_radius))
r = lambda: randint(0,255)
dot.setFill('#%02X%02X%02X' % (r(),r(),r()))
return dot
#If circle overlap with circles in array then return True
def checkOverLap(circle, array):
for circle_cmp in array:
dist = math.hypot(circle.getCenter().getX() - circle_cmp.getCenter().getX(),
circle.getCenter().getY() - circle_cmp.getCenter().getY())
if dist < circle.getRadius() + circle_cmp.getRadius():
return True
return False
def drawAllCircles(win, array):
for x in range(0, 250):
array[x].draw(win)
main()
I do not have a windows computer, so I will give you my best answer.
Try picking the random number for the coordinates of the circle, and loop through the already drawn circles and see if a circle drawn at those coordinates would touch anything else. Using a while loop, you can keep picking random coordinates until they do not touch anything else:
circles = list_of_circles_drawn
radius = radius_of_circles;
x = random.randint(1, 1000)
y = random.randint(1, 1000)
while any([math.sqrt(math.pow(math.fabs(x-c.x), 2)+math.pow(math.fabs(y-c.y), 2)) < radius for c in circles]):
x = random.randint(1, 1000)
y = random.randint(1, 1000)

Get circle to orbit with given equations using Python graphics.py module

I was given this equation in order to get the circle to orbit. I created an infinite loop assuming it should orbit forever. x = cx + r*cos(t) and y = cy + r*sin(t)
Am I doing something wrong?
from graphics import *
import math
def main():
win=GraphWin("Cirlce",600,600)
x=250
y=70
c=Circle(Point(x,y),18)
c.draw(win)
v=True
while v==True:
c.undraw()
x = x + c.getRadius()*math.cos(2)
y = y + c.getRadius()*math.sin(2)
c=Circle(Point(x,y),18)
c.draw(win)
main()
The problem is here:
x = x + c.getRadius()*math.cos(2)
y = y + c.getRadius()*math.sin(2)
You are moving in a straight line. And, as your code runs quite fast, it probably goes out of scope quite quickly. The correct version would be:
x0, y0 = 0, 0 # Coordinates of the centre
r = 2 # Radius
t = 0
dt = 0.01 # Or anything that looks smooth enough.
while True: # No need for an extra variable here
c.undraw() # I don't know this graphics library
# I will assume what you did is correct
x = x0 + r * math.cos(t)
y = y0 + r * math.sin(t)
c=Circle(Point(x,y),18)
c.draw(win)
t += dt
time.sleep(0.01)
At the end of the loop I send it to sleep for a bit so it goes at a finite pace. Some graphics libraries include a rate function, that allows you to run it at a fixed number of frames per second, independently of how fast the loop is in your machine.
c.undraw() # I don't know this graphics library, I will assume what
you did is correct
Using c.undraw(), c = Circle(...), and c.draw() on every loop iteration seems wasteful when GraphicsObjects can move(dx, dy). However, the tricky part is the movement is relative so you have to calculate the difference to your next position:
import math
import time
from graphics import *
WINDOW_WIDTH, WINDOW_HEIGHT = 600, 600
win = GraphWin("Circle", WINDOW_WIDTH, WINDOW_HEIGHT)
win.setCoords(-WINDOW_WIDTH / 2, -WINDOW_HEIGHT / 2, WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2)
ORBIT_RADIUS = 200
PLANET_RADIUS = 18
SOLAR_RADIUS = 48
x0, y0 = 0, 0 # Coordinates of the center
t = 0.0
dt = 0.01 # Or anything that looks smooth enough.
delay = 0.01
star = Circle(Point(x0, y0), SOLAR_RADIUS)
star.setFill("yellow")
star.draw(win)
orbit = Circle(Point(x0, y0), ORBIT_RADIUS)
orbit.setOutline("lightgray")
orbit.draw(win)
planet = Circle(Point(x0 + ORBIT_RADIUS * math.cos(t), y0 + ORBIT_RADIUS * math.sin(t)), PLANET_RADIUS)
planet.setFill("blue")
planet.draw(win)
while True:
x, y = x0 + ORBIT_RADIUS * math.cos(t), y0 + ORBIT_RADIUS * math.sin(t)
center = planet.getCenter()
planet.move(x - center.getX(), y - center.getY())
t = (t + dt) % (2 * math.pi)
if win.checkMouse() is not None:
break
time.sleep(delay)
win.close()
I added a star as it's hard to appreciate that something is orbiting without seeing what's being orbited:
You can click on the window to exit cleanly.

Categories