-
-
Save gillibrand/3271073 to your computer and use it in GitHub Desktop.
# Hockey | |
# | |
# A air hockey game for two players. First to | |
# seven wins. | |
# Most of the game is drawn with the scene | |
# module. Goal and winner messages are animated | |
# with layers. | |
from scene import * | |
from sound import * | |
from copy import copy | |
class Player (object): | |
def __init__(self, name, color): | |
self.color = color | |
self.score = 0 | |
self.name = name | |
def __str__(self): | |
return self.name | |
class Puck (object): | |
def __init__(self, pos, scene): | |
self.vector = Vector3(0, 0, 0) | |
self.pos = pos | |
def reverse_vector(self): | |
v = self.vector | |
self.vector = Vector3(-v.x, -v.y, 0) | |
class HockeyScene (Scene): | |
def setup(self): | |
self.left_player = Player("Red Player", Color(1, 0, 0)) | |
self.right_player = Player("Blue Player", Color(0, 0, 1)) | |
self.players = (self.left_player, self.right_player) | |
for s in ('Drums_06', 'Woosh_1', 'Powerup_2'): | |
load_effect(s) | |
center = self.size.w / 2 | |
middle = self.size.h / 2 | |
# goal height | |
gh = self.size.h / 4 | |
self.puck_radius = gh / 2.5 | |
self.puck = self.centered_puck() | |
# the last time the puck was hit. Used to animate its slow down | |
self.puck_start_t = 0 | |
pr = self.puck_radius | |
self.line_width = self.puck_radius / 6 | |
lw = self.line_width | |
self.red_line = Rect(center - lw / 2, 0, lw, self.size.h) | |
self.red_circle = Rect(w=gh, h=gh) | |
self.red_circle.center(self.bounds.center()) | |
self.left_goal = Rect(w=gh, h=gh) | |
self.right_goal = Rect(w=gh, h=gh) | |
self.left_goal.center(0, middle) | |
self.right_goal.center(self.size.w, middle) | |
# when someone reaches 7 goals, save them to show a message | |
self.winner = None | |
def score_goal(self, player): | |
player.score += 1 | |
self.puck = self.centered_puck() | |
for p in self.players: | |
if p.score >= 7: | |
self.winner = p | |
break | |
overlay = Layer(self.bounds) | |
def hide_overlay(): | |
overlay.animate('alpha', 0.0, 1, completion=lambda: overlay.remove_layer()) | |
if self.winner: | |
message = "%s Wins" % (self.winner) | |
on_completion = None | |
self.hide_overlay = hide_overlay | |
else: | |
message = "Goal!" | |
on_completion = hide_overlay | |
# restart the puck on the other player's side | |
if player == self.left_player: | |
self.puck.pos.x += self.size.w / 4 + self.puck_radius | |
else: | |
self.puck.pos.x -= self.size.w / 4 + self.puck_radius | |
size = self.puck_radius * 1.5 | |
text_layer = TextLayer(message, 'Futura', size) | |
text_layer.frame.center(self.bounds.center()) | |
text_layer.frame.y += self.size.h / 4 | |
text_layer.animate('scale_x', 1.3, 0.3, autoreverse=True) | |
text_layer.animate('scale_y', 1.3, 0.3, autoreverse=True) | |
overlay.add_layer(text_layer) | |
start_color, end_color = copy(player.color), copy(player.color) | |
start_color.a = 0 | |
end_color.a = .5 | |
overlay.background = start_color | |
overlay.animate('background', end_color, .7, completion=on_completion) | |
self.add_layer(overlay) | |
play_effect('Woosh_1') | |
def centered_puck(self): | |
pos = Rect(w=self.puck_radius, h=self.puck_radius) | |
pos.center(self.bounds.center()) | |
return Puck(pos, self) | |
def move_puck(self): | |
puck = self.puck | |
ease_x = (self.t - self.puck_start_t) / 4.0 | |
ease = max(0, 1 - curve_ease_out(ease_x)) | |
puck.pos.x += puck.vector.x * ease | |
puck.pos.y += puck.vector.y * ease | |
if puck.pos.right() > self.size.w and not puck.pos.intersects(self.right_goal): | |
puck.vector.x *= -1 | |
play_effect('Drums_06') | |
puck.pos.x = min(self.size.w - puck.pos.w, puck.pos.x) | |
if puck.pos.left() > self.size.w: | |
self.score_goal(self.left_player) | |
return | |
if puck.pos.left() < 0 and not puck.pos.intersects(self.left_goal): | |
puck.vector.x *= -1 | |
play_effect('Drums_06') | |
puck.pos.x = max(0, puck.pos.x) | |
if puck.pos.right() < 0: | |
self.score_goal(self.right_player) | |
return | |
if puck.pos.top() > self.size.h or puck.pos.bottom() < 0: | |
puck.vector.y *= -1 | |
play_effect('Drums_06') | |
puck.pos.y = max(0, puck.pos.y) | |
puck.pos.y = min(self.size.h - puck.pos.h, puck.pos.y) | |
def draw(self): | |
stroke_weight(self.line_width) | |
background(1, 1, 1) | |
# red lines on ice in middle | |
stroke(1.00, 0.40, 0.40) | |
fill(1.00, 0.40, 0.40) | |
rect(*self.red_line.as_tuple()) | |
fill(1, 1, 1) | |
ellipse(*self.red_circle.as_tuple()) | |
# red goal markers | |
for goal in (self.left_goal, self.right_goal): | |
rect(*goal.as_tuple()) | |
for touch in self.touches.values(): | |
self.handle_touch(touch) | |
# black puck | |
no_stroke() | |
fill(0, 0, 0) | |
ellipse(*self.puck.pos.as_tuple()) | |
self.draw_scores() | |
self.move_puck() | |
if self.root_layer: | |
self.root_layer.update(self.dt) | |
self.root_layer.draw() | |
if self.winner is not None: | |
c = Rect() | |
c.center(self.bounds.center()) | |
tint(1, 1, 1) | |
message = "Tap to Play Again" | |
y_offset = self.size.h / 4 | |
size = self.puck_radius | |
text(message, x=c.x, y=c.y-y_offset, font_size=size, font_name='Futura') | |
def draw_scores(self): | |
for p, x in zip(self.players, (50, self.size.w - 50)): | |
tint(*p.color.as_tuple()) | |
text(str(p.score), x=x, y=self.size.h- 50, font_size=32, font_name='Futura') | |
def handle_touch(self, touch): | |
if self.winner: | |
# check for Winner overlay and clear. | |
# This is Tap to Play Again | |
self.winner = None | |
self.hide_overlay() | |
del self.hide_overlay | |
self.left_player.score = 0 | |
self.right_player.score = 0 | |
return | |
tx = touch.location.x | |
ty = touch.location.y | |
finger = Rect(tx - 20, ty - 20, 40, 40) | |
if finger.intersects(self.puck.pos): | |
dx = tx - touch.prev_location.x | |
dy = ty - touch.prev_location.y | |
# Super simple richochet off "unmoving" finger. Just reverse it | |
if abs(dx) < 5 and abs(dy) < 5: | |
self.puck.reverse_vector() | |
# Slide puck out from the finger. No puck holding. | |
if dx == 0: dx = self.puck.vector.x | |
if dy == 0: dy = self.puck.vector.y | |
if not any([dx, dy]): return | |
while finger.intersects(self.puck.pos): | |
self.puck.pos.x += dx | |
self.puck.pos.y += dy | |
else: | |
self.step = 0 | |
self.puck_start_t = self.t | |
self.puck.vector = Vector3(dx * .8, dy * .8, 0) | |
self.puck.pos.x += dx | |
self.puck.pos.y += dy | |
run(HockeyScene(), LANDSCAPE) |
Sorry, missed your comment. Yes, you can include this as an example, but it looks like its too late for 1.1. It needs some tweaking for iPhone anyway--too many hard coded sizes.
Funnily enough, on the Codea site this was used to test the url.urlopen() and then script it to the editor.
code:
import urllib
import editor
url = 'https://raw.github.com/gist/3271073/6130e044172d580722317a95fc1d0799e2be893b/air_hockey.py'
contents = urllib.urlopen(url).read()
editor.make_new_file('AirHockey', contents)
I take no credit for the code - it was done by a different member. Link to the discussion: http://twolivesleft.com/Codea/Talk/discussion/1652/what-others-do%3A-pythonista
I like the air hockey game as well. Thank you for coding it!
Updated for iPhone.
Cant find TextLayer in docs!?
Thanks for this. It is just amazing! You did a great job.
My pythonista version said that the player stuff was not valid and in needed a parameter
Wow, this is really amazing! I'm the developer of Pythonista. Would you mind if I include this as a built-in example in a future update? I would of course credit you as the author. By the way, here's a sneak preview of what's coming in v1.1: http://omz-software.com/pythonista/docs/ios/new.html -- I'll submit it later this week and it would be pretty cool to include some new sample code as well.