# Stuck on ball collision

Page 1 of 1

## 4 Replies - 1070 Views - Last Post: 02 September 2013 - 08:03 PMRate Topic: //<![CDATA[ rating = new ipb.rating( 'topic_rate_', { url: 'http://www.dreamincode.net/forums/index.php?app=forums&module=ajax&section=topics&do=rateTopic&t=328094&amp;s=9bbb420d6286d6aece21e06f6d315b81&md5check=' + ipb.vars['secure_hash'], cur_rating: 0, rated: 0, allow_rate: 0, multi_rate: 1, show_rate_text: true } ); //]]>

### #1 Hikaroshi

Reputation: 8
• Posts: 42
• Joined: 26-April 09

# Stuck on ball collision

Posted 01 September 2013 - 02:05 PM

Hi everyone, I am back.

I was working on an old assignment (2010) for fun. Also because I was really stuck on it the first time and as of this year, programming has now clicked. Because of this, I have been doing little coding exercises and have been revisiting old assignments to see if I could easily write out the code. I have most of it down, but there is a little glitch.

```from graphics import *
from math import *
import random

W = 300
H = 300
ballList = []
squareList = []

#----------------------------------------------------------------
def simul(win, ballList):
for step in range( 200 ):
for i in range(len(ballList)):
c, dx, dy = ballList[i]
x = c.getCenter().getX()
y = c.getCenter().getY()

if (x  <= 0 + r ) or (x >= W - r):
dx = -dx
if (y <= 0 + r) or(y >= H - r):
dy = -dy

# stop the ball's movement
if isInSquare(x, y, r):
dx = 0
dy = 0

# make it bounce off the black box's bounds
if hitBlackBox(x, y, r):
dx = -dx
dy = -dy

(boolVal, ball1, ball2) = hasCollided(ballList) # create a tuple
if boolVal == True:
# change the dx and dy of each ball that satisfies condition
# then reverse the dx, dy of collided balls
c1, dx1, dy1 = ball1
c2, dx2, dy2 = ball2

# replace old ballList values with current ballList values
for list in ballList:
if ball1[0] in list:
dx1 = -dx1
dy1 = -dy1
list.pop(1)
list.insert(1, dx1)
list.pop(2)
list.insert(2, dy1)
if ball2[0] in list:
dx2 = -dx2
dy2 = -dy2
list.pop(1)
list.insert(1, dx2)
list.pop(2)
list.insert(2, dy2)
c1.move(dx1, dy1)
c2.move(dx2, dy2)
#                boolVal = False # need something of the sort

else:
c.move(dx, dy)
ballList[ i ] = [ c, dx, dy ] # update ball values

if allInBoxes(ballList):
end(win)

def allInBoxes(ballList):
counter = 0
for i in range(len(ballList)):
c, dx, dy = ballList[i]
if (dy == dx == 0):
counter += 1
if counter == len(ballList):
return True
return False

def hasCollided(ballList):
for i in range(len(ballList)-1):
for j in range(i+1, len(ballList)):
c1 = ballList[i][0]
c2 = ballList[j][0]
# get the dx1-2, dy1-2 vals
dx1 = ballList[i][1]
dy1 = ballList[i][2]
dx2 = ballList[j][1]
dy2 = ballList[j][2]
# get radius of i and j balls
# now get the points
P1 = Point(c1.getCenter().getX(), c1.getCenter().getY())
P2 = Point(c2.getCenter().getX(), c2.getCenter().getY())
#print "(", P1.getX(), ",",P1.getY(),")", "(" , P2.getX() , ",",P2.getY(),")", "This is P1, P2"
dist = distance(P1, P2)
if((r1 + r2) >= dist >= 0):
##                if dist < (r1 + r2):
##                    largeDist = False
##                    while largeDist == False:
##                        if dist < (r1 + r2):
##                            if(dx1 < 0 and dx2 < 0) and (dy1 < 0 and dy2 < 0) or\
##                            (dx1 > 0 and dx2 > 0) and (dy1 > 0 and dy2 > 0):
##                                #dist < (r1 + r2)
##                                # keep pushing by -dx1-2, -dy1-2 values
##                                c1.move(-dx1, -dy1)
##                                c2.move(dx2, dy2)
##                                P1 = Point(c1.getCenter().getX(), c1.getCenter().getY())
##                                P2 = Point(c2.getCenter().getX(), c2.getCenter().getY())
##                                dist = distance(P1, P2)
##                        else:
##                            largeDist = True
return True, ballList[i], ballList[j]
return False, 0, 0

def distance(P1, P2):
return sqrt( pow( P1.getX() - P2.getX(), 2 ) + pow( P1.getY() - P2.getY(), 2 ))

def isInSquare(x, y, r):
for i in range(len(squareList)-1): # exclude black box
sP1 = squareList[i].getP1() # (bOriginX, bOriginY)
sP2 = squareList[i].getP2() # (bW, bH)

if (sP2.getX() - r >= x >= sP1.getX() + r) and (sP2.getY() - r >= y >= sP1.getY() + r):
return True

def hitBlackBox(x, y, r):
# get last box
blackBox = squareList[-1]
sP1 = blackBox.getP1() # (bOriginX, bOriginY) | square P1
sP2 = blackBox.getP2() # (bW, bH) | square P2
if (sP2.getX() + r >= x >= sP1.getX() - r) and (sP2.getY() + r >= y  >= sP1.getY() - r):
return True

def drawSquare(win):
for i in range(4): # create 3 white boxes with random coords
bOriginX = random.randrange(0, W-50, 51)
bOriginY = random.randrange(0, H-100, 51)
bW = bOriginX + 50 # you have to include origin + width therefore 100-50 = 50 sBoxW
bH = bOriginY + 50
square = Rectangle(Point(bOriginX, bOriginY), Point(bW, bH))
square.draw(win)
square.setFill("white")
squareList.append(square)
squareList[-1].setFill("black") # Fill last square drawn with black

# this commented portion can work too, remove the previous line, set loop range(3)
##    blackSquare = Rectangle(Point(H-50,W-50), Point((W-50)+50, (H-50)+ 50))
##    blackSquare.draw(win)
##    blackSquare.setFill("black")
##    squareList.append(blackSquare)

# -------------------------------------------
def end(win):
waitForClick( win, "Click to End" )
win.close()

#----------------------------------------------------------------
def waitForClick( win, message ):
""" waitForClick: stops the GUI and displays a message.
Returns when the user clicks the window. The message is erased."""

# wait for user to click mouse to start
startMsg = Text( Point( win.getWidth()/2, win.getHeight()/2 ), message )
startMsg.draw( win )    # display message
win.getMouse()          # wait
startMsg.undraw()       # erase

#----------------------------------------------------------------
def main():
win = GraphWin( "moving ball", W, H )
#--- define a ball position and velocity ---
drawSquare(win)
ballColor = ["red", "blue", "pink", "purple", "green"]
for i in range(2):
c = Circle( Point( random.randrange( 16, W-15, 20 ), random.randrange( H/3, 2*H/3, 20 ) ), 15 )
c.setFill(ballColor[i%len(ballColor)])
c.draw(win)
ballList.append([c, 5 - random.randrange(10), 5 - random.randrange(10)])

for i in range(len(ballList)): # loop: no ball should be 0 dx and 0 dy
if ballList[i][1] == ballList[i][2] == 0:
ballList[i][1] == 1

waitForClick( win, "Click to Start" )

simul(win, ballList)
end(win)

main()
```

