Skip to content

Instantly share code, notes, and snippets.

@llimllib
Created July 20, 2020 02:31
Show Gist options
  • Save llimllib/f71168da1815c701c1f928fe48479fa7 to your computer and use it in GitHub Desktop.
Save llimllib/f71168da1815c701c1f928fe48479fa7 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
from enum import Enum
import math
import pyglet
from pyglet import event
from pyglet import shapes
from pyglet.window import key
# * lose game when ball hits bottom
# * change angle when it hits the paddle
# * make brick width dependent on screen width
# * space bar to start
# * smooth motion
# * handle resize (?)
# * probably could be skipped in the name of time
# examples/media_player.py in the pyglet repo is an excellent example to work from:
# https://github.com/pyglet/pyglet/blob/0b785dc8ffac0da86513e825552b49f839173596/examples/media_player.py
# colors from my web tut
# "#FF1C0A", "#FFFD0A", "#00A308", "#0008DB", "#EB0093"
# def hex2tup(hex_):
# return (int(hex_[1:3], 16), int(hex_[3:5], 16), int(hex_[5:7], 16))
brickcolors = {
"red": (255, 28, 10),
"yellow": (255, 253, 10),
"green": (0, 163, 8),
"blue": (0, 8, 219),
"magenta": (235, 0, 147),
}
class Ball(shapes.Circle):
RADIUS = 10
HALF_RADIUS = 5
def __init__(self, x, y, max_width, max_height, color=(255, 255, 255), batch=None):
super(Ball, self).__init__(x, y, self.RADIUS, color=color, batch=batch)
self._radius = self.RADIUS
self.max_width = max_width
self.max_height = max_height
self.x_velocity = max_width / 4
self.y_velocity = -max_height / 4
def _update_position(self):
super(Ball, self)._update_position()
def update(self, dt):
hit_bottom = False
if (self.x + self.HALF_RADIUS) > self.max_width and self.x_velocity > 0:
self.x_velocity *= -1
if (self.x - self.HALF_RADIUS) < 0 and self.x_velocity < 0:
self.x_velocity *= -1
if (self.y + self.HALF_RADIUS) > self.max_height and self.y_velocity > 0:
self.y_velocity *= -1
if (self.y - self.HALF_RADIUS) < 0 and self.y_velocity < 0:
self.y_velocity *= -1
hit_bottom = True
self.x += dt * self.x_velocity
self.y += dt * self.y_velocity
if hit_bottom:
return (self.x, self.y)
return None
class Paddle(shapes.Rectangle):
WIDTH = 100
HEIGHT = 20
def __init__(self, x, y, color=(128, 128, 128), batch=None):
super(Paddle, self).__init__(x, y, self.WIDTH, self.HEIGHT, color, batch)
class Brick(shapes.Rectangle):
HEIGHT = 20
def __init__(self, x, y, width, color=(128, 128, 128), batch=None):
super(Brick, self).__init__(x, y, width, self.HEIGHT, color, batch)
class BreakoutWindow(pyglet.window.Window):
WIDTH = 960
HEIGHT = 960
CENTERX = WIDTH * 0.5
CENTERY = HEIGHT * 0.5
# margin between the left of the window and the first brick, and the right
# of the window and the last brick
MARGIN = 10
# padding between bricks
PADDING = 10
PADDLE_RATE = 400
COLUMNS = 5
def __init__(self):
super(BreakoutWindow, self).__init__(self.WIDTH, self.HEIGHT)
self.batch = pyglet.graphics.Batch()
self.fps = pyglet.text.Label(
"0",
font_size=12,
x=20,
y=40,
anchor_x="center",
anchor_y="center",
batch=self.batch,
)
self.ball = Ball(
self.CENTERX,
self.HEIGHT / 5,
self.WIDTH,
self.HEIGHT,
color=(255, 255, 255),
batch=self.batch,
)
self.paddle = Paddle(self.CENTERX - (Paddle.WIDTH / 2), 0, batch=self.batch)
brickwidth = (
self.WIDTH - 2 * self.MARGIN - (self.COLUMNS - 1) * self.PADDING
) / self.COLUMNS
self.bricks = []
for row, color in enumerate(brickcolors.values()):
rowy = self.HEIGHT - Brick.HEIGHT - self.MARGIN - row * (Brick.HEIGHT + 5)
for col in range(self.COLUMNS):
self.bricks.append(
Brick(
self.MARGIN + col * (brickwidth + self.PADDING),
rowy,
brickwidth,
color=color,
batch=self.batch,
)
)
# XXX: forward this responsibility to paddle?
self.left_is_down = False
self.right_is_down = False
self.game_over = False
self.mousex = None
pyglet.clock.schedule_interval(self.update, 1 / 60)
def on_key_press(self, symbol, modifiers):
if symbol == key.LEFT:
self.left_is_down = True
if symbol == key.RIGHT:
self.right_is_down = True
# press x to quit
if symbol == key.X:
pyglet.app.exit()
def on_key_release(self, symbol, modifiers):
if symbol == key.LEFT:
self.left_is_down = False
if symbol == key.RIGHT:
self.right_is_down = False
def on_mouse_motion(self, x, y, dx, dy):
self.mousex = x
def update(self, dt):
# TODO: let the user start a new game or s/t
if self.game_over:
self.game_over_label = pyglet.text.Label(
"GAME OVER",
font_size=96,
x=self.CENTERX,
y=self.CENTERY,
anchor_x="center",
anchor_y="center",
batch=self.batch,
)
self.batch.draw()
return
window.clear()
if self.left_is_down:
if self.paddle.x > 0:
self.paddle.x -= dt * self.PADDLE_RATE
elif self.right_is_down:
if self.paddle.x < self.WIDTH - self.paddle.WIDTH:
self.paddle.x += dt * self.PADDLE_RATE
elif self.mousex is not None:
# updating paddle.x on every mouse move gives horribly jumpy
# animation, so save it for the update. We still get jumpy motion,
# but slightly better. How to fix for real? What's going on?
self.paddle.x = self.mousex - self.paddle.WIDTH / 2
self.mousex = None
hit_bottom = self.ball.update(dt)
if hit_bottom:
if not self.paddle.x < hit_bottom[0] < self.paddle.x + self.paddle.WIDTH:
self.game_over = True
# add an FPS meter
self.fps.text = f"{pyglet.clock.get_fps():0.0f}"
self.batch.draw()
if __name__ == "__main__":
window = BreakoutWindow()
pyglet.app.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment