bouncy fpga game
www.youtube.com/watch?v=IiLWF3GbV7w
1#!/usr/bin/env python3
2"""
3Bouncy Game Visualizer — mirrors the VHDL physics exactly.
4
5Controls: WASD to move/jump, R to reset, T to toggle trail, Q to quit.
6
7Prerequisites:
8 pip install pygame
9
10Usage:
11 python3 visualizer.py
12"""
13
14import math
15import pygame
16import sys
17
18# --- Screen / VGA constants (match VHDL) ---
19SCREEN_W = 640
20SCREEN_H = 480
21FPS = 60
22
23# --- Physics constants (match VHDL exactly) ---
24GRAVITY = 1
25IMPULSE = 3
26SLAM_TAP_FORCE = 18 # instant downward burst on S press in air
27SLAM_BOOST_CLOSE = 24 # upward boost on hold-slam landing, close range
28SLAM_BOOST_MED = 16 # upward boost, medium range
29SLAM_BOOST_FAR = 8 # upward boost, far range
30SLAM_CLOSE_THR = 80 # pixels: "close to surface" threshold
31SLAM_MED_THR = 200 # pixels: "medium distance" threshold
32AIR_CONTROL = 2
33JUMP_FORCE = 14 # initial jump force at zero velocity (scales down with |vel_y|)
34JUMP_VEL_SCALE = 12 # velocity magnitude at which initial jump force is halved
35JUMP_MIN_FORCE = 4 # floor for inverse-scaled initial jump
36JUMP_BOUNCE = 6 # fixed additive boost on each ground contact while W held (trampoline)
37MAX_VEL_X = 32
38SIZE = 7
39BOUNCE_SHIFT = 3 # energy loss = vel >> 3 (keep 87.5%)
40
41# --- Bounds (match VHDL) ---
42GROUND = 440
43CEILING = 16
44LEFT_WALL = 8
45RIGHT_WALL = 631
46GROUND_TOP = 448
47CEIL_BOT = 8
48
49# --- Obstacles: (left, top, right, bottom) matching VHDL ---
50OBSTACLES = [
51 (60, 370, 180, 386), # Low platform left
52 (250, 300, 390, 316), # Middle floating platform
53 (440, 200, 580, 216), # High platform right
54 (140, 120, 200, 150), # Small block upper-left
55]
56
57# --- Colors (1-bit RGB) ---
58COLOR_SKY = (0, 0, 0)
59COLOR_GROUND = (0, 255, 0)
60COLOR_CEIL = (0, 255, 0)
61COLOR_WALL = (0, 255, 255)
62COLOR_CHAR = (255, 0, 0)
63COLOR_OBS = (255, 255, 0)
64
65# --- 10-bit signed helpers ---
66MASK = 0x3FF
67
68def to_signed(val):
69 val = val & MASK
70 return val - 1024 if val >= 512 else val
71
72def to_unsigned(val):
73 return val & MASK
74
75def negate(v):
76 return ((~v) + 1) & MASK
77
78
79def main():
80 pygame.init()
81 screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
82 pygame.display.set_caption("Bouncy Game — VHDL Physics Preview")
83 clock = pygame.time.Clock()
84
85 char_x = 100
86 char_y = 200
87 vel_x = to_unsigned(0)
88 vel_y = to_unsigned(0)
89 squish = 0
90 squish_h = False # False = vertical squish (floor/ceil), True = horizontal squish (walls)
91 on_ground = False
92 jump_pressed = False
93 slam_held = False
94 slam_tap = False # S pressed and released before landing — suppress bounce
95 slam_start_y = 0
96 prev_key_s = False
97
98 keys_held = {'w': False, 'a': False, 's': False, 'd': False}
99 trail = []
100 show_trail = True
101 show_vector = False
102 font = pygame.font.SysFont("monospace", 14)
103
104 running = True
105 while running:
106 for event in pygame.event.get():
107 if event.type == pygame.QUIT:
108 running = False
109 elif event.type == pygame.KEYDOWN:
110 if event.key == pygame.K_w: keys_held['w'] = True
111 elif event.key == pygame.K_a: keys_held['a'] = True
112 elif event.key == pygame.K_s: keys_held['s'] = True
113 elif event.key == pygame.K_d: keys_held['d'] = True
114 elif event.key == pygame.K_q: running = False
115 elif event.key == pygame.K_t:
116 show_trail = not show_trail; trail.clear()
117 elif event.key == pygame.K_v:
118 show_vector = not show_vector
119 elif event.key == pygame.K_r:
120 char_x, char_y = 100, 200
121 vel_x = vel_y = to_unsigned(0)
122 squish = 0; squish_h = False; on_ground = False; jump_pressed = False
123 slam_held = False; slam_tap = False; slam_start_y = 0; prev_key_s = False
124 trail.clear()
125 elif event.type == pygame.KEYUP:
126 if event.key == pygame.K_w: keys_held['w'] = False
127 elif event.key == pygame.K_a: keys_held['a'] = False
128 elif event.key == pygame.K_s: keys_held['s'] = False
129 elif event.key == pygame.K_d: keys_held['d'] = False
130
131 # =============================================================
132 # Physics — matches VHDL exactly
133 # =============================================================
134 vx = vel_x
135 vy = vel_y
136 bounced = False
137 bounce_wall = False
138 bounce_speed = 0
139 grounded = False
140
141 # Jump latch
142 if not keys_held['w']:
143 jump_pressed = False
144
145 # Compute scaled jump force: full force at zero velocity, tapers off as speed rises
146 def scaled_jump():
147 speed = abs(to_signed(vel_y))
148 force = JUMP_FORCE * JUMP_VEL_SCALE / (JUMP_VEL_SCALE + speed)
149 return max(int(force), JUMP_MIN_FORCE)
150
151 # First press on ground: full jump (elif prevents double-apply on same frame)
152 if keys_held['w'] and not jump_pressed and on_ground:
153 vy = (vy - scaled_jump()) & MASK
154 jump_pressed = True
155 elif keys_held['w'] and jump_pressed and on_ground:
156 # Holding W while bouncing: fixed boost so trampoline accumulates each contact
157 vy = (vy - JUMP_BOUNCE) & MASK
158
159 # S slam: rising edge in air = tap burst downward; hold to get landing boost
160 if keys_held['s'] and not prev_key_s and not on_ground:
161 slam_start_y = char_y
162 slam_held = True
163 slam_tap = True
164 vy = (vy + SLAM_TAP_FORCE) & MASK
165 if not keys_held['s'] and slam_held:
166 # S released before landing — mark as tap, no hold boost
167 slam_held = False
168 prev_key_s = keys_held['s']
169
170 # A/D
171 if keys_held['a']:
172 if on_ground:
173 vx = (vx - IMPULSE) & MASK
174 else:
175 vx = (vx - AIR_CONTROL) & MASK
176
177 if keys_held['d']:
178 if on_ground:
179 vx = (vx + IMPULSE) & MASK
180 else:
181 vx = (vx + AIR_CONTROL) & MASK
182
183 # Gravity
184 vy = (vy + GRAVITY) & MASK
185
186 # Friction: vel -= vel/4, min 1
187 svx = to_signed(vx)
188 if svx > 0:
189 drag = svx >> 2
190 if drag == 0: drag = 1
191 svx -= drag
192 elif svx < 0:
193 drag = (-svx) >> 2
194 if drag == 0: drag = 1
195 svx += drag
196 vx = to_unsigned(svx)
197
198 # Clamp X
199 svx = to_signed(vx)
200 if svx > MAX_VEL_X: svx = MAX_VEL_X
201 elif svx < -MAX_VEL_X: svx = -MAX_VEL_X
202 vx = to_unsigned(svx)
203
204 # Clamp Y
205 svy = to_signed(vy)
206 if svy > 80: svy = 80
207 elif svy < -100: svy = -100
208 vy = to_unsigned(svy)
209
210 # Update position
211 px = char_x + to_signed(vx)
212 py = char_y + to_signed(vy)
213
214 # --- Ground bounce ---
215 if py >= GROUND:
216 py = GROUND
217 bounced = True; grounded = True
218 bounce_speed = abs(to_signed(vy))
219 vy = negate(vy)
220 svy = to_signed(vy)
221 if svy < -1:
222 svy += (-svy) >> BOUNCE_SHIFT
223 if abs(svy) < 2:
224 svy = 0
225 vy = to_unsigned(svy)
226
227 # --- Ceiling bounce ---
228 if py <= CEILING:
229 py = CEILING
230 bounced = True
231 bounce_speed = abs(to_signed(vy))
232 vy = negate(vy)
233 svy = to_signed(vy)
234 if abs(svy) > 1:
235 svy_abs = abs(svy)
236 loss = svy_abs >> BOUNCE_SHIFT
237 if svy > 0: svy -= loss
238 else: svy += loss
239 vy = to_unsigned(svy)
240
241 # --- Obstacle collisions (swept AABB — mirrors VHDL swept check) ---
242 prev_top = char_y - SIZE
243 prev_bot = char_y + SIZE
244 prev_left = char_x - SIZE
245 prev_right = char_x + SIZE
246
247 for (ol, ot, orr, ob) in OBSTACLES:
248 c_left = px - SIZE; c_right = px + SIZE
249 c_top = py - SIZE; c_bot = py + SIZE
250
251 if to_signed(vx) >= 0:
252 x_overlap = c_right >= ol and prev_left < orr
253 else:
254 x_overlap = prev_right > ol and c_left <= orr
255 if to_signed(vy) >= 0:
256 y_overlap = c_bot >= ot and prev_top < ob
257 else:
258 y_overlap = prev_bot > ot and c_top <= ob
259
260 if x_overlap and y_overlap:
261 if prev_bot <= ot + SIZE and to_signed(vy) >= 0:
262 py = ot - SIZE; grounded = True
263 bounced = True
264 bounce_speed = abs(to_signed(vy))
265 vy = negate(vy)
266 svy = to_signed(vy)
267 if abs(svy) > 1:
268 loss = abs(svy) >> BOUNCE_SHIFT
269 svy = svy - loss if svy > 0 else svy + loss
270 if abs(svy) < 2: svy = 0
271 vy = to_unsigned(svy)
272 elif prev_top >= ob and to_signed(vy) < 0:
273 py = ob + SIZE
274 bounced = True
275 vy = negate(vy)
276 svy = to_signed(vy)
277 if svy > 1:
278 svy -= svy >> BOUNCE_SHIFT
279 vy = to_unsigned(svy)
280 elif prev_right <= ol and to_signed(vx) >= 0:
281 px = ol - SIZE
282 bounced = True; bounce_wall = True
283 bounce_speed = abs(to_signed(vx))
284 vx = negate(vx)
285 svx = to_signed(vx)
286 if svx > 1: svx -= svx >> BOUNCE_SHIFT
287 elif svx < -1: svx += (-svx) >> BOUNCE_SHIFT
288 vx = to_unsigned(svx)
289 elif prev_left >= orr and to_signed(vx) < 0:
290 px = orr + SIZE
291 bounced = True; bounce_wall = True
292 bounce_speed = abs(to_signed(vx))
293 vx = negate(vx)
294 svx = to_signed(vx)
295 if svx > 1: svx -= svx >> BOUNCE_SHIFT
296 elif svx < -1: svx += (-svx) >> BOUNCE_SHIFT
297 vx = to_unsigned(svx)
298 else:
299 svy_now = to_signed(vy)
300 if svy_now >= 0: py = ot - SIZE; grounded = True
301 else: py = ob + SIZE
302 bounced = True
303 vy = negate(vy)
304 svy = to_signed(vy)
305 if abs(svy) > 1:
306 loss = abs(svy) >> BOUNCE_SHIFT
307 svy = svy - loss if svy > 0 else svy + loss
308 vy = to_unsigned(svy)
309
310 # --- Left wall bounce ---
311 if px <= LEFT_WALL + SIZE:
312 px = LEFT_WALL + SIZE
313 bounced = True; bounce_wall = True
314 bounce_speed = abs(to_signed(vx))
315 vx = negate(vx)
316 svx = to_signed(vx)
317 if svx > 1:
318 svx -= svx >> BOUNCE_SHIFT
319 vx = to_unsigned(svx)
320
321 # --- Right wall bounce ---
322 if px >= RIGHT_WALL - SIZE:
323 px = RIGHT_WALL - SIZE
324 bounced = True; bounce_wall = True
325 bounce_speed = abs(to_signed(vx))
326 vx = negate(vx)
327 svx = to_signed(vx)
328 if svx < -1:
329 svx += (-svx) >> BOUNCE_SHIFT
330 vx = to_unsigned(svx)
331
332 # Tap slam: S was released before landing — kill bounce, plant on surface
333 if grounded and slam_tap and not slam_held:
334 vy = to_unsigned(0)
335 slam_tap = False
336
337 # Hold-slam landing boost: bigger boost when slam started closer to the surface
338 elif grounded and slam_held:
339 dist = py - slam_start_y
340 if dist < SLAM_CLOSE_THR:
341 slam_boost_val = SLAM_BOOST_CLOSE
342 elif dist < SLAM_MED_THR:
343 slam_boost_val = SLAM_BOOST_MED
344 else:
345 slam_boost_val = SLAM_BOOST_FAR
346 vy = (vy - slam_boost_val) & MASK
347 slam_held = False
348 slam_tap = False
349
350 # Commit
351 char_x = px
352 char_y = py
353 vel_x = vx
354 vel_y = vy
355
356 # On ground
357 on_ground = grounded or (py >= GROUND - 1)
358
359 # Squish — only on impacts with real velocity, not idle ground contact
360 if bounced and bounce_speed > 3:
361 squish = min(bounce_speed, 8)
362 squish_h = bounce_wall
363 elif squish > 0:
364 squish -= 1
365
366 # Trail
367 if show_trail:
368 trail.append((char_x, char_y))
369 if len(trail) > 300:
370 trail.pop(0)
371
372 # =============================================================
373 # Rendering
374 # =============================================================
375 screen.fill(COLOR_SKY)
376
377 # Ceiling
378 pygame.draw.rect(screen, COLOR_CEIL, (0, 0, SCREEN_W, CEIL_BOT))
379
380 # Ground
381 pygame.draw.rect(screen, COLOR_GROUND,
382 (0, GROUND_TOP, SCREEN_W, SCREEN_H - GROUND_TOP))
383
384 # Walls
385 pygame.draw.rect(screen, COLOR_WALL, (0, 0, LEFT_WALL, SCREEN_H))
386 pygame.draw.rect(screen, COLOR_WALL,
387 (RIGHT_WALL, 0, SCREEN_W - RIGHT_WALL, SCREEN_H))
388
389 # Obstacles
390 for (ol, ot, orr, ob) in OBSTACLES:
391 pygame.draw.rect(screen, COLOR_OBS,
392 (ol, ot, orr - ol, ob - ot))
393
394 # Trail
395 if show_trail and len(trail) > 1:
396 for i, (tx, ty) in enumerate(trail):
397 alpha = int(80 * i / len(trail))
398 s = pygame.Surface((1, 1))
399 s.set_alpha(alpha)
400 s.fill((255, 100, 100))
401 screen.blit(s, (tx, ty))
402
403 # Character with squish
404 # Vertical squish (floor/ceil): wider + shorter
405 # Horizontal squish (walls): taller + narrower
406 if not squish_h:
407 cw = SIZE + squish
408 ch = SIZE - squish // 2
409 else:
410 cw = SIZE - squish // 2
411 ch = SIZE + squish
412 pygame.draw.rect(screen, COLOR_CHAR,
413 (char_x - cw, char_y - ch, cw * 2, ch * 2))
414
415 # Velocity vector arrow (scale: 2px per unit of velocity)
416 svx = to_signed(vel_x)
417 svy = to_signed(vel_y)
418 if show_vector and (svx != 0 or svy != 0):
419 ARROW_SCALE = 2
420 ax = char_x + svx * ARROW_SCALE
421 ay = char_y + svy * ARROW_SCALE
422 pygame.draw.line(screen, (255, 255, 255), (char_x, char_y), (ax, ay), 2)
423 angle = math.atan2(svy, svx)
424 head = 6
425 for side in (+0.5, -0.5):
426 hx = ax - head * math.cos(angle + side)
427 hy = ay - head * math.sin(angle + side)
428 pygame.draw.line(screen, (255, 255, 255), (ax, ay), (int(hx), int(hy)), 2)
429
430 # HUD
431 info = [
432 f"pos: ({char_x}, {char_y}) vel: ({svx}, {svy})",
433 f"squish: {squish} ground: {'yes' if on_ground else 'no'}",
434 "",
435 "WASD: move/jump R: reset T: trail V: vector Q: quit",
436 ]
437 for i, line in enumerate(info):
438 surf = font.render(line, True, (255, 255, 255))
439 screen.blit(surf, (LEFT_WALL + 4, CEIL_BOT + 4 + i * 16))
440
441 pygame.display.flip()
442 clock.tick(FPS)
443
444 pygame.quit()
445
446
447if __name__ == "__main__":
448 main()