1: import simplegui
2: import math
3: import random
4:
5: # globals for user interface
6: WIDTH = 800
7: HEIGHT = 600
8: score = 0
9: lives = 3
10: time = 0.5
11: started = False
12: rock_group = set()
13: missile_group = set()
14: explosion_group = set()
15: collisions = 0
16: n_collisions = 0
17: r_collisions = 0
18:
19: class ImageInfo:
20: def __init__(self, center, size, radius = 0, lifespan = None, animated = False):
21: self.center = center
22: self.size = size
23: self.radius = radius
24: if lifespan:
25: self.lifespan = lifespan
26: else:
27: self.lifespan = float('inf')
28: self.animated = animated
29:
30: def get_center(self):
31: return self.center
32:
33: def get_size(self):
34: return self.size
35:
36: def get_radius(self):
37: return self.radius
38:
39: def get_lifespan(self):
40: return self.lifespan
41:
42: def get_animated(self):
43: return self.animated
44:
45:
46: # art assets created by Kim Lathrop, may be freely re-used in non-commercial projects, please credit Kim
47:
48: # debris images - debris1_brown.png, debris2_brown.png, debris3_brown.png, debris4_brown.png
49: # debris1_blue.png, debris2_blue.png, debris3_blue.png, debris4_blue.png, debris_blend.png
50: debris_info = ImageInfo([320, 240], [640, 480])
51: debris_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_blue.png")
52:
53: # nebula images - nebula_brown.png, nebula_blue.png
54: nebula_info = ImageInfo([400, 300], [800, 600])
55: nebula_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/nebula_blue.png")
56:
57: # splash image
58: splash_info = ImageInfo([200, 150], [400, 300])
59: splash_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/splash.png")
60:
61: # ship image
62: ship_info = ImageInfo([45, 45], [90, 90], 35)
63: ship_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/double_ship.png")
64:
65: # missile image - shot1.png, shot2.png, shot3.png
66: missile_info = ImageInfo([5,5], [10, 10], 3, 50)
67: missile_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/shot2.png")
68:
69: # asteroid images - asteroid_blue.png, asteroid_brown.png, asteroid_blend.png
70: asteroid_info = ImageInfo([45, 45], [90, 90], 40)
71: asteroid_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blue.png")
72:
73: # animated explosion - explosion_orange.png, explosion_blue.png, explosion_blue2.png, explosion_alpha.png
74: explosion_info = ImageInfo([64, 64], [128, 128], 17, 24, True)
75: explosion_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/explosion_alpha.png")
76:
77: # sound assets purchased from sounddogs.com, please do not redistribute
78: # .ogg versions of sounds are also available, just replace .mp3 by .ogg
79: soundtrack = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/soundtrack.ogg")
80: missile_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/missile.ogg")
81: missile_sound.set_volume(.5)
82: ship_thrust_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/thrust.ogg")
83: explosion_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/explosion.ogg")
84:
85: # helper functions to handle transformations
86: def angle_to_vector(ang):
87: return [math.cos(ang), math.sin(ang)]
88:
89: def dist(p, q):
90: return math.sqrt((p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2)
91:
92:
93: # Ship class
94: class Ship:
95:
96: def __init__(self, pos, vel, angle, image, info):
97: self.pos = [pos[0], pos[1]]
98: self.vel = [vel[0], vel[1]]
99: self.thrust = False
100: self.angle = angle
101: self.angle_vel = 0
102: self.image = image
103: self.image_center = info.get_center()
104: self.image_size = info.get_size()
105: self.radius = info.get_radius()
106:
107: def draw(self,canvas):
108: if self.thrust:
109: canvas.draw_image(self.image, [self.image_center[0] + self.image_size[0], self.image_center[1]] , self.image_size,
110: self.pos, self.image_size, self.angle)
111: else:
112: canvas.draw_image(self.image, self.image_center, self.image_size,
113: self.pos, self.image_size, self.angle)
114: # canvas.draw_circle(self.pos, self.radius, 1, "White", "White")
115:
116: def update(self):
117: # update angle
118: self.angle += self.angle_vel
119:
120: # update position
121: self.pos[0] = (self.pos[0] + self.vel[0]) % WIDTH
122: self.pos[1] = (self.pos[1] + self.vel[1]) % HEIGHT
123:
124: # update velocity
125: if self.thrust:
126: acc = angle_to_vector(self.angle)
127: self.vel[0] += acc[0] * .5
128: self.vel[1] += acc[1] * .5
129:
130: self.vel[0] *= .95
131: self.vel[1] *= .95
132:
133: def set_thrust(self, on):
134: self.thrust = on
135: if on:
136: ship_thrust_sound.rewind()
137: ship_thrust_sound.play()
138: else:
139: ship_thrust_sound.pause()
140:
141: def increment_angle_vel(self):
142: self.angle_vel += .05
143:
144: def decrement_angle_vel(self):
145: self.angle_vel -= .05
146:
147: def shoot(self):
148: global a_missile
149: forward = angle_to_vector(self.angle)
150: missile_pos = [self.pos[0] + self.radius * forward[0], self.pos[1] + self.radius * forward[1]]
151: missile_vel = [self.vel[0] + 6 * forward[0], self.vel[1] + 6 * forward[1]]
152: a_missile = Sprite(missile_pos, missile_vel, self.angle, 0, missile_image, missile_info, missile_sound)
153: missile_group.add(a_missile)
154:
155:
156:
157: # Sprite class
158: class Sprite:
159: def __init__(self, pos, vel, ang, ang_vel, image, info, sound = None):
160: self.pos = [pos[0],pos[1]]
161: self.vel = [vel[0],vel[1]]
162: self.angle = ang
163: self.angle_vel = ang_vel
164: self.image = image
165: self.image_center = info.get_center()
166: self.image_size = info.get_size()
167: self.radius = info.get_radius()
168: self.lifespan = info.get_lifespan()
169: self.animated = info.get_animated()
170: self.age = 0
171: if sound:
172: sound.rewind()
173: sound.play()
174:
175: def draw(self, canvas):
176: if self.animated == True:
177: center = [self.image_center[0] + 2*self.image_center[0]*self.age, self.image_center[1]]
178: canvas.draw_image(self.image, center, self.image_size,
179: self.pos, self.image_size, self.angle)
180: else:
181: canvas.draw_image(self.image, self.image_center, self.image_size,
182: self.pos, self.image_size, self.angle)
183:
184: def update(self):
185: # update angle
186: self.angle += self.angle_vel
187:
188: # update position
189: self.pos[0] = (self.pos[0] + self.vel[0]) % WIDTH
190: self.pos[1] = (self.pos[1] + self.vel[1]) % HEIGHT
191: self.age += 1
192: if self.age >= self.lifespan:
193: return True
194: else:
195: return False
196:
197: def collide(self, other_object):
198: if dist(self.pos, other_object.pos) > self.radius + other_object.radius:
199: return False
200: else:
201: return True
202:
203:
204:
205: # key handlers to control ship
206: def keydown(key):
207: if key == simplegui.KEY_MAP['left']:
208: my_ship.decrement_angle_vel()
209: elif key == simplegui.KEY_MAP['right']:
210: my_ship.increment_angle_vel()
211: elif key == simplegui.KEY_MAP['up']:
212: my_ship.set_thrust(True)
213: elif key == simplegui.KEY_MAP['space']:
214: my_ship.shoot()
215:
216: def keyup(key):
217: if key == simplegui.KEY_MAP['left']:
218: my_ship.increment_angle_vel()
219: elif key == simplegui.KEY_MAP['right']:
220: my_ship.decrement_angle_vel()
221: elif key == simplegui.KEY_MAP['up']:
222: my_ship.set_thrust(False)
223:
224: # mouseclick handlers that reset UI and conditions whether splash image is drawn
225: def click(pos):
226: global started, lives, score
227: center = [WIDTH / 2, HEIGHT / 2]
228: size = splash_info.get_size()
229: inwidth = (center[0] - size[0] / 2) < pos[0] < (center[0] + size[0] / 2)
230: inheight = (center[1] - size[1] / 2) < pos[1] < (center[1] + size[1] / 2)
231: if (not started) and inwidth and inheight:
232: started = True
233: lives = 3
234: score = 0
235: soundtrack.play()
236:
237: def draw(canvas):
238: global time, started, lives, score, rock_group
239:
240: # animiate background
241: time += 1
242: center = debris_info.get_center()
243: size = debris_info.get_size()
244: wtime = (time / 8) % center[0]
245: canvas.draw_image(nebula_image, nebula_info.get_center(), nebula_info.get_size(), [WIDTH / 2, HEIGHT / 2], [WIDTH, HEIGHT])
246: canvas.draw_image(debris_image, [center[0] - wtime, center[1]], [size[0] - 2 * wtime, size[1]],
247: [WIDTH / 2 + 1.25 * wtime, HEIGHT / 2], [WIDTH - 2.5 * wtime, HEIGHT])
248: canvas.draw_image(debris_image, [size[0] - wtime, center[1]], [2 * wtime, size[1]],
249: [1.25 * wtime, HEIGHT / 2], [2.5 * wtime, HEIGHT])
250:
251: if group_collide(rock_group, my_ship) > 0:
252: lives -= 1
253:
254: if group_group_collide(rock_group, missile_group) > 0:
255: score += 1
256:
257: if lives == 0:
258: started = False
259: rock_group = set()
260: soundtrack.rewind()
261: lives = 3
262:
263: # draw UI
264: canvas.draw_text("Lives", [50, 50], 22, "White")
265: canvas.draw_text("Score", [680, 50], 22, "White")
266: canvas.draw_text(str(lives), [50, 80], 22, "White")
267: canvas.draw_text(str(score), [680, 80], 22, "White")
268:
269: # draw ship and sprites
270: my_ship.draw(canvas)
271:
272: process_sprite_group(rock_group, canvas)
273: process_sprite_group(missile_group, canvas)
274: process_sprite_group(explosion_group, canvas)
275:
276:
277: for a_missile in set(missile_group):
278: if a_missile.age >= a_missile.lifespan:
279: missile_group.remove(a_missile)
280: else:
281: pass
282: a_missile.draw(canvas)
283: a_missile.update()
284:
285: # update ship and sprites
286: my_ship.update()
287:
288: # draw splash screen if not started
289: if not started:
290: canvas.draw_image(splash_image, splash_info.get_center(),
291: splash_info.get_size(), [WIDTH / 2, HEIGHT / 2],
292: splash_info.get_size())
293:
294: # timer handler that spawns a rock
295: def rock_spawner():
296: global a_rock, rock_group, score
297: if started:
298: if len(rock_group) < 12:
299: rock_pos = [random.randrange(0, WIDTH), random.randrange(0, HEIGHT)]
300: rock_vel = [random.random() * .6 - .3, random.random() * .6 - .3]
301: rock_avel = random.random() * .2 - .1
302: if score > 10:
303: rock_vel[0] *= 4
304: rock_vel[1] *= 4
305: elif score > 20:
306: rock_vel[0] *= 6
307: rock_vel[1] *= 6
308: elif score > 30:
309: rock_vel[0] *= 10
310: rock_vel[1] *= 10
311: elif score > 40:
312: rock_vel[0] *= 20
313: rock_vel[1] *= 20
314:
315: a_rock = Sprite(rock_pos, rock_vel, 0, rock_avel, asteroid_image, asteroid_info)
316: if dist(my_ship.pos, rock_pos) > 3*my_ship.radius:
317: rock_group.add(a_rock)
318: else:
319: pass
320: else:
321: pass
322:
323: def process_sprite_group(group, canvas):
324: global collisions, lives
325:
326: for sprite in set(group):
327: sprite.draw(canvas)
328: if sprite.update() == True:
329: group.remove(sprite)
330: else:
331: pass
332:
333: sprite.update()
334:
335: def group_collide(group, sprite):
336: global collisions, explosion_group
337: for item in set(group):
338: if item.collide(sprite) == True:
339: group.remove(item)
340: exp = Sprite(item.pos, [0, 0], 0, 0, explosion_image, explosion_info, explosion_sound)
341: explosion_group.add(exp)
342: collisions += 1
343: return collisions
344: else:
345: pass
346:
347:
348: def group_group_collide(group1, group2):
349: global n_collisions, r_collisions
350: for item1 in set(group1):
351: if group_collide(group2, item1) > 0:
352: r_collisions += 1
353: group1.remove(item1)
354: return r_collisions
355: else:
356: pass
357:
358:
359:
360: # initialize stuff
361: frame = simplegui.create_frame("Asteroids", WIDTH, HEIGHT)
362:
363: # initialize ship and two sprites
364: my_ship = Ship([WIDTH / 2, HEIGHT / 2], [0, 0], 0, ship_image, ship_info)
365:
366: #a_rock = Sprite([WIDTH / 3, HEIGHT / 3], [1, 1], 0, .1, asteroid_image, asteroid_info)
367: #a_missile = Sprite([2 * WIDTH / 3, 2 * HEIGHT / 3], [-1,1], 0, 0, missile_image, missile_info, missile_sound)
368:
369:
370: # register handlers
371: frame.set_keyup_handler(keyup)
372: frame.set_keydown_handler(keydown)
373: frame.set_mouseclick_handler(click)
374: frame.set_draw_handler(draw)
375:
376: timer = simplegui.create_timer(1000.0, rock_spawner)
377:
378: # get things rolling
379: timer.start()
380: frame.start()
381:
382: