This program is an mutated version of Bouncing Balls Demonstration. Bombs fall to the floor, but if they touch each other, they explode with velocity and are quickly repelled away from each other. All bombs are identified with numbers and can be clicked to change colors. If you wish to follow individual bombs, you may wish to note their numbers. Otherwise, you may also change their colors in an attempt to keep track of them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | import random # FOR RANDOM BEGINNINGS
from Tkinter import * # ALL VISUAL EQUIPMENT
WIDTH = 500 # OF SCREEN IN PIXELS
HEIGHT = 500 # OF SCREEN IN PIXELS
BALLS = 20 # IN SIMULATION
WALL = 50 # FROM SIDE IN PIXELS
WALL_FORCE = 500 # ACCELERATION PER MOVE
SPEED_LIMIT = 5000 # FOR ball VELOCITY
BALL_RADIUS = 15 # FOR ballS IN PIXELS
OFFSET_START = 100 # FROM WALL IN PIXELS
FRAMES_PER_SEC = 40 # SCREEN UPDATE RATE
COLORS = 'red', 'orange', 'yellow', \
'green', 'blue', 'purple'
################################################################################
def main():
# Start the program.
initialise()
mainloop()
def initialise():
# Setup simulation variables.
global active
active = False
build_balls()
build_graph()
def build_graph():
# Build GUI environment.
global graph, left, top
root = Tk()
root.resizable(False, False)
root.title('Rain of Bombs')
left = (root.winfo_screenwidth() - WIDTH) / 2
top = (root.winfo_screenheight() - HEIGHT) / 2
root.geometry('%dx%d+%d+%d' % (WIDTH, HEIGHT, left, top))
root.bind_all('<Escape>', lambda event: event.widget.quit())
root.bind('<Configure>', window_move)
graph = Canvas(root, width=WIDTH, height=HEIGHT, background='white')
graph.after(1000 / FRAMES_PER_SEC, update)
graph.after(1000, activate)
graph.bind('<1>', change)
graph.pack()
def change(event):
try:
ball = balls[int(graph.gettags(graph.find_withtag(CURRENT))[0])]
ball.color = (ball.color + 1) % len(COLORS)
except:
pass
def activate():
# Active window_move event.
global active
active = True
def window_move(event):
# Respond to movements.
global left, top
if active:
diff = TwoD(left - event.x, top - event.y)
for ball in balls:
if HEIGHT - WALL - 2 < ball.position.y and top > event.y:
ball.velocity.y -= (1000 * (top - event.y))
ball.position += diff
left, top = event.x, event.y
def update():
# Main simulation loop.
graph.after(1000 / FRAMES_PER_SEC, update)
draw()
move()
def draw():
graph.delete(ALL)
# Draw sides.
graph.create_rectangle((0, 0, WALL - BALL_RADIUS, HEIGHT), fill='light green')
graph.create_rectangle((WIDTH - WALL + BALL_RADIUS, 0, WIDTH, HEIGHT), fill='light green')
# Draw floor.
y = HEIGHT - WALL + BALL_RADIUS + 2
graph.create_line((WALL - BALL_RADIUS, y, WIDTH - WALL + BALL_RADIUS, y), fill='blue', width=3)
# Draw all balls.
for n, ball in enumerate(balls):
x1 = ball.position.x - BALL_RADIUS
y1 = ball.position.y - BALL_RADIUS
x2 = ball.position.x + BALL_RADIUS
y2 = ball.position.y + BALL_RADIUS
graph.create_oval((x1, y1, x2, y2), fill=COLORS[ball.color], tag=n)
graph.create_text(ball.position.x, ball.position.y, text=str(n+1), tag=n)
graph.update()
def move():
# Move all balls.
for force in simulate_wall, simulate_gravity, simulate_friction:
for ball in balls:
force(ball)
for ball in balls:
ball.update_velocity(balls)
for ball in balls:
ball.move()
def simulate_wall(ball):
# Create viewing boundaries.
if ball.position.x < WALL:
ball.velocity.x += WALL_FORCE
elif ball.position.x > WIDTH - WALL:
ball.velocity.x -= WALL_FORCE
if ball.position.y >= HEIGHT - WALL:
ball.velocity.y *= -1
ball.position.y = HEIGHT - WALL
def simulate_gravity(ball):
# Create a pull.
ball.velocity.y += 50
def simulate_friction(ball):
# Slow velocity down.
ball.velocity *= .9
def limit_speed(ball):
# Limit ball speed.
if ball.velocity.mag() > SPEED_LIMIT:
ball.velocity /= ball.velocity.mag() / SPEED_LIMIT
def build_balls():
# Create balls variable.
global balls
balls = tuple(Ball(WIDTH, HEIGHT, OFFSET_START, FRAMES_PER_SEC) for ball in xrange(BALLS))
################################################################################
# TWO DIMENTIONAL VECTOR CLASS
class TwoD:
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __repr__(self):
return 'TwoD(%s, %s)' % (self.x, self.y)
def __add__(self, other):
return TwoD(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return TwoD(self.x - other.x, self.y - other.y)
def __mul__(self, other):
return TwoD(self.x * other, self.y * other)
def __div__(self, other):
return TwoD(self.x / other if other else self.x, self.y / other if other else self.y)
def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self
def __isub__(self, other):
self.x -= other.x
self.y -= other.y
return self
def __imul__(self, other):
self.x *= other
self.y *= other
return self
def __idiv__(self, other):
self.x /= other
self.y /= other
return self
def mag(self):
return ((self.x ** 2) + (self.y ** 2)) ** 0.5
################################################################################
# BALL IMPLEMENTATION CLASS
class Ball:
def __init__(self, width, height, offset, move_divider):
self.velocity = TwoD(0, 0)
self.position = TwoD(*(-offset if random.randint(0, 1) else width + offset, random.randint(1, height)))
self.move_divider = move_divider * 5
self.color = 0
def update_velocity(self, balls):
vector = TwoD(0, 0)
for ball in balls:
if ball is not self:
if (self.position - ball.position).mag() < (BALL_RADIUS * 2 + 2):
vector -= (ball.position - self.position)
self.__temp = vector * self.velocity.mag() / vector.mag() * 10
def move(self):
self.velocity += self.__temp
limit_speed(self)
self.position += self.velocity / self.move_divider
################################################################################
# Execute the simulation.
if __name__ == '__main__':
main()
|
This recipe continues the work started while programming a boids example solution in Python. Different features are changed, and an alternate program is produced. Within the field of simulation, very small changes can have very large results, and that is what this recipe demonstrates. The code here may also prove useful to those who are interested in GUI programming and are wondering where to start. I usually do not enjoy writing GUI applications such as this, but the best way to adjust to what you do not like is to take on a fun project that will force you to learn new things. This recipe certainly is not the greatest example of GUI or simulation programming, but it is a start -- something that can be improved and learned from.