Welcome, guest | Sign In | My Account | Store | Cart
NOTE: Recipes have moved! Please visit GitHub.com/activestate/code for the current versions.

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.

Python, 210 lines
  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.