1 # -*- coding: utf-8 -*-
4 from os
.path
import join
5 from math
import sin
, cos
, radians
, sqrt
6 from random
import randint
, randrange
, choice
, random
9 from pygame
import Surface
, Color
, PixelArray
10 from pygame
.image
import load
, save
11 from pygame
.draw
import circle
, arc
, aaline
, line
, ellipse
12 from pygame
.font
import Font
13 from pygame
.mixer
import Sound
14 from pygame
.event
import clear
15 from pygame
.locals import *
17 from lake_of_heavenly_wind
.pgfw
.Game
import Game
18 from lake_of_heavenly_wind
.pgfw
.GameChild
import GameChild
19 from lake_of_heavenly_wind
.pgfw
.Sprite
import Sprite
20 from lake_of_heavenly_wind
.pgfw
.Animation
import Animation
22 class LakeOfHeavenlyWind(Game
):
28 def set_children(self
):
29 Game
.set_children(self
)
30 self
.high_scores
= HighScores(self
)
31 self
.title
= Title(self
)
32 self
.game_screen
= GameScreen(self
)
33 self
.book
= Book(self
)
37 self
.game_screen
.update()
41 class Book(GameChild
, list):
43 def __init__(self
, parent
):
44 GameChild
.__init__(self
, parent
)
45 path
= self
.get_resource("text", "book")
46 for block
in codecs
.open(path
, "r", "utf-8").read().split("\n\n"):
47 self
.append(Hexagram(self
, block
))
49 def hide_indicators(self
):
51 hexagram
.indicator
.hide()
53 def hide_explanations(self
):
55 hexagram
.explanation
.hide()
57 def are_indicators_hidden(self
):
58 return all(hexagram
.indicator
.is_hidden() for hexagram
in self
)
65 class Hexagram(GameChild
):
67 TRIGRAM_NAMES
= {0b111
: "heaven", 0b110
: "marsh", 0b101
: "fire",
68 0b100
: "thunder", 0b11
: "wind", 0b10
: "water",
69 1: "mountain", 0: "earth"}
71 def __init__(self
, parent
, block
):
72 GameChild
.__init__(self
, parent
)
73 lines
= self
.lines
= block
.split("\n")
74 self
.index
= int(lines
[0], 2)
77 pair
+= self
.TRIGRAM_NAMES
[(self
.index
& \
78 (0b111
<< ii
* 3)) >> (ii
* 3)]. \
80 pair
+= " ABOVE ~ " if not ii
else " BELOW"
82 font_path
= self
.get_resource("display", "font")
83 font
= Font(font_path
, 10)
84 explanation
= self
.explanation
= Sprite(self
, 4000)
85 for line
in map(unicode, lines
[1:]):
87 frame
= Surface((640, 16), SRCALPHA
)
91 caption
= font
.render(spaced
, True, (102, 102, 102))
92 rect
= caption
.get_rect()
93 rect
.center
= frame
.get_rect().center
94 frame
.blit(caption
, rect
)
95 explanation
.add_frame(frame
)
96 explanation
.location
.center
= self
.parent
.parent
.game_screen
.link
. \
97 background
.location
.center
99 indicator
= self
.indicator
= Sprite(self
, 500)
100 font
= Font(font_path
, 36)
101 font
.set_italic(True)
102 indicator
.add_frame(font
.render(lines
[1][0], True, (31, 31, 31)))
103 blank
= Surface(indicator
.location
.size
)
104 blank
.set_colorkey((0, 0, 0))
105 indicator
.add_frame(blank
)
106 rect
= self
.parent
.parent
.game_screen
.oracle
.screens
[0].get_rect()
107 indicator
.location
.center
= 32 + rect
.centerx
, 16 + rect
.centery
108 indicator
.add_location(offset
=(self
.get_display_surface(). \
109 get_width() - rect
.centerx
* 2 - 64, 0))
112 def show_indicator(self
):
113 self
.indicator
.unhide()
115 def show_explanation(self
):
116 self
.explanation
.get_current_frameset().reset()
117 self
.explanation
.unhide()
120 self
.explanation
.update()
121 self
.indicator
.update()
124 class HighScores(GameChild
):
126 def __init__(self
, parent
):
127 GameChild
.__init__(self
, parent
)
131 for line
in file(self
.get_resource("text", "scores")):
132 scores
.append(int(line
))
135 def add(self
, score
):
136 file(self
.get_resource("text", "scores"), "a").write(str(score
) + "\n")
139 class Title(GameChild
):
141 def __init__(self
, parent
):
142 GameChild
.__init__(self
, parent
)
143 self
.time_filter
= self
.get_game().time_filter
144 self
.display_surface
= self
.get_display_surface()
145 self
.delegate
= self
.get_game().delegate
146 self
.music
= Sound(self
.get_resource("audio", "outer"))
147 self
.start_fx
= SoundEffect(self
, "start", .4)
149 background
= self
.background
= Sprite(self
)
150 tile_size
= self
.tile_size
= 8
152 tile
= Surface((tile_size
, tile_size
))
153 frame
= Surface(self
.display_surface
.get_size())
154 for x
in xrange(tile
.get_width()):
155 for y
in xrange(tile
.get_height()):
156 tile
.set_at((x
, y
), choice([(128, 128, 128), (95, 95, 95)]))
157 for x
in xrange(0, frame
.get_width(), tile
.get_width()):
158 for y
in xrange(0, frame
.get_height(), tile
.get_height()):
159 frame
.blit(tile
, (x
, y
))
160 background
.add_frame(frame
)
161 layers
= self
.layers
= []
166 tile
= Surface((tile_size
, tile_size
))
168 for x
in xrange(tile
.get_width()):
169 for y
in xrange(tile
.get_height()):
171 tile
.set_at((x
, y
), [(255, 255, 80), (80, 255, 255),
175 frame
= Surface((background
.location
.w
+ tile_size
* (16 + ii
),
176 background
.location
.h
+ tile_size
* (16 + ii
)))
177 frame
.set_colorkey(key
)
178 for x
in xrange(0, frame
.get_width(), tile
.get_width()):
179 for y
in xrange(0, frame
.get_height(), tile
.get_height()):
180 frame
.blit(choice(tiles
), (x
, y
))
181 layer
.add_frame(frame
)
183 self
.subscribe(self
.respond
)
185 def set_caption(self
):
186 caption
= self
.caption
= Sprite(self
, 5000)
187 font
= Font(self
.get_resource("display", "font"), 12)
188 color
= Color(0, 0, 0)
190 texts
= ["Wind Off of Heaven's Lake", u
"澤 天 風"]
192 texts
= ["Pleasure in Strong Penetration", u
"兌 乾 巽"]
197 frame
= Surface((640, 100), SRCALPHA
)
198 plate
= font
.render(spaced
.upper(), True, (255, 255, 255))
199 rect
= plate
.get_rect()
200 rect
.center
= frame
.get_rect().center
201 frame
.blit(plate
, rect
)
202 caption
.add_frame(frame
)
203 caption
.location
.center
= self
.display_surface
.get_rect().centerx
, 400
204 caption
.get_current_frameset().current_index
= 1
206 def deactivate(self
):
208 self
.music
.fadeout(500)
210 def respond(self
, event
):
212 if self
.delegate
.compare(event
, "any"):
214 self
.parent
.game_screen
.activate()
217 def activate(self
, incoming
=None):
219 self
.music
.play(-1, 0, 500)
220 scores
= self
.scores
= []
222 for ii
, score
in enumerate(sorted(self
.parent
. \
223 high_scores
.read())[-count
:]):
224 font
= Font(self
.get_resource("display", "font"), 11 + (ii
* 1))
225 sprite
= Sprite(self
, 380)
226 sprite
.add_frame(font
.render(str(score
), True, (32, 200, 32),
228 if score
== incoming
:
229 surface
= Surface(sprite
.location
.size
)
232 surface
.set_colorkey(key
)
233 sprite
.add_frame(surface
)
234 sprite
.location
.centerx
= self
.display_surface
.get_rect(). \
235 centerx
+ 100 * ((count
- ii
- 1) - \
237 scores
.append(sprite
)
243 self
.background
.update()
244 ds
= self
.display_surface
245 for ii
, layer
in enumerate(reversed(self
.layers
)):
247 layer
.move(-.25, -.25)
248 if layer
.location
.right
< ds
.get_width():
249 layer
.move(self
.tile_size
* 16, self
.tile_size
* 16)
252 if layer
.location
.right
< ds
.get_width():
253 layer
.move(self
.tile_size
* 16)
256 if layer
.location
.left
> 0:
257 layer
.move(-self
.tile_size
* 16, -self
.tile_size
* 16)
259 for score
in self
.scores
:
261 self
.caption
.update()
264 class SoundEffect(GameChild
, Sound
):
266 def __init__(self
, parent
, name
, volume
=1.0):
267 GameChild
.__init__(self
, parent
)
268 Sound
.__init__(self
, self
.get_resource("audio", name
))
270 self
.set_volume(volume
)
272 def play(self
, position
=.5):
273 channel
= Sound
.play(self
)
274 right
= 1 + min(0, ((position
- .5) * 2))
275 left
= 1 - max(0, ((position
- .5) * 2))
276 if channel
is not None:
277 channel
.set_volume(left
, right
)
280 class GameScreen(GameChild
):
282 PALETTE
= [[(191, 191, 220), (191, 255, 191)],
283 [(191, 191, 220), (255, 191, 191)],
284 [(191, 191, 225), (191, 255, 191)],
285 [(191, 191, 255), (255, 191, 191)],
286 [(191, 220, 191), (191, 191, 255)],
287 [(191, 220, 191), (255, 191, 191)],
288 [(191, 255, 191), (255, 191, 191)],
289 [(220, 191, 191), (191, 191, 255)],
290 [(220, 191, 191), (191, 255, 191)]]
292 def __init__(self
, parent
):
293 GameChild
.__init__(self
, parent
)
294 self
.delegate
= self
.get_game().delegate
295 self
.time_filter
= self
.get_game().time_filter
296 self
.display_surface
= self
.get_display_surface()
297 self
.music
= Sound(self
.get_resource("audio", "inner"))
298 self
.music
.set_volume(.8)
299 self
.previous_palette
= None
302 self
.set_background()
303 self
.pulp
= Pulp(self
)
304 self
.link
= Link(self
)
305 self
.paddles
= Paddles(self
)
306 self
.rails
= Rails(self
)
307 self
.oracle
= Oracle(self
)
308 self
.subscribe(self
.respond
)
310 def set_background(self
):
312 tile
= Surface((w
, h
))
314 palette
= choice(self
.PALETTE
)
315 if self
.previous_palette
!= palette
:
316 self
.previous_palette
= palette
320 tile
.set_at((x
, y
), palette
[(x
+ y
) %
2])
321 background
= self
.background
= Surface(self
.display_surface
.get_size())
322 for x
in xrange(0, background
.get_width(), w
):
323 for y
in xrange(0, background
.get_height(), h
):
324 background
.blit(tile
, (x
, y
))
329 self
.game_over_elapsed
= 0
336 self
.paddles
.set_background()
337 self
.paddles
.reset_position()
338 self
.paddles
.arrange_graticules(0)
339 self
.set_background()
343 self
.introducing
= True
344 self
.introducing_elapsed
= 0
346 def increase_wave(self
):
348 self
.paddles
.explode_mines()
349 self
.paddles
.set_background()
350 self
.paddles
.reset_position()
351 self
.paddles
.arrange_graticules(0)
352 self
.rails
.increase_spawn_rate()
354 self
.rails
.clear_phages()
355 self
.rails
.set_deviation()
356 self
.set_background()
357 self
.pulp
.health
+= 1.0 / 6
358 if self
.pulp
.health
> .9999:
359 self
.pulp
.health
= .9999
360 self
.parent
.book
.hide_indicators()
371 def deactivate(self
):
378 self
.parent
.high_scores
.add(int(self
.pulp
.score
))
379 self
.paddles
.explode_mines()
380 self
.paddles
.reset_position()
381 self
.oracle
.animals
.hide()
382 for hexagram
in self
.parent
.book
:
383 if not hexagram
.explanation
.is_hidden():
384 hexagram
.explanation
.halt()
386 def respond(self
, event
):
387 if self
.active
and self
.ending
and self
.game_over_elapsed
> 3000 and \
388 self
.delegate
.compare(event
, "any"):
389 self
.parent
.book
.hide_explanations()
390 self
.parent
.book
.hide_indicators()
392 self
.parent
.title
.activate(int(self
.pulp
.score
))
396 self
.display_surface
.blit(self
.background
, (0, 0))
398 font
= Font(self
.get_resource("display", "font"), 10)
399 text
= "WAVE " + str(self
.wave
)
403 text
= font
.render(spaced
, True, (31, 31, 31))
404 rect
= text
.get_rect()
405 rect
.center
= self
.display_surface
.get_rect().centerx
, 260
406 self
.display_surface
.blit(text
, rect
)
407 if self
.introducing_elapsed
> 2000:
408 self
.introducing
= False
411 self
.introducing_elapsed
+= self
.time_filter
. \
412 get_last_frame_duration()
414 font
= Font(self
.get_resource("display", "font"), 10)
415 text
= font
.render("G A M E O V E R", True,
417 rect
= text
.get_rect()
418 rect
.center
= self
.display_surface
.get_rect().centerx
, 260
419 self
.display_surface
.blit(text
, rect
)
420 self
.game_over_elapsed
+= self
.time_filter
. \
421 get_last_frame_duration()
422 self
.oracle
.animals
.update()
424 if not self
.is_frozen():
426 self
.paddles
.update()
431 class Pulp(GameChild
, Surface
):
433 def __init__(self
, parent
):
434 GameChild
.__init__(self
, parent
)
435 self
.display_surface
= self
.get_display_surface()
436 self
.font
= Font(self
.get_resource("display", "font"), 24)
437 self
.explosions
= [Explosion(self
) for _
in xrange(32)]
438 self
.lose_health_fx
= SoundEffect(self
, "lose-health")
439 self
.blow_up_fx
= SoundEffect(self
, "blow-up", .8)
441 Surface
.__init__(self
, (self
.display_surface
.get_width(), 96))
442 self
.rect
= self
.get_rect()
443 indicators
= self
.indicators
= []
444 root
= self
.get_resource("image", "pulp")
445 for path
in sorted(listdir(root
)):
446 indicator
= Sprite(self
, 5500)
447 image
= load(join(root
, path
))
448 image_r
= self
.image_r
= image
.get_rect()
449 width
= self
.get_width()
450 height
= (self
.rect
.h
/ image_r
.h
+ 2) * image_r
.h
451 color
= Color(0, 0, 0)
452 for hue
in [-150, -128, 149]:
453 pixels
= PixelArray(image
.copy())
454 for x
in xrange(len(pixels
)):
455 for y
in xrange(len(pixels
[0])):
456 color
= Color(*image
.unmap_rgb(pixels
[x
][y
]))
457 h
, s
, l
, a
= color
.hsla
458 color
.hsla
= round((h
+ hue
) %
360), round(s
), \
461 plate
= pixels
.make_surface()
462 frame
= Surface((width
, height
), SRCALPHA
)
463 for x
in xrange(0, width
, image_r
.w
):
464 for y
in xrange(0, height
, image_r
.h
):
465 frame
.blit(plate
, (x
, y
))
466 indicator
.add_frame(frame
)
467 indicator
.display_surface
= self
468 indicators
.append(indicator
)
469 self
.score_backgrounds
= []
470 tile
= Surface((2, 2))
471 for x
in xrange(tile
.get_width()):
472 for y
in xrange(tile
.get_height()):
473 tile
.set_at((x
, y
), [(220, 220, 220),
474 (128, 128, 128)][(x
+ y
) %
2])
476 surface
= Surface((22, 26))
477 for x
in xrange(0, surface
.get_width(), tile
.get_width()):
478 for y
in xrange(0, surface
.get_height(), tile
.get_height()):
479 surface
.blit(tile
, (x
, y
))
480 self
.score_backgrounds
.append(surface
)
485 for explosion
in self
.explosions
:
489 for indicator
in self
.indicators
:
490 indicator
.move(dy
=-1)
491 if indicator
.location
.top
< -self
.image_r
.h
:
492 indicator
.move(dy
=self
.image_r
.h
)
493 self
.indicators
[int(self
.health
* len(self
.indicators
))].update()
494 self
.display_surface
.blit(self
, self
.rect
)
495 # if not self.parent.is_frozen():
496 # self.score += self.health * .1
497 text
= str(int(self
.score
))
498 color
= randint(0, 120), randint(0, 120), randint(0, 120)
499 for ii
, digit
in enumerate(text
):
500 background
= self
.score_backgrounds
[ii
]
501 br
= background
.get_rect()
502 br
.centerx
= 40 * (ii
- len(text
) / 2.0 + .5) + \
503 self
.display_surface
.get_rect().centerx
504 self
.display_surface
.blit(self
.score_backgrounds
[ii
], br
)
505 glyph
= self
.font
.render(digit
, True, color
)
506 gr
= glyph
.get_rect()
507 gr
.centerx
= br
.centerx
508 self
.display_surface
.blit(glyph
, gr
)
510 for phage
in self
.parent
.rails
.phages
:
511 if phage
.get_center()[1] <= self
.rect
.centery
:
512 outgoing
.append(phage
)
514 for explosion
in self
.explosions
:
515 if explosion
.is_hidden():
516 explosion
.get_current_frameset().reset()
517 explosion
.location
.center
= phage
.get_center()
525 for phage
in outgoing
:
526 total
+= phage
.get_center()[0]
527 self
.parent
.rails
.phages
.remove(phage
)
528 position
= float(total
) / len(outgoing
) / \
529 self
.get_display_surface().get_width()
530 self
.lose_health_fx
.play(position
)
531 self
.blow_up_fx
.play(position
)
532 for explosion
in self
.explosions
:
536 class Explosion(Sprite
):
538 def __init__(self
, parent
):
539 Sprite
.__init__(self
, parent
)
541 color
= Color(0, 0, 0)
542 for ii
in xrange(count
):
543 frame
= Surface((64, 64), SRCALPHA
)
544 ratio
= float(ii
) / count
545 color
.hsla
= 60 - ratio
* 60, 100, 50, 100 - ratio
* 100
546 fr
= frame
.get_rect()
547 circle(frame
, color
, fr
.center
, 6 + int(ratio
* (fr
.w
/ 2 - 6)), 5)
548 self
.add_frame(frame
)
554 def shift_frame(self
):
555 Sprite
.shift_frame(self
)
556 frameset
= self
.get_current_frameset()
557 if frameset
.current_index
== frameset
.length() - 1:
561 class Link(GameChild
):
563 def __init__(self
, parent
):
564 GameChild
.__init__(self
, parent
)
565 self
.display_surface
= self
.get_display_surface()
566 self
.background
= Sprite(self
, 500)
567 tile
= Surface((2, 2))
568 for x
in xrange(tile
.get_width()):
569 for y
in xrange(tile
.get_height()):
570 tile
.set_at((x
, y
), [(255, 255, 0), (228, 228, 228)][(x
+ y
) %
2])
572 frame
= Surface((self
.display_surface
.get_width(), 24))
573 for x
in xrange(0, frame
.get_width(), tile
.get_width()):
574 for y
in xrange(0, frame
.get_height(), tile
.get_height()):
575 frame
.blit(tile
, (x
, y
))
576 for ii
, x
in enumerate(xrange(-4, frame
.get_width(), 16)):
577 colors
= [(63, 255, 63), (240, 240, 63), (191, 31, 220)]
578 primary_color
= colors
[(ii
- jj
) %
3]
579 secondary_color
= colors
[(ii
- jj
+ 1) %
3]
580 frame
.fill(primary_color
, (x
, 0, 8, 3))
581 frame
.fill(secondary_color
, (x
+ 2, 1, 4, 2))
582 frame
.fill(primary_color
, (x
, frame
.get_height() - 3, 8, 3))
583 frame
.fill(secondary_color
, (x
+ 2, frame
.get_height() - 3, 4,
585 self
.background
.add_frame(frame
)
586 self
.background
.location
.top
= self
.parent
.pulp
.get_rect().bottom
589 self
.background
.update()
592 class Paddles(GameChild
):
594 LEFT
, RIGHT
= range(2)
595 GRATICULES
= (((0, 0),),
596 ((.5, 270), (.5, 90)),
597 ((.5, 0), (.5, 180)),
598 ((.5, 45), (.5, 225)),
599 ((.5, 135), (.5, 315)),
600 ((1.0, 0), (0, 0), (1.0, 180)),
601 ((1.0, 45), (0, 0), (1.0, 225)),
602 ((1.0, 135), (0, 0), (1.0, 315)),
603 ((.5774, 0), (.5774, 120), (.5774, 240)))
605 def __init__(self
, parent
):
606 GameChild
.__init__(self
, parent
)
607 self
.time_filter
= self
.get_game().time_filter
608 self
.display_surface
= self
.get_display_surface()
609 self
.delegate
= self
.get_game().delegate
610 self
.detonate_mine_fx
= SoundEffect(self
, "detonate", .8)
611 self
.drop_mine_fx
= SoundEffect(self
, "drop", .3)
612 self
.eliminate_phage_fx
= SoundEffect(self
, "eliminate", 1)
614 self
.set_background()
617 self
.graticule
= Graticule(self
)
618 self
.arrange_graticules(0)
619 self
.mines
= [Mine(self
) for _
in xrange(16)]
620 self
.active_mines
= [[], []]
621 self
.subscribe(self
.respond
)
623 def set_background(self
):
624 background
= self
.background
= Sprite(self
, 120)
625 mask
= Surface((8, 8), SRCALPHA
)
626 for x
in xrange(mask
.get_width()):
627 for y
in xrange(mask
.get_height()):
628 mask
.set_at((x
, y
), [(255, 255, 255, 255),
629 (255, 255, 255, 0)][(x
+ y
) %
2])
630 color
= Color(0, 0, 0)
634 hue_base
= randrange(0, 360)
635 for ii
in xrange(count
):
636 frame
= Surface((self
.display_surface
.get_width(), h
), SRCALPHA
)
638 hue
= 30 * (float(hue_ii
) / (count
/ 2)) + hue_base
641 color
.hsla
= hue
, 100, 50, 100 * (float(y
) / h
)
642 frame
.fill(color
, (0, y
, frame
.get_width(), 1))
643 hue_ii
+= 1 if ii
< count
/ 2 else -1
644 for x
in xrange(0, frame
.get_width(), mask
.get_width()):
645 for y
in xrange(0, h
, mask
.get_height()):
646 frame
.blit(mask
, (x
, y
), None, BLEND_RGBA_MIN
)
647 background
.add_frame(frame
)
648 background
.location
.bottom
= self
.get_display_surface().get_rect(). \
651 def set_lattice(self
):
652 mask
= load(self
.get_resource("image", "plateau")).convert_alpha()
653 tile
= Surface((9, 9))
654 tw
, th
= tile
.get_size()
657 tile
.set_at((x
, y
), [(0, 255, 255), (255, 0, 255),
658 (255, 255, 0)][(x
+ y
) %
3])
659 w
, h
= self
.background
.location
.size
660 w
+= mask
.get_width()
661 base
= Surface((w
, h
), SRCALPHA
)
662 for x
in xrange(0, w
, tw
):
663 for y
in xrange(0, h
, th
):
664 base
.blit(tile
, (x
, y
))
665 for x
in xrange(0, w
, mask
.get_width()):
666 base
.blit(mask
, (x
, 0), None, BLEND_RGBA_MIN
)
667 lattice
= self
.lattice
= Sprite(self
)
668 lattice
.add_frame(base
)
669 lattice
.location
.bottom
= self
.get_display_surface().get_rect().bottom
+ 2
671 def set_paddles(self
):
672 image
= load(self
.get_resource("image", "paddle")).convert_alpha()
673 self
.paddle_length
= image
.get_width()
674 margin
= self
.margin
= (self
.get_display_surface().get_width() / 2) - \
676 surface
= Surface((image
.get_width() * 2 + margin
, image
.get_height()))
679 surface
.set_colorkey(key
)
680 surface
.blit(image
, (0, 0))
681 surface
.blit(image
, (image
.get_width() + margin
, 0))
682 paddles
= self
.paddles
= Sprite(self
, 60)
684 color
= Color(0, 0, 0)
685 for ii
in xrange(count
):
686 color
.hsla
= randrange(0, 360), 100, \
687 60 + 40 * (float(ii
) / (count
- 1)), 100
688 pixels
= PixelArray(surface
.copy())
689 pixels
.replace((255, 255, 255), color
)
690 frame
= pixels
.make_surface()
691 frame
.set_colorkey(key
)
692 paddles
.add_frame(frame
)
693 ds
= self
.get_display_surface()
694 paddles
.add_location()
695 self
.reset_position()
696 self
.reset_throttle()
698 def reset_position(self
):
699 self
.moving
= [False, False]
700 ds
= self
.get_display_surface()
701 for ii
, location
in enumerate(self
.paddles
.locations
):
702 location
.midbottom
= ds
.get_rect().centerx
, \
705 location
.left
+= ds
.get_width()
707 def reset_throttle(self
):
708 self
.throttle
= [0, 0]
710 def arrange_graticules(self
, index
=None):
712 index
= randrange(1, len(self
.GRATICULES
))
715 states
= self
.GRATICULES
[index
], self
.GRATICULES
[index
+ 1]
717 states
= self
.GRATICULES
[index
], self
.GRATICULES
[index
- 1]
719 states
= self
.GRATICULES
[index
], self
.GRATICULES
[index
]
722 states
= self
.GRATICULES
[index
], self
.GRATICULES
[0]
724 states
= self
.GRATICULES
[0], self
.GRATICULES
[index
]
725 graticule
= self
.graticule
726 graticule
.remove_locations()
728 y
= self
.graticule
.location
.centery
729 for ii
, state
in enumerate(states
):
731 x
= self
.paddles
.location
.left
+ self
.paddle_length
/ 2
733 x
= self
.paddles
.location
.right
- self
.paddle_length
/ 2
736 dx
= margin
* offset
[0] * sin(radians(offset
[1]))
737 dy
= margin
* offset
[0] * - cos(radians(offset
[1]))
738 center
= int(float(x
+ dx
)), int(float(y
+ dy
))
740 graticule
.location
.center
= center
741 graticule
.location
.side
= self
.LEFT
744 location
= graticule
.add_location((0, 0))
746 location
.center
= center
748 def explode_mines(self
):
751 for side
in self
.active_mines
:
754 total_x
+= mine
.location
.centerx
755 mine
.set_frameset("explode")
756 for location
in self
.graticule
.locations
:
759 self
.detonate_mine_fx
.play(float(total_x
) / count
/ \
760 self
.get_display_surface().get_width())
762 def respond(self
, event
):
763 if self
.parent
.active
and not self
.parent
.ending
:
764 compare
= self
.delegate
.compare
765 if compare(event
, "left") or compare(event
, "left", True):
766 self
.moving
[self
.LEFT
] = not event
.cancel
767 elif compare(event
, "right") or compare(event
, "right", True):
768 self
.moving
[self
.RIGHT
] = not event
.cancel
769 elif not self
.parent
.is_frozen() and \
770 (compare(event
, "release-left") or \
771 compare(event
, "release-right")):
772 side
= self
.LEFT
if event
.command
== "release-left" else self
.RIGHT
773 if self
.active_mines
[side
]:
775 count
= len(self
.active_mines
[side
])
778 while self
.active_mines
[side
]:
779 mine
= self
.active_mines
[side
][0]
780 mine_total_x
+= mine
.location
.centerx
781 center
= mine
.location
.center
782 mine
.set_frameset("explode")
783 mine
.location
.center
= center
784 mine
.get_current_frameset().reset()
785 for phage
in self
.parent
.rails
.phages
:
786 px
, py
= phage
.get_center()
787 d
= sqrt((px
- mine
.location
.centerx
) ** 2 + \
788 (py
- mine
.location
.centery
) ** 2)
792 phage
.health
-= 1.5 * (reach
- float(d
)) / reach
794 if phage
not in (r
[0] for r
in outgoing
):
796 if record
[0] == phage
:
800 outgoing
.append((phage
, start
))
802 if phage
not in (r
[0] for r
in hit
):
803 hit
.append((phage
, start
))
804 self
.active_mines
[side
].remove(mine
)
805 self
.detonate_mine_fx
.play(float(mine_total_x
) / count
/ \
806 self
.get_display_surface(). \
810 for record
in outgoing
:
811 increase
+= record
[1]
812 phage_total_x
+= record
[0].get_center()[0]
813 record
[0].play(record
[0].die
)
814 # self.parent.rails.phages.remove(record[0])
815 self
.parent
.pulp
.score
+= increase
* len(outgoing
) * 10
817 self
.eliminate_phage_fx
.play(float(phage_total_x
) / \
822 for location
in self
.graticule
.locations
:
823 if location
.side
== side
:
825 self
.throttle
[side
] = 0
826 elif self
.throttle
[side
] > 500:
828 for location
in self
.graticule
.locations
:
829 if location
.side
== side
:
831 mine
= choice(self
.mines
)
834 mine
.location
.center
= location
.center
835 mine
.get_current_frameset().reset()
837 self
.active_mines
[side
].append(mine
)
838 total_x
+= mine
.location
.centerx
840 self
.drop_mine_fx
.play(float(total_x
) / \
841 len(self
.active_mines
[side
]) / \
842 self
.get_display_surface(). \
846 for ii
in xrange(len(self
.throttle
)):
847 self
.throttle
[ii
] += self
.time_filter
.get_last_frame_duration()
849 if self
.moving
[self
.LEFT
]:
850 self
.paddles
.move(-speed
)
851 self
.graticule
.move(-speed
)
852 elif self
.moving
[self
.RIGHT
]:
853 self
.paddles
.move(speed
)
854 self
.graticule
.move(speed
)
855 ds
= self
.get_display_surface()
856 if self
.paddles
.location
.right
< 0:
857 self
.paddles
.move(ds
.get_width())
858 elif self
.paddles
.location
.right
> ds
.get_width():
859 self
.paddles
.move(-ds
.get_width())
860 y
= self
.graticule
.location
.centery
861 start
= self
.parent
.link
.background
.rect
.bottom
862 end
= ds
.get_height() - 32
863 position
= (end
- y
) / float(end
- start
)
864 dy
= self
.get_game().interpolator
.get_nodeset("shoot").get_y(position
)
865 self
.graticule
.move(dy
=-dy
)
866 for location
in self
.graticule
.locations
:
867 if location
.right
< 0:
868 location
.move_ip(ds
.get_width(), 0)
869 elif location
.right
> ds
.get_width():
870 location
.move_ip(-ds
.get_width(), 0)
871 if location
.top
< self
.parent
.link
.background
.rect
.bottom
:
872 location
.move_ip(0, ds
.get_height() - \
873 self
.parent
.link
.background
.rect
.bottom
- 32)
874 self
.lattice
.move(-.5)
875 if self
.lattice
.location
.right
< ds
.get_rect().right
:
876 self
.lattice
.move(ds
.get_rect().right
- \
877 self
.lattice
.location
.right
+ 16)
878 self
.lattice
.update()
879 if not self
.parent
.is_frozen():
880 self
.graticule
.update()
881 for mine
in self
.mines
:
883 self
.paddles
.update()
884 self
.background
.update()
887 class Graticule(Sprite
):
889 def __init__(self
, parent
):
890 Sprite
.__init__(self
, parent
, 120)
891 surface
= Surface((14, 14), SRCALPHA
)
892 rect
= surface
.get_rect()
895 inner_color
, outer_color
, bg_color
= Color(0, 0, 0), Color(0, 0, 0), \
898 for ii
in xrange(count
):
899 inner_color
.hsla
= 80, 50, 40 + 10 * (float(color_ii
) / mid
), 100
900 hue
= randrange(0, 360)
901 outer_color
.hsla
= hue
, 100, 50, 100
902 bg_color
.hsla
= randrange(0, 360), 100, 50, 16
903 frame
= surface
.copy()
904 circle(frame
, bg_color
, rect
.center
, 7)
905 circle(frame
, inner_color
, (rect
.centerx
, rect
.centery
+ 1), 5)
906 circle(frame
, outer_color
, rect
.center
, 5)
907 circle(frame
, inner_color
, rect
.center
, 3)
908 circle(frame
, (0, 0, 0, 0), (rect
.centerx
, rect
.centery
+ 1), 2)
909 self
.add_frame(frame
)
910 color_ii
+= -1 if ii
>= count
/ 2 else 1
915 def __init__(self
, parent
):
916 Sprite
.__init__(self
, parent
, 120)
917 surface
= Surface((20, 20), SRCALPHA
)
919 distances
= [4, 2, 2, 1, 0, 1, 2, 2, 4]
920 radii
= [2, 2, 2, 2, 2, 2, 2, 2, 2]
922 for ii
in xrange(particle_count
):
923 angle
= 360 * float(ii
) / particle_count
924 distance_ii
= ii %
len(distances
)
925 particles
.append(Particle(ii
, angle
, distance_ii
))
928 self
.add_frameset(name
="wait", switch
=True)
929 for frame_ii
in xrange(frame_count
):
930 frame
= surface
.copy()
931 circle(frame
, (255, 255, randint(0, 63), randint(100, 160)),
933 for particle
in sorted(particles
, key
=lambda p
: p
.layer
):
934 distance
= distances
[particle
.distance_ii
]
935 x
= int(round(cx
+ distance
* sin(radians(particle
.angle
))))
936 y
= int(round(cy
+ distance
* - cos(radians(particle
.angle
))))
937 circle(frame
, particle
.color
, (x
, y
),
938 radii
[particle
.distance_ii
])
939 particle
.distance_ii
+= 1
940 if particle
.distance_ii
>= len(distances
):
941 particle
.distance_ii
= 0
942 particle
.layer
+= ((len(particles
) / 2 - particle
.layer
) * \
945 particle
.angle
= (particle
.angle
+ 180) %
360
946 self
.add_frame(frame
)
947 self
.add_frameset(name
="explode", switch
=True)
948 surface
= Surface((100, 100), SRCALPHA
)
950 color
= Color(0, 0, 0)
951 for radius
in xrange(6, 50, 2):
952 frame
= surface
.copy()
953 ratio
= float(radius
- 6) / (50 - 6)
954 color
.hsla
= 60 * (1 - ratio
), 80 + 10 * (1 - ratio
), \
955 75 + 18 * (1 - ratio
), thickness
* 30 * (100 / 255.0)
956 circle(frame
, color
, (50, 50), radius
, max(1, int(thickness
)))
958 self
.add_frame(frame
)
959 self
.set_frameset("wait")
962 def shift_frame(self
):
963 Sprite
.shift_frame(self
)
964 frameset
= self
.get_current_frameset()
965 if frameset
.name
== "explode":
966 if frameset
.current_index
== frameset
.length() - 1:
967 self
.set_frameset("wait")
973 def __init__(self
, ii
, angle
, distance_ii
):
974 self
.color
= Color(0, 0, 0)
975 self
.color
.hsla
= 0, 0, randint(0, 100), 100
976 self
.color
= choice([(27, 27, 27), (255, 63, 63), (63, 63, 255),
980 self
.distance_ii
= distance_ii
983 class Rails(GameChild
, list):
985 def __init__(self
, parent
):
986 GameChild
.__init__(self
, parent
)
987 self
.display_surface
= self
.get_display_surface()
988 self
.depth
= self
.display_surface
.get_height() + 4
991 interpolator
= self
.get_game().interpolator
992 self
.spawn_nodeset
= interpolator
.get_nodeset("spawn")
993 self
.step_nodeset
= interpolator
.get_nodeset("step")
995 limit
= self
.display_surface
.get_width() - margin
996 step
= (limit
- margin
) / 16
997 for x
in xrange(48, limit
, step
):
998 self
.append(Rail(self
, x
,
999 x
> self
.display_surface
.get_rect().centerx
))
1000 deviations
= self
.deviations
= []
1003 nodeset
= self
.get_game().interpolator
.get_nodeset("deviation-" + \
1007 deviations
.append(nodeset
)
1010 self
.set_deviation()
1013 def set_deviation(self
):
1014 self
.deviation
= choice(self
.deviations
)
1015 self
.set_background()
1018 self
.increase_spawn_rate()
1021 def clear_phages(self
):
1024 def increase_spawn_rate(self
):
1025 self
.spawn_rate
= self
.spawn_nodeset
.get_y(self
.parent
.wave
)
1026 self
.phage_step
= self
.step_nodeset
.get_y(self
.parent
.wave
)
1028 def set_background(self
):
1029 end
= self
.parent
.link
.background
.location
.bottom
1030 length
= self
.depth
- end
1031 color
= Color(0, 0, 0)
1032 background
= self
.background
= \
1033 Surface(self
.get_display_surface().get_size())
1034 background
.set_colorkey((0, 0, 0))
1035 for ii
, y
in enumerate(xrange(self
.depth
, end
- 5, -5)):
1036 dx
= self
.stray
* self
.deviation
.get_y(float(ii
* 5) / length
)
1037 px
= self
.stray
* self
.deviation
.get_y(float((ii
- 1) * 5) / length
)
1038 hue
= min(360, int(360 * float(ii
* 5) / length
))
1039 color
.hsla
= hue
, 100, 40, 100
1041 modifier
= rail
.get_modifier()
1042 line(background
, color
, (rail
.x
+ px
* modifier
, y
),
1043 (rail
.x
+ dx
* modifier
, y
))
1046 if random() < self
.spawn_rate
:
1047 self
.phages
.append(Phage(self
, choice(self
)))
1049 self
.display_surface
.blit(self
.background
, (0, self
.view_y
),
1051 self
.display_surface
.get_width(), height
))
1053 if self
.view_y
+ height
< self
.parent
.link
.background
.location
.bottom
:
1054 self
.view_y
= self
.display_surface
.get_height() - height
1055 for phage
in self
.phages
:
1059 class Rail(GameChild
):
1061 def __init__(self
, parent
, x
, mirrored
):
1062 GameChild
.__init__(self
, parent
)
1064 self
.mirrored
= mirrored
1066 def get_modifier(self
):
1067 return -1 if self
.mirrored
else 1
1070 class Phage(Animation
):
1072 TRANSPARENT_COLOR
= (255, 0, 255)
1074 def __init__(self
, parent
, rail
):
1075 Animation
.__init__(self
, parent
, self
.die
, 50)
1079 body
= self
.body
= []
1080 self
.yr
= self
.parent
.parent
.link
.background
.location
.bottom
, \
1082 for size
in xrange(4, 0, -1):
1083 segment
= Sprite(self
, 500)
1084 for ii
in xrange(2):
1085 surface
= Surface((size
, size
))
1086 surface
.fill([(255, 255, 255), (31, 31, 31)][(size
+ ii
) %
2])
1087 surface
.set_colorkey(self
.TRANSPARENT_COLOR
)
1088 segment
.add_frame(surface
)
1089 body
.append(segment
)
1090 center
= self
.parent
.phage_step
1091 self
.step
= random() * .004 - .002 + center
1093 def get_center(self
):
1094 return self
.body
[0].location
.midbottom
1097 for segment
in self
.body
:
1098 alpha
= segment
.alpha
- 48
1100 self
.parent
.phages
.remove(self
)
1104 for _
in xrange(10):
1105 w
, h
= segment
.location
.size
1106 segment
.get_current_frame().set_at((randrange(0, w
),
1108 self
.TRANSPARENT_COLOR
)
1109 segment
.set_alpha(alpha
)
1112 if not self
.is_playing():
1113 step
= self
.parent
.phage_step
* self
.health
1116 for ii
, segment
in sorted(enumerate(self
.body
), key
=lambda b
: b
[0],
1118 dx
= self
.parent
.deviation
.get_y(self
.t
- ii
* step
) * \
1119 self
.parent
.stray
* self
.rail
.get_modifier()
1120 segment
.location
.center
= self
.rail
.x
+ dx
, \
1121 yr
[1] - (yr
[1] - yr
[0]) * (self
.t
- \
1125 for segment
in self
.body
:
1127 Animation
.update(self
)
1130 class Oracle(Animation
):
1132 def __init__(self
, parent
):
1133 Animation
.__init__(self
, parent
)
1134 self
.time_filter
= self
.get_game().time_filter
1135 self
.display_surface
= self
.get_display_surface()
1136 # self.line_appears_fx = SoundEffect(self, "no")
1137 screens
= self
.screens
= []
1138 for ii
in xrange(2):
1139 surface
= Surface((48, 48), SRCALPHA
)
1140 surface
.fill((127, 127, 127, 127))
1141 screens
.append(surface
)
1142 lines
= self
.lines
= []
1143 for ii
in xrange(2):
1144 line
= Surface((36, 6))
1145 line
.fill((31, 31, 31))
1148 line
.set_colorkey(key
)
1149 line
.fill(key
, (14, 0, 8, 6))
1151 self
.animals
= Animals(self
)
1152 self
.coins
= [Coin(self
, ii
) for ii
in xrange(3)]
1153 self
.register(self
.display_indicator
)
1156 def display_indicator(self
):
1157 self
.clear_screens()
1158 self
.parent
.parent
.book
.hide_explanations()
1159 response
= choice(self
.parent
.parent
.book
)
1160 response
.show_indicator()
1161 response
.show_explanation()
1162 response
.explanation
.halt()
1164 def clear_screens(self
):
1165 for screen
in self
.screens
:
1166 screen
.fill(screen
.get_at((0, 0)))
1171 self
.flip_elapsed
= 0
1172 self
.wait_elapsed
= 0
1173 self
.waiting
= False
1174 self
.clear_screens()
1175 for coin
in self
.coins
:
1181 if not self
.parent
.is_frozen() and not self
.waiting
:
1183 for coin
in self
.coins
:
1185 coin
.set_frameset(0)
1186 if len(self
.hexagram
) == 6:
1188 self
.parent
.parent
.book
.hide_indicators()
1189 for hexagram
in self
.parent
.parent
.book
:
1190 explanation
= hexagram
.explanation
1191 if not explanation
.is_hidden():
1192 frameset
= explanation
.get_current_frameset()
1193 frameset
.current_index
= frameset
.length() - 1
1194 hexagram
.explanation
.play()
1195 self
.parent
.increase_wave()
1196 self
.flip_elapsed
= 0
1199 self
.flip_elapsed
+= self
.time_filter
.get_last_frame_duration()
1200 if self
.flip_elapsed
> flip_length
:
1201 self
.coins
[self
.flips
].stop()
1203 self
.flip_elapsed
-= flip_length
1205 self
.hexagram
.append(Result(self
.coins
))
1206 if len(self
.hexagram
) == 3:
1207 self
.animals
.send(self
.hexagram
)
1208 elif len(self
.hexagram
) == 6:
1209 self
.play(self
.display_indicator
, delay
=1000,
1213 # self.line_appears_fx.play()
1215 self
.wait_elapsed
+= self
.time_filter
.get_last_frame_duration()
1216 if self
.wait_elapsed
> 7500:
1217 self
.waiting
= False
1218 self
.wait_elapsed
= 0
1220 if self
.parent
.parent
.book
.are_indicators_hidden():
1221 for ii
, result
in enumerate(self
.hexagram
):
1222 screen
= self
.screens
[ii
/ 3]
1223 screen
.blit(self
.lines
[result
.get_line_index()],
1224 (6, 5 + ((2 - ii
) %
3) * 16))
1225 for ii
, screen
in enumerate(self
.screens
):
1226 rect
= screen
.get_rect()
1228 rect
.topleft
= (ow
, oh
)
1230 rect
.topright
= self
.display_surface
.get_width() - ow
, oh
1231 self
.display_surface
.blit(screen
, rect
)
1232 for coin
in self
.coins
:
1234 Animation
.update(self
)
1237 class Animals(GameChild
, list):
1239 def __init__(self
, parent
):
1240 GameChild
.__init__(self
, parent
)
1241 self
.collect_token_fx
= SoundEffect(self
, "power-up", .7)
1242 font
= Font(self
.get_resource("display", "font"), 18)
1243 color
= Color(0, 0, 0)
1244 for glyph
in [u
"馬", u
"羊", u
"雉", u
"龍", u
"雞", u
"豕", u
"狗", u
"牛"]:
1245 animal
= Sprite(self
, 80)
1246 for ii
in xrange(3):
1247 hue
= int(360 * float(ii
) / 3)
1248 for jj
in xrange(8):
1249 lightness
= 20 + 80 * float(jj
+ 1) / 8
1250 w
, h
= font
.size(glyph
)
1251 frame
= Surface((w
+ 2, h
+ 2), SRCALPHA
)
1252 color
.hsla
= hue
, 80, lightness
, 100
1253 frame
.blit(font
.render(glyph
, True, color
), (0, 0))
1254 frame
.blit(font
.render(glyph
, True, color
), (1, 0))
1255 frame
.blit(font
.render(glyph
, True, color
), (2, 0))
1256 frame
.blit(font
.render(glyph
, True, color
), (2, 1))
1257 frame
.blit(font
.render(glyph
, True, color
), (2, 2))
1258 frame
.blit(font
.render(glyph
, True, color
), (1, 2))
1259 frame
.blit(font
.render(glyph
, True, color
), (0, 2))
1260 frame
.blit(font
.render(glyph
, True, color
), (0, 1))
1261 color
.hsla
= (hue
+ 60) %
360, 100, lightness
, 100
1262 frame
.blit(font
.render(glyph
, True, color
), (1, 1))
1263 animal
.add_frame(frame
)
1264 paddles
= self
.parent
.parent
.paddles
1265 animal
.add_location(count
=3)
1270 self
.in_motion
= None
1274 def send(self
, hexagram
):
1275 index
= sum(2 ** ii
* x
for ii
, x
in \
1276 enumerate(reversed([r
.get_binary() for r
in hexagram
])))
1277 in_motion
= self
.in_motion
= self
[index
]
1278 for location
in in_motion
.locations
:
1279 location
.bottom
= self
.parent
.parent
.link
.background
. \
1285 paddles
= self
.parent
.parent
.paddles
1286 for ii
, location
in enumerate(self
.in_motion
.locations
):
1287 location
.centerx
= paddles
.paddles
.location
.left
+ \
1288 paddles
.paddle_length
/ 2
1290 location
.centerx
+= self
.get_display_surface().get_width()
1292 location
.centerx
+= paddles
.paddle_length
+ paddles
.margin
1293 if self
.in_motion
.location
.colliderect(paddles
.paddles
.location
):
1294 paddles
.arrange_graticules(self
.index(self
.in_motion
) + 1)
1295 self
.collect_token_fx
.play(paddles
.paddles
.location
.centerx
/ \
1296 float(self
.get_display_surface().\
1300 self
.in_motion
.move(dy
=4)
1301 self
.in_motion
.update()
1306 HEADS
, TAILS
= "heads", "tails"
1308 def __init__(self
, parent
, ii
):
1309 Sprite
.__init__(self
, parent
)
1312 for frame_ii
in xrange(w
):
1313 frame
= Surface((w
, w
))
1315 frame
.set_colorkey(key
)
1316 x
= [0, 1, 2, 3, 3, 2, 1, 0][frame_ii
]
1317 rect
= (x
, 0, (w
/ 2 - x
) * 2, w
)
1318 thickness
= 0 if frame_ii
< (w
/ 2) else 1
1319 ellipse(frame
, (160, 120, 40), rect
)
1320 ellipse(frame
, (200, 150, 60), rect
, thickness
)
1321 self
.add_frame(frame
)
1322 self
.add_frameset([0], name
=self
.HEADS
)
1323 self
.add_frameset([w
- 1], name
=self
.TAILS
)
1324 self
.add_frameset(xrange(w
), name
="flipping")
1325 self
.location
.top
= 36
1326 self
.location
.centerx
= 20 * (ii
- 3 / 2.0 + .5) + \
1327 self
.display_surface
.get_rect().centerx
1332 self
.side
= self
.HEADS
1334 self
.side
= self
.TAILS
1335 self
.set_frameset(self
.side
)
1340 def __init__(self
, coins
):
1343 if coin
.side
== coin
.HEADS
:
1349 def get_line_index(self
):
1350 return 0 if self
.total
in [7, 9] else 1
1352 def get_binary(self
):
1353 return 0 if self
.total
in [7, 9] else 1