The program loads some balls of varying colors. The program waits for the user to click to start, the balls bounce off the edge of the screen, each other, and a black box. There are 4 boxes on the screen loaded at random positions. Three of them stop the ball's movement if the ball is inside of the bounds of the box. The last one (black box) acts as an obstacle and the ball bounces off the black box upon collision. The program ends at the end of loop or if all balls are in a white box.

I am having problems with the collision detection function called hasCollided(). If the balls load on top of each other or manage to get that way, they jiggle back and fourth and the program spends the rest of the iterations trying to get them to push away from each other. I noticed this happens if the balls overlap with each other because the dx, dy increment values are too small to effectively push them away on the first iteration. The program spends all of it's time trying to get them unstuck but it moves the balls back by -dx1, -dy1 and -dx2, -dy2 then back to dx1, dy1, dx2, dy2 creating the jiggle.

I tried to create a while loop inside of hasCollided() for the condition of the balls either as being originally drawn to overlap or if they overlap naturally due based on how much they collide by. It didn't work out. See commented section. I realized my thinking isn't 100% right on the collision for both the black box (hitBlackBox()) and the ball collision function (hasCollided()) because the balls cannot get unstuck in situations where the ball overlaps with the object it must oppose. Can someone explain to me what I should have done instead?

This post has been edited by Hikaroshi: 01 September 2013 - 02:07 PM

