diff --git a/game.py b/game.py index 27e021c..eaafd91 100644 --- a/game.py +++ b/game.py @@ -525,8 +525,8 @@ class Game: if self.charging: self.was_charging = True self.charge = min(1.0, self.charge + 0.022) - if self.frame % 5 == 0: - self.sfx.play("tick", vol=0.3 + self.charge * 0.5) + if self.frame % 6 == 0: + self.sfx.play("tick", vol=0.16 + self.charge * 0.25) if self.charge >= 1.0: self._fire() else: @@ -628,9 +628,9 @@ class Game: elif e.kind == "nudge": self.sfx.play("tick", vol=0.2) elif e.kind == "wall_hit": - if self.wall_cool <= 0 and e.data > 9.5: - self.sfx.play("wall", vol=min(0.5, (e.data - 9) / 5)) - self.wall_cool = 5 + if self.wall_cool <= 0 and e.data > 11.0: + self.sfx.play("wall", vol=min(0.4, (e.data - 11) / 6.0)) + self.wall_cool = 9 elif e.kind == "flipper_hit": if self.rng.random() < 0.3: self.sfx.play("tick", vol=0.25) diff --git a/physics.py b/physics.py index 4a927ba..a9c0ec3 100644 --- a/physics.py +++ b/physics.py @@ -17,7 +17,7 @@ MAX_SPEED = 13.0 # speed cap (prevents tunneling; ~300px of rise) RESTITUTION = 0.42 BALL_R = 7.0 -FLIP_LEN = 170.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) @@ -36,10 +36,11 @@ BUMPERS = [ SLING_KICK_L = (0.60, -0.80) SLING_KICK_R = (-0.60, -0.80) SLINGS = [ - # upper slingshots by the lower bumpers - kick toward table center + # 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 - protect the corners + # 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)), ] @@ -137,7 +138,7 @@ class Table: for b in BUMPERS] self.slings = [ dict(x=s["pos"][0], y=s["pos"][1], r=s["r"], cool=0, flash=0, - kick=s["kick"], guard=(i >= 2)) + kick=s["kick"]) for i, s in enumerate(SLINGS)] self.stars = [dict(x=x, y=y, r=13.0, lit=False, cool=0, pulse=0) for x, y in STARS] diff --git a/sfx.py b/sfx.py index 22e15d1..c8ceca5 100644 --- a/sfx.py +++ b/sfx.py @@ -113,9 +113,9 @@ class Synth: # ---- all effects ----------------------------------------------------- def _build_all(self): - self._snd("flipL", self.tone(180, 0.07, 0.5, "square", slide=0.7)) - self._snd("flipR", self.tone(210, 0.07, 0.5, "square", slide=0.7)) - self._snd("wall", self.tone(90, 0.045, 0.18, "square", duty=0.3)) + self._snd("flipL", self.tone(180, 0.07, 0.4, "square", slide=0.7)) + self._snd("flipR", self.tone(210, 0.07, 0.4, "square", slide=0.7)) + self._snd("wall", self.tone(140, 0.05, 0.09, "tri")) self._snd("launch", self._mix( self.noise(0.28, 0.5, lp=0.25), self.tone(160, 0.3, 0.35, "saw", slide=1.9))) @@ -162,43 +162,70 @@ class Synth: self._build_music() - def _build_music(self): - """Two short generative loops: a dreamy menu loop and a bouncy play loop.""" - # menu loop: soft arpeggio C - Am - F - G - chords = [["C", "E", "G"], ["A", "C", "E"], ["F", "A", "C"], ["G", "B", "D"]] - bass = [("C", 2), ("A", 2), ("F", 2), ("G", 2)] - beat = 0.26 - parts = [] - for ci in range(4): - n = int(SR * beat * 2) - parts.append(self.tone(freq(bass[ci][0], bass[ci][1]), beat * 2.1, 0.22, "tri")) - # melodic ripple over the chord - for k in range(4): - note = chords[ci][k % 3] - f0 = freq(note, 4 if k < 2 else 5) - parts.append(self.tone(f0, beat * 0.8, 0.14, "square", duty=0.4)) - parts.append(array.array("h", [0]) * int(SR * beat * 0.2)) - self._menu_music = self._mix(*parts) + # ------------------------------------------------------------------ music + def _music_buffer(self, tracks, total_s): + """Render time-placed notes (start_s, note, octave, dur_s, vol, shape) + into one softly-mixed, normalized buffer.""" + n = int(SR * total_s) + buf = [0] * n + for (t0, nm, oc, dur, vol, shape) in tracks: + samples = self.tone(freq(nm, oc), dur, vol, shape) + off = int(SR * t0) + for i, v in enumerate(samples): + j = off + i + if j < n: + buf[j] = max(-32767, min(32767, buf[j] + v)) + peak = max(1, max(abs(v) for v in buf)) + sc = 28000.0 / peak if peak > 28000 else 1.0 + return array.array("h", (int(v * sc) for v in buf)) - # play loop: bouncy 8th-note line C-C-G-G-A-A-G - line = [("C", 4), ("C", 4), ("G", 4), ("G", 4), ("A", 4), ("A", 4), ("G", 4), ("E", 4)] - bassp = [("C", 2), ("C", 2), ("G", 2), ("G", 2), ("A", 2), ("A", 2), ("E", 2), ("E", 2)] - step = 0.16 - parts = [] + def _build_music(self): + """Two short generative loops, deliberately sparse & soft: a calm menu + loop and a gentle bouncy play loop. (Square waves and hats are used in + SFX only - never in the music - to keep it from getting harsh.)""" + + # ---- menu: dreamy, mostly rests, C - Am - F - G ------------------------ + seq = [ + (("C", 2), ["C", "E", "G", "D"], "E"), + (("A", 2), ["A", "C", "E", "B"], "C"), + (("F", 2), ["F", "A", "C", "G"], "A"), + (("G", 2), ["G", "B", "D", "A"], "B"), + ] + tracks = [] + CH = 1.8 + for ci, (bass, arp, twinkle) in enumerate(seq): + t = ci * CH + tracks.append((t, bass[0], bass[1], CH * 0.72, 0.12, "tri")) # bass + for k in range(4): # arpeggio + oc = 4 if k < 2 else 5 + tracks.append((t + 0.35 + k * 0.26, arp[k], oc, 0.24, 0.08, "tri")) + tracks.append((t + 1.3, twinkle, 5, 0.55, 0.045, "tri")) # twinkle + menu_total = 4 * CH + 0.8 + self._menu_music = self._music_buffer(tracks, menu_total) + + # ---- play: bouncy but gentle, 16 eighth-notes @ ~0.30s ---------------- + step = 0.30 + bass_seq = [("C", 2), ("C", 2), ("G", 2), ("G", 2), + ("A", 2), ("A", 2), ("F", 2), ("G", 2), + ("C", 2), ("C", 2), ("G", 2), ("G", 2), + ("A", 2), ("A", 2), ("G", 2), ("C", 2)] + lead_seq = [None, None, ("E", 4), None, ("G", 4), None, ("E", 4), None, + ("D", 4), None, ("F", 4), None, ("A", 4), None, ("G", 4), None] + tracks = [] for i in range(16): - nm, oc = line[i % 8] - bn, bo = bassp[i % 8] - parts.append(self.tone(freq(bn, bo), step * 1.8, 0.28, "tri")) - parts.append(self.tone(freq(nm, oc), step * 1.3, 0.18, "square", duty=0.4)) - if i % 2 == 1: - parts.append(self.noise(0.03, 0.10, lp=0.15)) - else: - parts.append(array.array("h", [0]) * int(SR * step * 0.3)) - self._play_music = self._mix(*parts) + t = i * step + if i % 2 == 0: # bass on the beat + bn, bo = bass_seq[i] + tracks.append((t, bn, bo, step * 1.25, 0.15, "tri")) + if lead_seq[i]: # light melody + nm, oc = lead_seq[i] + tracks.append((t + 0.02, nm, oc, step * 0.85, 0.085, "tri")) + play_total = 16 * step + 1.2 + self._play_music = self._music_buffer(tracks, play_total) self._menu = pygame.mixer.Sound(buffer=self._menu_music.tobytes()) self._play = pygame.mixer.Sound(buffer=self._play_music.tobytes()) - self._menu.set_volume(0.5) - self._play.set_volume(0.45) + self._menu.set_volume(0.40) + self._play.set_volume(0.30) # ---- playback -------------------------------------------------------- def play(self, name, vol=1.0, pitch_var=0): diff --git a/shots/1_title.png b/shots/1_title.png index 5d24fd8..23e4cb4 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 cc61d25..a57fa5c 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 292e6f5..85df4e3 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 b91af54..1a7efd0 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 0ac7f61..0db34f4 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 a3f8377..a3d27c6 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 8b037d3..8b0d386 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 c4edd41..08e1a73 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 885e54c..bad8ec8 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 0d70199..d0f27b2 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 93b55a6..c3cb933 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 af72a2f..3998bd2 100644 Binary files a/shots/6_gameover_2x.png and b/shots/6_gameover_2x.png differ