The recipe is a direct descendant of the program presented here: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/502251 This program was created at the suggestion of "Mark" (a friend of mine), and so the game was named in his honor. A high score table is supported and is automatically saved to and loaded from a file if possible. The object of the game is to click on each ball until it is purple. The score is based on how many seconds are left on the clock in the bottom, left corner of the screen. Have fun, and try to beat the game in sixty seconds or less; it is possible!
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 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 | # ORIGINAL IMPORTS
import random
from Tkinter import *
# GAME IMPORTS
import time
import tkSimpleDialog
import tkMessageBox
import zlib
################################################################################
# ORIGINAL VARIABLES
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
FLOOR_COLOR = 'blue' # COLOR OF GAME FLOOR
FORCE_COLOR = 'light green' # COLOR OF FORCE FIELDS
COLORS = '#FF0000', '#FF7F00', '#FFFF00', \
'#00FF00', '#0000FF', '#FF00FF' # COLOR CYCLE OF BALLS
TITLE = "Mark's Game (Version 1)" # TITLE OF PROGRAM
# GAME VARIABLES
TIME_LIMIT = 600 # IN SECONDS
SAMPLE_HST = {540: ['Wiz-Kid'],
480: ['Speed Daemon'],
420: ['[SW] O B 1'],
360: ['1337 Spartan'],
300: ['<<SHIFTED>>'],
240: ['NovaSuperNova'],
180: ['[ZT] Berserk Fury'],
120: ['[ZT] Shadow'],
60: ['newbie123'],
0: ['SiriuS']} # DATABASE DEMO
HST_WIDTH = 30 # IN CHARACTERS
MAX_NAME_WIDTH = 20 # IN CHARACTERS
HST_SEP = '.' # JOINS NAME AND SCORE
HST_COLOR = 'green' # COLOR OF HIGH SCORES
HST_BACK = 'black' # HST BACKGROUND
B1_COLOR = 'blue' # BUTTON FONT COLOR
B2_COLOR = 'red' # BUTTON SELECT COLOR
GAME_COLOR = 'white' # BACKGROUND COLOR
WIN_TEXT = 'YOU WIN' # VICORY MESSAGE
WIN_COLOR = 'red' # TEXT COLOR
BLINK_RATE = 500 # IN MILLISECONDS
PAUSE_TIME = 2250 # IN MILLISECONDS
HST_FILE = 'HST.dat' # HIGH SCORE FILENAME
################################################################################
# PROGRAM INITIALIZATION FUNCTIONS
def main():
# Start the program.
make_root()
high_score_table()
mainloop()
def make_root():
# Make root window and canvas.
global root, graph
root = Tk()
root.resizable(False, False)
root.title(TITLE)
root.protocol('WM_DELETE_WINDOW', quit_game)
root.bind_all('<Escape>', quit_game)
left = (root.winfo_screenwidth() - WIDTH) / 2
top = (root.winfo_screenheight() - HEIGHT) / 2
root.geometry('%dx%d+%d+%d' % (WIDTH, HEIGHT, left, top))
graph = Canvas(root, width=WIDTH, height=HEIGHT)
graph.pack()
def high_score_table():
global HS_database
# Create high score table.
if not globals().has_key('HS_database'):
HS_database = load_HST()
string = format_HST(HS_database)
graph.create_text(WIDTH / 2, HEIGHT / 2, text=string, font='Courier 15', fill=HST_COLOR, tag='HST')
graph.create_text(WIDTH / 2, 75, text='Start Game', font='Helvetica 25', fill=B1_COLOR, tag='start')
graph.tag_bind('start', '<Any-Enter>', select_start)
graph.tag_bind('start', '<Any-Leave>', deselect_start)
graph.tag_bind('start', '<1>', start_game)
graph['background'] = HST_BACK
################################################################################
# PROGRAM TERMINATION FUNCTION
def quit_game(event=None):
# Save HST and quit program.
file(HST_FILE, 'wb').write(zlib.compress(repr(HS_database), 9))
root.quit()
################################################################################
# HST PREPARATION FUNCTIONS
def load_HST():
# Load (H)igh (S)core (T)able.
try:
database = eval(zlib.decompress(file(HST_FILE, 'rb').read()))
names = 0
records = sum(map(len, SAMPLE_HST.values()))
for key in database.keys():
assert isinstance(key, int)
assert isinstance(database[key], list)
for name in database[key]:
assert isinstance(name, str)
names += 1
assert names <= records
assert names == records
return database
except:
return SAMPLE_HST
def format_HST(database):
# Format HST database to string.
lines = []
for key in sorted(database.keys(), reverse=True):
score = ' ' + str(key)
for name in database[key]:
lines.append((name[:MAX_NAME_WIDTH] + ' ').ljust(HST_WIDTH - len(score), HST_SEP) + score)
return '\n'.join(lines)
################################################################################
# MENU SUPPORT FUNCTIONS
def select_start(event):
# Highlight start button.
graph.itemconfig('start', fill=B2_COLOR)
def deselect_start(event):
# Deselect the start button.
graph.itemconfig('start', fill=B1_COLOR)
def start_game(event):
# Start game play.
graph.delete(ALL)
initialise()
################################################################################
# GAME SETUP FUNCTIONS
def initialise():
# Setup simulation variables.
build_balls()
build_graph()
def build_balls():
# Create balls variable.
global balls
balls = tuple(Ball(WIDTH, HEIGHT, OFFSET_START, FRAMES_PER_SEC) for ball in xrange(BALLS))
move()
while len(set(tuple(ball.position) for ball in balls)) != BALLS:
balls = tuple(Ball(WIDTH, HEIGHT, OFFSET_START, FRAMES_PER_SEC) for ball in xrange(BALLS))
move()
def build_graph():
# Build GUI environment.
global frame_handle, y, x, start, sec, timer_text, clock_handle
frame_handle = graph.after(1000 / FRAMES_PER_SEC, update)
graph.bind('<1>', change)
graph['background'] = GAME_COLOR
# Draw environment.
y = HEIGHT - WALL + BALL_RADIUS + 2
graph.create_rectangle((0, 0, WALL - BALL_RADIUS, y), fill=FORCE_COLOR)
graph.create_rectangle((WIDTH - WALL + BALL_RADIUS, 0, WIDTH, y), fill=FORCE_COLOR)
graph.create_line((0, y, WIDTH, y), fill=FLOOR_COLOR, width=3)
# Prepare timer data.
x = (WALL - BALL_RADIUS) / 2
y = (y + HEIGHT) / 2
start = time.clock()
sec = 0
timer_text = graph.create_text(x, y, text=f_time(TIME_LIMIT))
clock_handle = graph.after(1000, update_clock)
################################################################################
# ANIMATION LOOP FUNCTIONS
def update():
# Main simulation loop.
global frame_handle
frame_handle = graph.after(1000 / FRAMES_PER_SEC, update)
draw()
move()
def draw():
graph.delete('ball')
# 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], tags=(n, 'ball'))
graph.create_text(ball.position.x, ball.position.y, text=str(n+1), tag=(n, 'ball'))
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()
################################################################################
# VELOCITY MUTATOR FUNCTIONS
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
################################################################################
# GAME INTERACTION FUNCTIONS
def change(event):
# Change color of balls.
try:
ball = balls[int(graph.gettags(graph.find_withtag(CURRENT))[0])]
ball.color = (ball.color + 1) % len(COLORS)
if sum(map(lambda ball: ball.color, balls)) == len(COLORS) * BALLS - BALLS:
do_win_action()
except:
pass
def do_win_action():
# Cause the game to win.
global win, blink_handle
graph.after_cancel(frame_handle)
graph.after_cancel(clock_handle)
graph.delete('ball')
win = graph.create_text(WIDTH / 2, HEIGHT / 2, text=WIN_TEXT, font='Helvetica 50', fill=WIN_COLOR)
blink_handle = graph.after(BLINK_RATE, blink)
graph.after(PAUSE_TIME, enter_high_score if TIME_LIMIT - sec >= min(HS_database.keys()) else do_lose_action)
def blink():
# Blink the WIN_TEXT.
global win, blink_handle
blink_handle = graph.after(BLINK_RATE, blink)
if win:
graph.delete(win)
win = None
else:
win = graph.create_text(WIDTH / 2, HEIGHT / 2, text=WIN_TEXT, font='Helvetica 50', fill=WIN_COLOR)
def enter_high_score():
# Display a dialog box.
name = tkSimpleDialog.askstring('High Score', 'Please enter your name\nfor the high score table.') or 'No Name'
# Update the database.
my_key = TIME_LIMIT - sec
if HS_database.has_key(my_key):
HS_database[my_key].insert(0, name)
else:
HS_database[my_key] = [name]
low_key = min(HS_database.keys())
if len(HS_database[low_key]) > 1:
del HS_database[low_key][-1]
else:
del HS_database[low_key]
# Restart the game.
graph.after_cancel(blink_handle)
graph.delete(ALL)
high_score_table()
################################################################################
# GAME TIMER FUNCTIONS
def update_clock():
# Update clock on screen.
global timer_text, sec, clock_handle
graph.delete(timer_text)
sec += 1
timer_text = graph.create_text(x, y, text=f_time(TIME_LIMIT - sec))
if TIME_LIMIT - sec:
clock_handle = graph.after(int((start + sec + 1 - time.clock()) * 1000), update_clock)
else:
do_lose_action(True)
def f_time(secs):
# Format time correctly.
return '%02d:%02d' % (secs / 60, secs % 60)
def do_lose_action(real=False):
# Cause the game to lose.
answer = tkMessageBox.askquestion('GAME OVER', 'Do you want to try again?')
graph.after_cancel(frame_handle if real else blink_handle)
graph.delete(ALL)
if answer == 'yes':
initialise()
else:
high_score_table()
################################################################################
# VECTOR MATHEMATICS CLASS
class TwoD:
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __iter__(self):
yield self.x
yield self.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(0, 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
################################################################################
# Begin execution of the game.
if __name__ == '__main__':
main()
|
This program demonstrates a simple game implemented using the standard, cross-platform libraries that come with Python. All other operability needed is provided by a few functions and classes. If you want to start writing games, you do not need to immediately download a package such as Pygame to begin. Tkinter is not the perfect GUI API, but it is not a bad starting point either. Enjoy the game, and adapt it for your own purposes if you wish!