Is This A Good Question/Topic? 0

## Replies To: Stuck on ball collision

### #2 tlhIn`toq

• Freelance developer

Reputation: 6220
• Posts: 13,726
• Joined: 02-June 10

## Re: Stuck on ball collision

Posted 01 September 2013 - 06:04 PM

If I am picturing this right... Giggle would mean in a downward collision they hit, move up a pixel, then down a pixel then up then down ... a giggle.

Sounds like you aren't effectively changing the direction after the collision. Once two balls hit the continued direction of the down-traveling ball should become up-traveling and continue that direction.

If the two balls hit, change direction, and move away from each other... Yet are still considered to be in a state of collision after the move (causing them to change direction again back into each other {repeat})... You've got a basic logic error someplace. Either they were allowed to travel too far into a state of overlap before being detected as colliding, or they are being re-checked for collision before they have moved away from each other.

### #3 woooee

Reputation: 27
• Posts: 109
• Joined: 21-November 12

## Re: Stuck on ball collision

Posted 01 September 2013 - 08:12 PM

You can take a look at the hypotenuse function in the following which is in Tkinter, but the Graphics module is just a subset of Tkinter so you should be able to understand it. You have to get the center x and y for each ball, store both sets of coordinates, which appears to be the problem in the code above as you loop through each one separately, and then calculate the hypotenuse of a right triangle = the distance between the centers and see if it is equal to or smaller than the sum of the two radii. If you then reverse the X and Y direction (angle of incidence=angle of reflection) you should get the desired result. The radius should be constant so you only have to store that once in the beginning. Perhaps the diagram below will explain it better.
```      A
y1-y2 |\
= side| \
|  \
|___B  <-- X1-X2 = bottom

try:
import Tkinter as tk     ## Python 2.x
except ImportError:
import tkinter as tk     ## Python 3.x

import time
import math

class MovingCircles():
def __init__(self):
self.root = tk.Tk()

self.x_width = 600
self.y_height = 300
self.canvas = tk.Canvas(self.root, width=self.x_width,
height=self.y_height)
self.canvas.pack()
## line above the exit button
self.canvas.create_line(0, self.y_height, self.x_width, self.y_height, width=2.0)

## key=tk id for circle-->[x, y, radius, move_x_distance, move_y]
self.circle_dict={}
self.drawcircle(100, 100, 20, 'blue', 1, 1)
self.drawcircle(500, 100, 20, 'red', 3, 1)

b1 = tk.Button(text="Exit", bg='red', command=self.exit)
b1.pack()

self.running = True
self.start_moving()

self.root.mainloop()

def drawcircle(self, x, y, rad, color, move_x, move_y):
##                            move x & y
self.circle_dict[cir] = [x, y, move_x, move_y]

def exit(self):
self.running = False

def hypotenuse(self):
centers_list = []
for circle in self.circle_dict:
## center point for circle = x, y
centers_list.append([self.circle_dict[circle][0], \
self.circle_dict[circle][1]])
x_diff = abs(centers_list[0][0] - centers_list[1][0])     # x differences
y_diff = abs(centers_list[0][1] - centers_list[1][1])     # y differences
between = math.sqrt(x_diff**2 + y_diff**2)
if between < 42:     ## 2 radii * 20 each
return True
return False

def move_circle(self, circle):
move_x = self.circle_dict[circle][2]
move_y = self.circle_dict[circle][3]
self.canvas.move(circle, move_x, move_y)
self.circle_dict[circle][0] += move_x     ## x
self.circle_dict[circle][1] += move_y     ## y

## test for edges
if self.circle_dict[circle][0] < 21 or \
self.circle_dict[circle][0] > self.x_width-21:
self.circle_dict[circle][2] *= -1     ## x move direction

if self.circle_dict[circle][1] < 21 or \
self.circle_dict[circle][1] > self.y_height-21:
self.circle_dict[circle][3] *= -1     ## y move direction

time.sleep(0.01)
self.canvas.update()

def start_moving(self):
ctr = 0
time_to_sleep=0
time_list = [0.02, 0.010]
while (ctr < 3000) and self.running:
ctr += 1
for circle in self.circle_dict:
self.move_circle(circle)
if self.hypotenuse():
circles_list = self.circle_dict.keys()
self.circle_dict[circles_list[0]][2] *= -1     ## x coord
self.circle_dict[circles_list[1]][2] *= -1     ## x coord
self.circle_dict[circles_list[1]][3] *= -1     ## y coord
self.root.destroy()
self.root.quit()

MC=MovingCircles()
```

