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, 102 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
from physics import *
from Tkinter import *
from random import randint
from traceback import format_exc

SCREEN_WIDTH = 400
SCREEN_HEIGHT = 400
BALLS = 10
BALL_RADIUS = 10
WALL_SPACE = 50
WALL_FORCE = 500
SPEED_LIMIT = 250
START_SPACE = 20
FPS = 40
BACKGROUND = 'white'
BALL_COLOR = 'red'

def main():
    initialise()
    mainloop()

def initialise():
    global balls, x, y, screen, lock
    space = WALL_SPACE + BALL_RADIUS
    balls = []
    for ball in xrange(BALLS):
        table = randint(0, 3)
        if table >> 1:
            x = randint(space, SCREEN_WIDTH - space)
            y = -START_SPACE if table & 1 else START_SPACE + SCREEN_HEIGHT
        else:
            x = -START_SPACE if table & 1 else START_SPACE + SCREEN_WIDTH
            y = randint(space, SCREEN_HEIGHT - space)
        balls.append(Ball(x, y, BALL_RADIUS))
    root = Tk()
    root.resizable(False, False)
    root.title('Space 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 = Canvas(root, width=SCREEN_WIDTH, height=SCREEN_HEIGHT, background=BACKGROUND)
    screen.after(1000 / FPS, update)
    screen.after(10000 / FPS, unlock)
    screen.pack()
    lock = True

def move(event):
    global x, y
    if not lock:
        diff = Vector(x - event.x, y - event.y)
        screen.move(ALL, diff.x, diff.y)
        for ball in balls:
            ball.pos += diff
        x, y = event.x, event.y

def update():
    try:
        ident = screen.after(1000 / FPS, update)
        for mutate in container, 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(ALL)
        for ball in balls:
            x1 = ball.pos.x - BALL_RADIUS
            y1 = ball.pos.y - BALL_RADIUS
            x2 = ball.pos.x + BALL_RADIUS
            y2 = ball.pos.y + BALL_RADIUS
            screen.create_oval(x1, y1, x2, y2, fill=BALL_COLOR)
    except:
        screen.after_cancel(ident)
        screen.delete(ALL)
        screen.create_text(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, text=format_exc(), font='Courier 10', fill='red')

def container(ball):
    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
    if ball.pos.y <= space:
        ball.vel.y += force
    elif ball.pos.y >= SCREEN_HEIGHT - space:
        ball.vel.y -= force

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

def unlock():
    global lock
    lock = False

if __name__ == '__main__':
    main()

This recipe led to the design of the following two recipes:

(1) Bouncy Balls (2) Explosive Embers