Welcome, guest | Sign In | My Account | Store | Cart

This recipe is designed to show sample usage of the physics module. Once the program is running, the window can be dragged around the screen; and the balls will react accordingly. If an error is displayed on the screen, the window moved to show the rest of the error message.

Python, 140 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
import Tkinter
import random
import time
import traceback
import physics

################################################################################

BALLS = 10                  # NUMBER OF SIMULATED BALLS

BALL_RADIUS = 5             # RADIUS OF BALL IN PIXELS
START_SPACE = 20            # SIDE OFFSET IN PIXELS
SCREEN_WIDTH = 400          # WIDTH OF SCREEN IN PIXELS
SCREEN_HEIGHT = 400         # HEIGHT OF SCREEN IN PIXELS
WALL_SPACE = 50             # WIDTH OF WALLS IN PIXELS
FLOOR_SPACE = 25            # HEIGHT OF FLOOR IN PIXELS

BACKGROUND = 'white'        # COLOR OF BACKGROUND
BALL_COLOR = 'red'          # COLOR OF BALLS
FLOOR_COLOR = 'blue'        # COLOR OF FLOOR
FORCE_COLOR = 'light green' # COLOR OF FOURCE FIELD

FPS = 60                    # FRAMES PER SECOND
SPEED_LIMIT = 500           # PIXELS PER SECOND
WALL_FORCE = 500            # PIXELS PER SECOND
GRAV_RATE = 400             # PIXELS PER SECOND
FRIC_RATE = 0.875           # VELOCITY PER SECOND

################################################################################

def main():
    'Setup and start demonstration.'
    initialise()
    Tkinter.mainloop()

def initialise():
    'Build balls and prepare GUI.'
    global balls, x, y, screen, lock, start, frame
    balls = []
    for ball in xrange(BALLS):
        x = -START_SPACE if random.randint(0, 1) else START_SPACE + SCREEN_WIDTH
        y = random.randint(BALL_RADIUS, SCREEN_HEIGHT - FLOOR_SPACE - BALL_RADIUS)
        balls.append(physics.Ball(x, y, BALL_RADIUS))
    root = Tkinter.Tk()
    root.resizable(False, False)
    root.title('Bouncy Balls')
    x = (root.winfo_screenwidth() - SCREEN_WIDTH) / 2
    y = (root.winfo_screenheight() - SCREEN_HEIGHT) / 2
    root.geometry('%dx%d+%d+%d' % (SCREEN_WIDTH, SCREEN_HEIGHT, x, y))
    root.bind_all('<Escape>', lambda event: event.widget.quit())
    root.bind('<Configure>', move)
    screen = Tkinter.Canvas(root, width=SCREEN_WIDTH, height=SCREEN_HEIGHT, background=BACKGROUND)
    screen.after(1000 / FPS, update)
    screen.after(10000 / FPS, unlock)
    screen.pack()
    floor_height = SCREEN_HEIGHT - FLOOR_SPACE + 2
    screen.create_rectangle(0, 0, WALL_SPACE - 1, floor_height, fill=FORCE_COLOR)
    screen.create_rectangle(SCREEN_WIDTH - WALL_SPACE + 1, 0, SCREEN_WIDTH, floor_height, fill=FORCE_COLOR)
    screen.create_line(0, floor_height, SCREEN_WIDTH, floor_height, width=3, fill=FLOOR_COLOR)
    lock = True
    start = time.clock()
    frame = 1.0

def move(event):
    'Simulate movement of screen.'
    global x, y
    if not lock:
        diff = physics.Vector(x - event.x, y - event.y)
        screen.move('animate', diff.x, diff.y)
        floor_height = SCREEN_HEIGHT - FLOOR_SPACE - BALL_RADIUS
        for ball in balls:
            ball.pos += diff
            if ball.pos.y >= floor_height:
                ball.vel.y += diff.y * FPS
                floor(ball)
        x, y = event.x, event.y

def update():
    'Run physics and update screen.'
    global frame
    try:
        for mutate in wall, floor, gravity, friction, governor:
            for ball in balls:
                mutate(ball)
        for index, ball_1 in enumerate(balls[:-1]):
            for ball_2 in balls[index+1:]:
                ball_1.crash(ball_2)
        for ball in balls:
            ball.move(FPS)
        screen.delete('animate')
        for ball in balls:
            x1 = ball.pos.x - ball.rad
            y1 = ball.pos.y - ball.rad
            x2 = ball.pos.x + ball.rad
            y2 = ball.pos.y + ball.rad
            screen.create_oval(x1, y1, x2, y2, fill=BALL_COLOR, tag='animate')
        frame += 1
        screen.after(int((start + frame / FPS - time.clock()) * 1000), update)
    except:
        screen.delete(Tkinter.ALL)
        screen.create_text(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, text=traceback.format_exc(), font='Courier 10', fill='red', tag='animate')

def wall(ball):
    'Simulate a wall.'
    space = WALL_SPACE + BALL_RADIUS
    force = float(WALL_FORCE) / FPS
    if ball.pos.x <= space:
        ball.vel.x += force
    elif ball.pos.x >= SCREEN_WIDTH - space:
        ball.vel.x -= force

def floor(ball):
    'Simulate a floor.'
    floor_height = SCREEN_HEIGHT - FLOOR_SPACE - BALL_RADIUS
    if ball.pos.y >= floor_height:
        ball.pos.y = floor_height
        ball.vel.y *= -1

def gravity(ball):
    'Simulate gravity.'
    ball.vel.y += float(GRAV_RATE) / FPS

def friction(ball):
    'Simulate friction.'
    ball.vel *= FRIC_RATE ** (1.0 / FPS)

def governor(ball):
    'Simulate speed governor.'
    if abs(ball.vel) > SPEED_LIMIT:
        ball.vel = ball.vel.unit() * SPEED_LIMIT

def unlock():
    'Activate the "move" function.'
    global lock
    lock = False

################################################################################

if __name__ == '__main__':
    main()

This recipe is a redesigned version of the program presented here: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/502241