This post has been edited by woooee: 01 September 2013 - 08:20 PM

### #4 baavgai

• Dreaming Coder

Reputation: 6608
• Posts: 13,948
• Joined: 16-October 07

## Re: Stuck on ball collision

Posted 02 September 2013 - 07:27 AM

My first response was to use objects. On a closer you, you are, but they're GUI objects and honestly seem to be getting in your way.

I would create objects to help, rather than hinder, your program.

Something like:
```# from graphics import *
import graphics as gui
from math import *
import random

class Square(object):
def __init__(self, x1, y1, x2, y2, isBlack=False):
self.x1, self.y1, self.x2, self.y2, self.isBlack = x1, y1, x2, y2, isBlack
self.gobj = None

def draw(self, canvas):
pt1 = gui.Point(self.x1, self.y1)
pt2 = gui.Point(self.x2, self.y2)
self.gobj = gui.Rectangle(pt1, pt2)
if self.isBlack:
self.gobj.setFill("black")
else:
self.gobj.setFill("white")
self.gobj.draw(canvas)

class Ball(object):
def __init__(self, cx, cy, radius, dx, dy):
self.dx, self.dy = 0, 0
self.captured = False
self.gobj = None

def setCaptured(self):
self.dx, self.dy = 0, 0
self.captured = True

def draw(self, canvas, color):

def isInsideSquare(self, sq):

def inSquare(self, sq):

def move(self):
if self.gobj and not self.captured:
self.gobj.move(dx, dy)

def bounceOff(self, sq):

def run(balls, whiteSquares, blackSquares, width, height):
for step in range( 200 ):
allCaptured = True
for ball in balls:
if not ball.captured:
allCaptured = False
for sq in whiteSquares:
if ball.isInsideSquare(sq):
ball.setCaptured()
break

# ...

```

### #5 Hikaroshi

Reputation: 8
• Posts: 42
• Joined: 26-April 09

## Re: Stuck on ball collision

Posted 02 September 2013 - 08:03 PM

@baavgai: Thank you for your response. Wish I saw it earlier. You're right though, objects would be better suited for this. I was doing it exactly as the assignment requirements at that time (using functions), but as I realized I needed more functions to do things, I started to wonder if OOP would be better suited for this.

In your 'code sketch' we would have a class that bridges the ball class and square class together. From there, the square objects would still get pushed into a list, and that's what gets passed in the Square class and also in methods like isInsideSquare(), correct?

Since I didn't see your response until now, I managed to get a little further on the ball collision logic. I think it's significantly better now, and if I stopped right now I would feel pretty okay. Sometimes the balls would get pushed out of bounds by the other ball because I created a while loop inside of the collision detection function but couldn't wrap my mind around creating another function to do that check. Then I realized it would be more efficient if I could reuse that hypothetical function for the two if statements in simul() that check for collision at the bounds of the screen. I left the black box thing alone, but figured it was almost the same as the while loop in the hasCollided() function. Then I realized that I would have to consider the balls going out of bounds based on the original direction of the ball relative to the location of the black box and whether it's at a corner. I think I might call it quits, but I wouldn't mind an explanation on what I could have done for the case of screen bound checking + the while loop and the black box (balls still getting stuck and jiggling) OOP or not is fine.

