diff --git a/README.md b/README.md index f05b403..2f16ff1 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ audio asset files**: every sprite, texture, particle, chiptune and sound effect is generated by code at runtime. Not web-based: it opens its own native window and renders on a low-res -`480×360` virtual canvas, upscaled with crisp nearest-neighbour scaling for an -authentic retro look. +**portrait** `360×480` virtual canvas — like a proper pinball machine — +upscaled with crisp nearest-neighbour scaling for an authentic retro look. ![demo](shots/3_play.png) diff --git a/autotest.py b/autotest.py index 1755931..d252b5f 100644 --- a/autotest.py +++ b/autotest.py @@ -54,9 +54,9 @@ class AutoPlayer: # flipper policy: catch anything heading down the middle/bottom b = t.ball - if t.ball_live and b.vy > 0 and b.y > 150: - g.keys["L"] = b.x < 258 - g.keys["R"] = b.x > 222 + if t.ball_live and b.vy > 0 and b.y > 200: + g.keys["L"] = b.x < 196 + g.keys["R"] = b.x > 164 else: if t.ball_live and self.rng.random() < 0.04: g.keys["L"] = self.rng.random() < 0.5 diff --git a/game.py b/game.py index eaafd91..b3f8205 100644 --- a/game.py +++ b/game.py @@ -9,7 +9,7 @@ import pygame from physics import (CV_W, CV_H, PLAY_L, PLAY_R, PLAY_T, PLAY_B, BALL_R, CENTER_HOLE, OUTLANE_L, OUTLANE_R, DRAIN_Y, LAUNCH_DIR, LAUNCH_MIN, LAUNCH_MAX, Table, FLIP_PIVOT_L, FLIP_PIVOT_R, - FLIP_RADIUS, FLIP_LEN, flipper_tip) + FLIP_RADIUS, FLIP_LEN, flipper_tip, BUMPERS) import font as pix # --------------------------------------------------------------------------- @@ -362,9 +362,9 @@ class Game: t = y / CV_H col = tuple(int(a + (b - a) * t) for a, b in zip(C["bg_top"], C["bg_bot"])) pygame.draw.line(bg, col, (0, y), (CV_W, y)) - # twinkle stars & planets for the background + # twinkle stars & planets around the table, drawn over the HUD & margins for sx, sy, tw, ph in self.stars_bg: - if 0 < sx < 20 or 460 < sx < CV_W or sy < 46: + if sx < PLAY_L - 6 or sx > PLAY_R + 6 or sy < 50: pygame.draw.rect(bg, (230, 225, 255), (sx, sy, 1, 1)) self.bg = bg @@ -382,12 +382,11 @@ class Game: PLAY_R - PLAY_L - 8, PLAY_B - PLAY_T - 8), border_radius=5) # candy dot sparkle inside the field r = self.rng - for _ in range(46): + for _ in range(60): dx = r.randrange(int(PLAY_L) + 12, int(PLAY_R) - 12) dy = r.randrange(int(PLAY_T) + 14, int(PLAY_B) - 12) if any((dx - b["pos"][0]) ** 2 + (dy - b["pos"][1]) ** 2 < (b["r"] + 4) ** 2 - for b in ({"pos": (150, 180), "r": 23}, {"pos": (330, 180), "r": 23}, - {"pos": (240, 240), "r": 23})): + for b in BUMPERS): continue col = PASTELS[r.randrange(len(PASTELS))] field.fill(tuple(c // 3 for c in col), (dx, dy, 2, 2)) @@ -412,8 +411,11 @@ class Game: hud.fill(C["hud"]) pygame.draw.line(hud, C["rail_dark"], (0, 50), (CV_W, 50), 2) pygame.draw.line(hud, C["hud_line"], (0, 47), (CV_W, 47), 1) - pygame.draw.rect(hud, (255, 255, 255), (0, 0, 8, 52)) - pygame.draw.rect(hud, (255, 255, 255), (CV_W - 8, 0, 8, 52)) + # static labels + pix.draw_text(hud, 10, 4, "SCORE", C["dim"], 1) + pix.draw_text(hud, CV_W - 10, 4, "BEST", C["dim"], 1, align="right") + pix.draw_text(hud, 10, 33, "BALLS", C["dim"], 1) + pix.draw_text(hud, 120, 33, "STARS", C["dim"], 1) self.hud = hud # ------------------------------------------------------------------ game @@ -471,8 +473,8 @@ class Game: self.sfx.play("fever") self.cat_say("CANDY FEVER!", 90, "star") self.shake = 3 - self.add_msg("CANDY FEVER!", CV_W // 2, 150, C["gold"], scale=2) - self.add_msg("DOUBLE SCORE x2", CV_W // 2, 166, C["pink"], scale=1) + self.add_msg("CANDY FEVER!", CV_W // 2, 210, C["gold"], scale=2) + self.add_msg("DOUBLE SCORE x2", CV_W // 2, 226, C["pink"], scale=1) for i in range(30): self.particles.append(dict( x=self.rng.uniform(PLAY_L + 10, PLAY_R - 10), y=self.rng.uniform(60, 120), @@ -645,7 +647,7 @@ class Game: self.lives += 1 self.sfx.play("extra") self.cat_say("EXTRA BALL!", 90, "star") - self.add_msg("EXTRA BALL!", CV_W // 2, 120, C["pink"], scale=2) + self.add_msg("EXTRA BALL!", CV_W // 2, 200, C["pink"], scale=2) self.next_extra += 30000 def add_msg(self, text, x, y, color, scale=1): @@ -741,7 +743,7 @@ class Game: # combo indicator if self.combo >= 2 and self.pending == "live": col = (255, 140, 90) if self.combo >= 5 else C["gold"] - pix.draw_text(c, CV_W // 2, 56, "COMBO x%d!" % self.combo, col, 1, align="center") + pix.draw_text(c, CV_W // 2, 68, "COMBO x%d!" % self.combo, col, 1, align="center") # particles for p in self.particles: @@ -772,21 +774,21 @@ class Game: # pending banners if self.pending == "ball_banner" and self.pending_t > 40: self._dim_center(c) - pix.draw_text(c, CV_W // 2, 140, "BALL %d" % self.ball_no, C["white"], 3, align="center") - pix.draw_text(c, CV_W // 2, 170, "GET READY...", C["pink"], 1, align="center") + pix.draw_text(c, CV_W // 2, 255, "BALL %d" % self.ball_no, C["white"], 3, align="center") + pix.draw_text(c, CV_W // 2, 300, "GET READY...", C["pink"], 1, align="center") if self.pending == "drain_pause": self._dim_center(c) - pix.draw_text(c, CV_W // 2, 150, "LOST THE BALL!", C["pink"], 2, align="center") + pix.draw_text(c, CV_W // 2, 285, "LOST THE BALL!", C["pink"], 2, align="center") if self.pending == "serve" and not t.ball_live: - pix.draw_text(c, CV_W // 2, 300, "HOLD SPACE / UP TO CHARGE", C["dim"], 1, align="center") + pix.draw_text(c, CV_W // 2, 372, "HOLD SPACE / UP TO CHARGE", C["dim"], 1, align="center") # HUD self._render_hud(c) def _dim_center(self, c): self.overlay.fill((0, 0, 0, 0)) - pygame.draw.rect(self.overlay, (12, 8, 40, 160), - (CV_W // 2 - 100, 110, 200, 90), border_radius=8) + pygame.draw.rect(self.overlay, (12, 8, 40, 170), + (CV_W // 2 - 100, 230, 200, 120), border_radius=10) c.blit(self.overlay, (0, 0)) def _draw_bubble(self, c): @@ -801,29 +803,28 @@ class Game: def _render_hud(self, c): c.blit(self.hud, (0, 0)) - pix.draw_text(c, 14, 4, "SCORE", C["dim"], 1) - pix.draw_text(c, 14, 13, "{:,}".format(self.score), C["white"], 2) - pix.draw_text(c, 170, 4, "BEST", C["dim"], 1) + pix.draw_text(c, 10, 13, "{:,}".format(self.score), C["white"], 2) best = max(self.hi, self.score) - pix.draw_text(c, 170, 13, "{:,}".format(best), C["gold"], 2) - # lives hearts - for i in range(self.lives): - draw_heart(c, 336 + i * 14, 14, 2, C["heart"]) - pix.draw_text(c, 336, 30, "LIVES", C["dim"], 1) - # stars progress - pix.draw_text(c, 14, 32, "STARS", C["dim"], 1) + pix.draw_text(c, CV_W - 10, 13, "{:,}".format(best), C["gold"], 2, align="right") + # lives hearts (cap the icon row, spill into a +N text) + hearts = self.lives + for i in range(min(hearts, 5)): + draw_heart(c, 10 + i * 14, 42, 2, C["heart"]) + if hearts > 5: + pix.draw_text(c, 10 + 5 * 14 + 2, 43, "+%d" % (hearts - 5), C["pink"], 1) + # star progress for i, st in enumerate(self.table.stars): - x = 52 + i * 18 - draw_star(c, x, 37, 5 if st["lit"] else 4, + x = 120 + i * 16 + draw_star(c, x + 4, 42, 5 if st["lit"] else 4, C["gold"] if st["lit"] else (90, 78, 150)) # fever indicator + bar if self.fever > 0: frac = max(0.0, self.fever / (60 * 20)) - pygame.draw.rect(c, (60, 46, 110), (260, 30, 70, 6)) - pygame.draw.rect(c, (255, 150, 200), (260, 30, int(70 * frac), 6)) - pix.draw_text(c, 260, 38, "FEVER x2", C["gold"], 1) + pix.draw_text(c, 205, 33, "FEVER", C["gold"], 1) + pygame.draw.rect(c, (60, 46, 110), (205, 44, 90, 5)) + pygame.draw.rect(c, (255, 150, 200), (205, 44, int(90 * frac), 5)) if self.paused: - pix.draw_text(c, CV_W - 40, 40, "PAUSED", C["dim"], 1, align="right") + pix.draw_text(c, CV_W - 10, 40, "PAUSED", C["dim"], 1, align="right") # ------------------------------------------------------------------ title def _title_update(self): @@ -837,12 +838,12 @@ class Game: def _render_title(self, c): c.blit(self.bg, (0, 0)) - # ringed planet - pygame.draw.circle(c, (150, 110, 220), (90, 70), 24) - pygame.draw.circle(c, (190, 160, 245), (90, 70), 24, 2) - pygame.draw.ellipse(c, (255, 200, 220), (58, 64, 64, 18), 2) - pygame.draw.circle(c, (110, 90, 200), (300, 320), 10) - pygame.draw.circle(c, (110, 90, 200), (300, 320), 10, 1) + # ringed planet top-left, little moon bottom-right + pygame.draw.circle(c, (150, 110, 220), (62, 80), 22) + pygame.draw.circle(c, (190, 160, 245), (62, 80), 22, 2) + pygame.draw.ellipse(c, (255, 200, 220), (34, 74, 56, 16), 2) + pygame.draw.circle(c, (110, 90, 200), (310, 380), 12) + pygame.draw.circle(c, (150, 120, 230), (310, 380), 12, 1) # twinkling stars for sx, sy, tw, ph in self.stars_bg: if (self.frame + ph) % 70 < 6: @@ -852,57 +853,55 @@ class Game: c.fill(p["color"], (int(p["x"]), int(p["y"]), 2, 2)) # logo - words = [("COSMIC", 3, 36, C["pink"]), ("CANDY", 3, 62, C["gold"]), - ("PINBALL", 2, 108, C["mint"])] - y = 40 - for txt, sc, _, col in words: + y = 44 + for txt, sc, col in (("COSMIC", 3, C["pink"]), ("CANDY", 3, C["gold"]), + ("PINBALL", 2, C["mint"])): tw = pix.text_width(txt, sc) x = CV_W // 2 - tw // 2 for i, ch in enumerate(txt): - col2 = col - pix.draw_text(c, x, y, ch, col2, sc, shadow=(20, 12, 50)) + pix.draw_text(c, x, y, ch, col, sc, shadow=(20, 12, 50)) x += (5 + 1) * sc y += (5 + 2) * sc # little shooting star occasionally if self.frame % 90 < 24: t0 = self.frame % 90 - sx = 330 + t0 * 3 - sy = 40 + t0 * 1.5 + sx = 250 + t0 * 2 + sy = 130 + t0 pygame.draw.line(c, (255, 255, 255), (int(sx), int(sy)), (int(sx - 8), int(sy - 4)), 1) # cat - draw_body(c, CV_W // 2 - 26, 180, 2) - draw_cat(c, CV_W // 2 - 26, 168, 2, "happy", self.frame) + draw_body(c, CV_W // 2 - 24, 208, 2) + draw_cat(c, CV_W // 2 - 24, 196, 2, "happy", self.frame) # instructions panel - pygame.draw.rect(c, (22, 15, 56), (70, 240, CV_W - 140, 74), border_radius=8) - pygame.draw.rect(c, C["rail"], (70, 240, CV_W - 140, 74), 2, border_radius=8) + pygame.draw.rect(c, (22, 15, 56), (30, 262, CV_W - 60, 84), border_radius=8) + pygame.draw.rect(c, C["rail"], (30, 262, CV_W - 60, 84), 2, border_radius=8) lines = [ ("LEFT / RIGHT ARROWS = FLIPPERS", C["flipper"]), ("HOLD SPACE OR UP = CHARGE CANNON", C["gold"]), ("M MUTE R RESTART P PAUSE", C["dim"]), ] for i, (txt, col) in enumerate(lines): - pix.draw_text(c, CV_W // 2, 250 + i * 20, txt, col, 1, align="center") + pix.draw_text(c, CV_W // 2, 272 + i * 20, txt, col, 1, align="center") if (self.frame // 30) % 2 == 0: - pix.draw_text(c, CV_W // 2, 325, "PRESS ENTER TO START", C["white"], 2, align="center") + pix.draw_text(c, CV_W // 2, 378, "PRESS ENTER TO START", C["white"], 2, align="center") # ------------------------------------------------------------------ game over def _render_over(self, c): self._render_play(c) self.overlay.fill((14, 9, 40, 215)) c.blit(self.overlay, (0, 0)) - pix.draw_text(c, CV_W // 2, 60, "GAME OVER", C["pink"], 3, align="center") - pix.draw_text(c, CV_W // 2, 105, "SCORE", C["dim"], 1, align="center") - pix.draw_text(c, CV_W // 2, 115, "{:,}".format(self.score), C["white"], 3, align="center") - pix.draw_text(c, CV_W // 2, 160, "BEST", C["dim"], 1, align="center") - pix.draw_text(c, CV_W // 2, 170, "{:,}".format(self.hi), C["gold"], 2, align="center") + pix.draw_text(c, CV_W // 2, 70, "GAME OVER", C["pink"], 3, align="center") + pix.draw_text(c, CV_W // 2, 128, "SCORE", C["dim"], 1, align="center") + pix.draw_text(c, CV_W // 2, 140, "{:,}".format(self.score), C["white"], 3, align="center") + pix.draw_text(c, CV_W // 2, 195, "BEST", C["dim"], 1, align="center") + pix.draw_text(c, CV_W // 2, 207, "{:,}".format(self.hi), C["gold"], 2, align="center") if self.new_hi and (self.frame // 20) % 2 == 0: - pix.draw_text(c, CV_W // 2, 205, "NEW HI-SCORE!", C["gold"], 1, align="center") - draw_body(c, CV_W // 2 - 20, 240, 2) - draw_cat(c, CV_W // 2 - 20, 226, 2, "sad", self.frame) + pix.draw_text(c, CV_W // 2, 250, "NEW HI-SCORE!", C["gold"], 1, align="center") + draw_body(c, CV_W // 2 - 20, 310, 2) + draw_cat(c, CV_W // 2 - 20, 296, 2, "sad", self.frame) if (self.frame // 30) % 2 == 0: - pix.draw_text(c, CV_W // 2, 320, "PRESS ENTER TO PLAY AGAIN", C["white"], 1, align="center") + pix.draw_text(c, CV_W // 2, 400, "PRESS ENTER TO PLAY AGAIN", C["white"], 1, align="center") # ------------------------------------------------------------------ input def event(self, ev): diff --git a/physics.py b/physics.py index a9c0ec3..c7fb24f 100644 --- a/physics.py +++ b/physics.py @@ -1,60 +1,65 @@ """Pinball physics: pure logic, no rendering. Testable headless. -Coordinate system: y grows downward. 480x360 virtual canvas. +Coordinate system: y grows downward. 360x480 portrait virtual canvas. """ import math import random # --------------------------------------------------------------------------- -# Playfield geometry +# Playfield geometry (portrait - like a proper pinball machine) # --------------------------------------------------------------------------- -CV_W, CV_H = 480, 360 -PLAY_L, PLAY_R, PLAY_T, PLAY_B = 20.0, 460.0, 52.0, 346.0 +CV_W, CV_H = 360, 480 +PLAY_L, PLAY_R, PLAY_T, PLAY_B = 26.0, 334.0, 60.0, 448.0 GRAVITY = 0.24 # px/frame^2 MAX_SPEED = 13.0 # speed cap (prevents tunneling; ~300px of rise) RESTITUTION = 0.42 BALL_R = 7.0 -FLIP_LEN = 154.0 -FLIP_REST = math.radians(6.0) # tips slightly lowered toward center drain -FLIP_UP = -math.radians(52.0) # fired position -FLIP_PIVOT_L = (62.0, 338.0) -FLIP_PIVOT_R = (418.0, 338.0) -FLIP_RADIUS = 5.5 +# regular-sized flippers (about a quarter of the playfield width each), +# pivoting near the lower corners with their tips meeting at the centre +FLIP_LEN = 82.0 +FLIP_REST = math.radians(5.0) # tips slightly lowered toward the drain +FLIP_UP = -math.radians(48.0) # fired position +FLIP_PIVOT_L = (78.0, 430.0) +FLIP_PIVOT_R = (282.0, 430.0) +FLIP_RADIUS = 5.0 FLIP_SNAP_UP = 0.36 # rad/frame while firing FLIP_SNAP_DOWN = 0.05 # gentle return speed FLIP_BOUNCE = 0.12 # elements BUMPERS = [ - dict(pos=(150.0, 180.0), r=23.0, color="pink"), - dict(pos=(330.0, 180.0), r=23.0, color="mint"), - dict(pos=(240.0, 240.0), r=23.0, color="lemon"), + dict(pos=(102.0, 205.0), r=22.0, color="pink"), + dict(pos=(258.0, 205.0), r=22.0, color="mint"), + dict(pos=(180.0, 300.0), r=22.0, color="lemon"), ] SLING_KICK_L = (0.60, -0.80) SLING_KICK_R = (-0.60, -0.80) SLINGS = [ - # upper slingshots just below the two outer bumpers - kick toward centre - dict(pos=(150.0, 282.0), r=11.0, kick=SLING_KICK_L), - dict(pos=(330.0, 282.0), r=11.0, kick=SLING_KICK_R), - # guard posts at the outlane mouths: wall-huggers get bounced back into - # play instead of silently draining - the centre gap is the real risk - dict(pos=(44.0, 314.0), r=12.0, kick=(0.80, -0.60)), - dict(pos=(436.0, 314.0), r=12.0, kick=(-0.80, -0.60)), + # slingshots in the lower side areas - kick toward the centre + dict(pos=(92.0, 336.0), r=11.0, kick=SLING_KICK_L), + dict(pos=(268.0, 336.0), r=11.0, kick=SLING_KICK_R), + # guard posts above the outlane mouths: wall-huggers bounce back into + # play - the centre gap is the real risk + dict(pos=(52.0, 408.0), r=13.0, kick=(0.80, -0.60)), + dict(pos=(308.0, 408.0), r=13.0, kick=(-0.80, -0.60)), + # little floor-level posts that seal the outlane mouths + dict(pos=(46.0, 432.0), r=9.0, kick=(0.72, -0.69)), + dict(pos=(314.0, 432.0), r=9.0, kick=(-0.72, -0.69)), ] -STARS = [(168.0, 92.0), (240.0, 92.0), (312.0, 92.0)] +STARS = [(120.0, 96.0), (180.0, 96.0), (240.0, 96.0)] -CANNON = (440.0, 152.0) +CANNON = (300.0, 170.0) CANNON_R = 17.0 -LAUNCH_DIR = (-0.94, -0.34) +LAUNCH_DIR = (-0.93, -0.36) LAUNCH_MIN, LAUNCH_MAX = 7.0, 13.5 -DRAIN_Y = 362.0 -CENTER_HOLE = (200.0, 280.0) -OUTLANE_L = 46.0 -OUTLANE_R = 434.0 +DRAIN_Y = 458.0 +CENTER_HOLE = (150.0, 210.0) +OUTLANE_L = 44.0 +OUTLANE_R = 316.0 def clamp(v, lo, hi): diff --git a/probe.py b/probe.py index 16d713f..5cc03a8 100644 --- a/probe.py +++ b/probe.py @@ -1,5 +1,5 @@ """Pixel-probe the rendered frames for layout sanity (since art can't be -viewed directly in this session).""" +viewed directly in this session). Portrait 360x480.""" import os @@ -35,43 +35,45 @@ def main(): st["lit"] = True g.table.ball_live = True g.pending = "live" - g.table.ball.x, g.table.ball.y = 240, 300 + g.table.ball.x, g.table.ball.y = 180, 330 g.table.ball.vx, g.table.ball.vy = 0, 3 for _ in range(3): g.update() g.render() s = g.canvas checks = [] - # HUD white score digits around (14..100, 13..26) - checks.append(("HUD score digits", count_px(s, (14, 13, 110, 15), - lambda p: near(p, (255, 255, 255), 40)) > 25)) - # HUD gold BEST digits near (170..300, 13..26) - checks.append(("HUD gold best", count_px(s, (170, 13, 150, 15), + # HUD white score digits around (10..120, 13..26) + checks.append(("HUD score digits", count_px(s, (10, 13, 110, 15), + lambda p: near(p, (255, 255, 255), 40)) > 20)) + # HUD gold BEST digits near right (250..350, 13..26) + checks.append(("HUD gold best", count_px(s, (230, 13, 120, 15), + lambda p: near(p, C["gold"], 55)) > 10)) + # hearts red at (8..90, 40..50) + checks.append(("hearts", count_px(s, (8, 40, 90, 12), + lambda p: near(p, C["heart"], 50)) > 15)) + # three gold stars in HUD at (118..175, 36..48) + checks.append(("HUD stars lit", count_px(s, (116, 36, 60, 14), lambda p: near(p, C["gold"], 55)) > 10)) - # hearts red at (336..390, 14..30) - checks.append(("hearts", count_px(s, (336, 12, 60, 22), - lambda p: near(p, C["heart"], 50)) > 20)) - # three gold stars in HUD at (52..112, 30..44) - checks.append(("HUD stars lit", count_px(s, (44, 28, 90, 18), - lambda p: near(p, C["gold"], 55)) > 15)) # playfield interior present (not black) - checks.append(("field fill", near(s.get_at((240, 100)), C["field"], 70))) - # pink rail present on left edge inside play area - rail_ok = count_px(s, (14, 70, 12, 20), lambda p: near(p, C["rail"], 60)) > 10 + checks.append(("field fill", near(s.get_at((180, 120)), C["field"], 70))) + # left rail pink present inside play area + rail_ok = count_px(s, (23, 100, 8, 20), lambda p: near(p, C["rail"], 60)) > 5 checks.append(("left rail pink", rail_ok)) - # bumper pink at (150,180) - checks.append(("bumper pink", near(s.get_at((150, 178)), C["pink"], 55))) - # bumper lemon at (240,240) - checks.append(("bumper lemon", near(s.get_at((240, 238)), C["lemon"], 55))) - # pit dark center at (240, 352) - checks.append(("pit center", near(s.get_at((240, 353)), C["pit"], 30))) - # flipper blue present around (240, 320) - checks.append(("flipper blue", count_px(s, (100, 300, 280, 45), - lambda p: near(p, C["flipper"], 55)) > 30)) - # cannon purple body at (440,152) - checks.append(("cannon", near(s.get_at((440, 152)), (150, 100, 220), 70))) - # ball near (240,300) is white-ish - checks.append(("ball", count_px(s, (230, 290, 20, 20), + # bumper bodies (sample the rim, away from the face) + checks.append(("bumper pink", near(s.get_at((82, 205)), C["pink"], 55))) + checks.append(("bumper mint", count_px(s, (230, 190, 60, 40), + lambda p: near(p, C["mint"], 55) or + near(p, C["mint_l"], 55)) > 80)) + checks.append(("bumper lemon", near(s.get_at((160, 300)), C["lemon"], 55))) + # pit dark center at (180, 455) + checks.append(("pit center", near(s.get_at((180, 455)), C["pit"], 30))) + # flippers present on BOTH sides (pivot ~78 and ~282) + fl = count_px(s, (60, 395, 260, 50), lambda p: near(p, C["flipper"], 55)) + checks.append(("flipper blue both sides", fl > 60)) + # cannon purple body around (300,170) + checks.append(("cannon", near(s.get_at((300, 170)), (150, 100, 220), 70))) + # ball near (180,330) is white-ish + checks.append(("ball", count_px(s, (170, 320, 20, 20), lambda p: near(p, (245, 250, 255), 40)) > 5)) ok = True for name, passed in checks: diff --git a/shots.py b/shots.py index d026e86..a5f62d6 100644 --- a/shots.py +++ b/shots.py @@ -49,9 +49,9 @@ def main(): t.reset_ball_in_cannon() g.pending = "live" t.ball_live = True - t.ball.x, t.ball.y = 415, 60 + t.ball.x, t.ball.y = 295, 75 t.ball.vx, t.ball.vy = -2.2, 3.1 - for _ in range(70): + for _ in range(80): g.keys["fire"] = False g.update() save(g, "3_play", out) @@ -60,13 +60,13 @@ def main(): g.combo = 4 g.combo_t = 150 g.fever = 60 * 12 - g.add_msg("+500", 150, 150, (255, 214, 92)) + g.add_msg("+500", 180, 150, (255, 214, 92)) for st in t.stars: st["lit"] = True b = t.bumpers[0] b["squash"] = 4 b["face_t"] = 0.0 - g.particles.append(dict(x=150, y=150, life=14, kind="ring", color=(255, 255, 255))) + g.particles.append(dict(x=180, y=150, life=14, kind="ring", color=(255, 255, 255))) g.bubble = ["CANDY FEVER!", 90, "star"] save(g, "4_fever", out) diff --git a/shots/1_title.png b/shots/1_title.png index 23e4cb4..a0f0447 100644 Binary files a/shots/1_title.png and b/shots/1_title.png differ diff --git a/shots/1_title_2x.png b/shots/1_title_2x.png index a57fa5c..d682bc8 100644 Binary files a/shots/1_title_2x.png and b/shots/1_title_2x.png differ diff --git a/shots/2_serve.png b/shots/2_serve.png index 85df4e3..12fdd9c 100644 Binary files a/shots/2_serve.png and b/shots/2_serve.png differ diff --git a/shots/2_serve_2x.png b/shots/2_serve_2x.png index 1a7efd0..0256317 100644 Binary files a/shots/2_serve_2x.png and b/shots/2_serve_2x.png differ diff --git a/shots/3_play.png b/shots/3_play.png index 0db34f4..64c0ffd 100644 Binary files a/shots/3_play.png and b/shots/3_play.png differ diff --git a/shots/3_play_2x.png b/shots/3_play_2x.png index a3d27c6..22c3d74 100644 Binary files a/shots/3_play_2x.png and b/shots/3_play_2x.png differ diff --git a/shots/4_fever.png b/shots/4_fever.png index 8b0d386..025cf19 100644 Binary files a/shots/4_fever.png and b/shots/4_fever.png differ diff --git a/shots/4_fever_2x.png b/shots/4_fever_2x.png index 08e1a73..fc7d98f 100644 Binary files a/shots/4_fever_2x.png and b/shots/4_fever_2x.png differ diff --git a/shots/5_drain.png b/shots/5_drain.png index bad8ec8..169f39d 100644 Binary files a/shots/5_drain.png and b/shots/5_drain.png differ diff --git a/shots/5_drain_2x.png b/shots/5_drain_2x.png index d0f27b2..43ebd9f 100644 Binary files a/shots/5_drain_2x.png and b/shots/5_drain_2x.png differ diff --git a/shots/6_gameover.png b/shots/6_gameover.png index c3cb933..6f9ce81 100644 Binary files a/shots/6_gameover.png and b/shots/6_gameover.png differ diff --git a/shots/6_gameover_2x.png b/shots/6_gameover_2x.png index 3998bd2..9c7ae0b 100644 Binary files a/shots/6_gameover_2x.png and b/shots/6_gameover_2x.png differ