I edited the "if boolVal == True" statement in simul based on the changes I made in the "if (dist <= (r1 + r2)):" statement until the end of that function. I created a function called "isSameSign()" and attempted to do something with another function called "isInBounds()".

```from graphics import *
from math import *
import random

W = 300
H = 300
ballList = []
squareList = []

#----------------------------------------------------------------
def simul(win, ballList):
for step in range( 200 ):
for i in range(len(ballList)):
c, dx, dy = ballList[i]
x = c.getCenter().getX()
y = c.getCenter().getY()

if (x  <= 0 + r ) or (x >= W - r):
dx = -dx
if (y <= 0 + r) or(y >= H - r):
dy = -dy

# stop the ball's movement
if isInSquare(x, y, r):
dx = 0
dy = 0

# make it bounce off the black box's bounds
if hitBlackBox(x, y, r):
dx = -dx
dy = -dy

(boolVal, ball1, ball2) = hasCollided(ballList) # create a tuple
if boolVal == True:
# change the dx and dy of each ball that satisfies condition
# then reverse the dx, dy of collided balls
c1, dx1, dy1 = ball1
c2, dx2, dy2 = ball2

# replace old ballList values with current ballList values
for list in ballList:
if ball1[0] in list:
list.pop(1)
list.insert(1, dx1)
list.pop(2)
list.insert(2, dy1)
if ball2[0] in list:
list.pop(1)
list.insert(1, dx2)
list.pop(2)
list.insert(2, dy2)
c1.move(dx1, dy1)
c2.move(dx2, dy2)

else:
c.move(dx, dy)
ballList[ i ] = [ c, dx, dy ] # update ball values

if allInBoxes(ballList):
end(win)

def allInBoxes(ballList):
counter = 0
for i in range(len(ballList)):
c, dx, dy = ballList[i]
if (dy == dx == 0):
counter += 1
if counter == len(ballList):
return True
return False

def hasCollided(ballList):
newBallList = []
for i in range(len(ballList)-1):
for j in range(i+1, len(ballList)):
c1 = ballList[i][0]
c2 = ballList[j][0]
# get the dx1-2, dy1-2 vals
dx1 = ballList[i][1]
dy1 = ballList[i][2]
dx2 = ballList[j][1]
dy2 = ballList[j][2]
# get radius of i and j balls
# now get the points
P1 = Point(c1.getCenter().getX(), c1.getCenter().getY())
P2 = Point(c2.getCenter().getX(), c2.getCenter().getY())
#print "(", P1.getX(), ",",P1.getY(),")", "(" , P2.getX() , ",",P2.getY(),")", "This is P1, P2"
dist = distance(P1, P2)
if (dist <= (r1 + r2)):
if isSameSign(dx1, dx2): # push away one ball if sign's same
dx1 = -dx1
elif isSameSign(dy1, dy2):
dy1 = -dy1
else: # assume opposing sign, intend to push away each ball
dx1 = -dx1
dy1 = -dy1
dx2 = -dx2
dy2 = -dy2
while (dist <= (r1 + r2)):
c1.move(dx1, dy1)
c2.move(dx2, dy2)
P1 = Point(c1.getCenter().getX(), c1.getCenter().getY())
P2 = Point(c2.getCenter().getX(), c2.getCenter().getY())
dist = distance(P1, P2) # call again, get updated points

newBallList.append([c1, dx1, dy1])
newBallList.append([c2, dx2, dy2])
return True, newBallList[0], newBallList[1]
return False, 0, 0

def distance(P1, P2):
return sqrt( pow( P1.getX() - P2.getX(), 2 ) + pow( P1.getY() - P2.getY(), 2 ))

def isSameSign(a, B)/>/>/>/>:
# check if both signs are same
if ((a < 0) and (b < 0)) or ((a > 0) and (b > 0)):
return True

# Complete but incomplete concept (not implemented):
# This function is to assist the while loop in the collision check
# to ensure the balls don't knock each other out of the screen.
# I was going to integrate this in the while loop above and
# find the ball that is not closest to the bounds and push
# that one in the opposite direction, then bounce the other one.
# I've been working on this program for a few days, I think it's better
# to do something else now, so I'll leave this incomplete.
def isInBounds(c1, c2):
boundsList = []
boundsList.append(c1)
boundsList.append(c2)
for i in range(len(boundsList)):
x = boundsList[i].getCenter().getX()
y = boundsList[i].getCenter().getY()
if ((x  <= 0 + r ) or (x >= W - r)) or ((y <= 0 + r) or(y >= H - r)):
return False
return True

def isInSquare(x, y, r):
for i in range(len(squareList)-1): # exclude black box
sP1 = squareList[i].getP1() # (bOriginX, bOriginY)
sP2 = squareList[i].getP2() # (bW, bH)

if (sP2.getX() - r >= x >= sP1.getX() + r) and (sP2.getY() - r >= y >= sP1.getY() + r):
return True

# Another complete but incomplete concept.
# I was going to do make the code push out
# any balls that are already inside/go inside the black box.
# It's pretty much like the while loop, but I think it's better
# to move on. On to another project!
def hitBlackBox(x, y, r):
# get last box
blackBox = squareList[-1]
sP1 = blackBox.getP1() # (bOriginX, bOriginY) | square P1
sP2 = blackBox.getP2() # (bW, bH) | square P2
if (sP2.getX() + r >= x >= sP1.getX() - r) and (sP2.getY() + r >= y  >= sP1.getY() - r):
return True

def drawSquare(win):
for i in range(4): # create 3 white boxes with random coords
bOriginX = random.randrange(0, W-50, 51)
bOriginY = random.randrange(0, H-100, 51)
bW = bOriginX + 50 # you have to include origin + width therefore 100-50 = 50 sBoxW
bH = bOriginY + 50
square = Rectangle(Point(bOriginX, bOriginY), Point(bW, bH))
square.draw(win)
square.setFill("white")
squareList.append(square)
squareList[-1].setFill("black") # Fill last square drawn with black

# this commented portion can work too, remove the previous line, set loop range(3)
##    blackSquare = Rectangle(Point(H-50,W-50), Point((W-50)+50, (H-50)+ 50))
##    blackSquare.draw(win)
##    blackSquare.setFill("black")
##    squareList.append(blackSquare)

# -------------------------------------------
def end(win):
waitForClick( win, "Click to End" )
win.close()

#----------------------------------------------------------------
def waitForClick( win, message ):
""" waitForClick: stops the GUI and displays a message.
Returns when the user clicks the window. The message is erased."""

# wait for user to click mouse to start
startMsg = Text( Point( win.getWidth()/2, win.getHeight()/2 ), message )
startMsg.draw( win )    # display message
win.getMouse()          # wait
startMsg.undraw()       # erase

#----------------------------------------------------------------
def main():
win = GraphWin( "moving ball", W, H )
#--- define a ball position and velocity ---
drawSquare(win)
ballColor = ["red", "blue", "pink", "purple", "green"]
for i in range(5):
c = Circle( Point( random.randrange( 16, W-15, 20 ), random.randrange( H/3, 2*H/3, 20 ) ), 15 )
c.setFill(ballColor[i%len(ballColor)])
c.draw(win)
ballList.append([c, 5 - random.randrange(10), 5 - random.randrange(10)])

for i in range(len(ballList)): # loop: no ball should be 0 dx and 0 dy
if ballList[i][1] == ballList[i][2] == 0:
ballList[i][1] == 1

waitForClick( win, "Click to Start" )

simul(win, ballList)
end(win)

main()
```

@woooee: I think I understand what you're talking about. I would have thought with the same distance formula it wouldn't make a difference unless you mean making the ball move based on its initial direction before the collision call changes the direction to -dx, -dy.

This post has been edited by Hikaroshi: 02 September 2013 - 08:13 PM