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 pygame
15import sys
16
17# --- Screen / VGA constants (match VHDL) ---
18SCREEN_W = 640
19SCREEN_H = 480
20FPS = 60
21
22# --- Physics constants (match VHDL exactly) ---
23GRAVITY = 1
24IMPULSE = 3
25AIR_CONTROL = 2
26JUMP_FORCE = 13
27MAX_VEL_X = 32
28SIZE = 7
29BOUNCE_SHIFT = 3 # energy loss = vel >> 3 (keep 87.5%)
30
31# --- Bounds (match VHDL) ---
32GROUND = 440
33CEILING = 16
34LEFT_WALL = 8
35RIGHT_WALL = 620
36GROUND_TOP = 448
37CEIL_BOT = 8
38
39# --- Obstacles: (left, top, right, bottom) matching VHDL ---
40OBSTACLES = [
41 (60, 370, 180, 386), # Low platform left
42 (250, 300, 390, 316), # Middle floating platform
43 (440, 200, 580, 216), # High platform right
44 (140, 120, 200, 150), # Small block upper-left
45]
46
47# --- Colors (1-bit RGB) ---
48COLOR_SKY = (0, 0, 0)
49COLOR_GROUND = (0, 255, 0)
50COLOR_CEIL = (0, 255, 0)
51COLOR_WALL = (0, 255, 255)
52COLOR_CHAR = (255, 0, 0)
53COLOR_OBS = (255, 255, 0)
54
55# --- 10-bit signed helpers ---
56MASK = 0x3FF
57
58def to_signed(val):
59 val = val & MASK
60 return val - 1024 if val >= 512 else val
61
62def to_unsigned(val):
63 return val & MASK
64
65def negate(v):
66 return ((~v) + 1) & MASK
67
68
69def main():
70 pygame.init()
71 screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
72 pygame.display.set_caption("Bouncy Game — VHDL Physics Preview")
73 clock = pygame.time.Clock()
74
75 char_x = 100
76 char_y = 200
77 vel_x = to_unsigned(0)
78 vel_y = to_unsigned(0)
79 squish = 0
80 squish_h = False # False = vertical squish (floor/ceil), True = horizontal squish (walls)
81 on_ground = False
82 jump_pressed = False
83
84 keys_held = {'w': False, 'a': False, 's': False, 'd': False}
85 trail = []
86 show_trail = True
87 font = pygame.font.SysFont("monospace", 14)
88
89 running = True
90 while running:
91 for event in pygame.event.get():
92 if event.type == pygame.QUIT:
93 running = False
94 elif event.type == pygame.KEYDOWN:
95 if event.key == pygame.K_w: keys_held['w'] = True
96 elif event.key == pygame.K_a: keys_held['a'] = True
97 elif event.key == pygame.K_s: keys_held['s'] = True
98 elif event.key == pygame.K_d: keys_held['d'] = True
99 elif event.key == pygame.K_q: running = False
100 elif event.key == pygame.K_t:
101 show_trail = not show_trail; trail.clear()
102 elif event.key == pygame.K_r:
103 char_x, char_y = 100, 200
104 vel_x = vel_y = to_unsigned(0)
105 squish = 0; squish_h = False; on_ground = False; jump_pressed = False
106 trail.clear()
107 elif event.type == pygame.KEYUP:
108 if event.key == pygame.K_w: keys_held['w'] = False
109 elif event.key == pygame.K_a: keys_held['a'] = False
110 elif event.key == pygame.K_s: keys_held['s'] = False
111 elif event.key == pygame.K_d: keys_held['d'] = False
112
113 # =============================================================
114 # Physics — matches VHDL exactly
115 # =============================================================
116 vx = vel_x
117 vy = vel_y
118 bounced = False
119 bounce_wall = False
120 bounce_speed = 0
121 grounded = False
122
123 # Jump latch
124 if not keys_held['w']:
125 jump_pressed = False
126
127 # First press on ground: full jump
128 if keys_held['w'] and not jump_pressed and on_ground:
129 vy = to_unsigned(0)
130 vy = (vy - JUMP_FORCE) & MASK
131 jump_pressed = True
132
133 # Holding W while bouncing: boost each ground contact
134 if keys_held['w'] and jump_pressed and on_ground:
135 vy = (vy - 4) & MASK
136
137 # S: slam (air only)
138 if keys_held['s'] and not on_ground:
139 vy = (vy + IMPULSE) & MASK
140
141 # A/D
142 if keys_held['a']:
143 if on_ground:
144 vx = (vx - IMPULSE) & MASK
145 else:
146 vx = (vx - AIR_CONTROL) & MASK
147
148 if keys_held['d']:
149 if on_ground:
150 vx = (vx + IMPULSE) & MASK
151 else:
152 vx = (vx + AIR_CONTROL) & MASK
153
154 # Gravity
155 vy = (vy + GRAVITY) & MASK
156
157 # Friction: vel -= vel/4, min 1
158 svx = to_signed(vx)
159 if svx > 0:
160 drag = svx >> 2
161 if drag == 0: drag = 1
162 svx -= drag
163 elif svx < 0:
164 drag = (-svx) >> 2
165 if drag == 0: drag = 1
166 svx += drag
167 vx = to_unsigned(svx)
168
169 # Clamp X
170 svx = to_signed(vx)
171 if svx > MAX_VEL_X: svx = MAX_VEL_X
172 elif svx < -MAX_VEL_X: svx = -MAX_VEL_X
173 vx = to_unsigned(svx)
174
175 # Clamp Y
176 svy = to_signed(vy)
177 if svy > 63: svy = 63
178 elif svy < -64: svy = -64
179 vy = to_unsigned(svy)
180
181 # Update position
182 px = char_x + to_signed(vx)
183 py = char_y + to_signed(vy)
184
185 # --- Ground bounce ---
186 if py >= GROUND:
187 py = GROUND
188 bounced = True
189 grounded = True
190 bounce_speed = abs(to_signed(vy))
191 vy = negate(vy)
192 svy = to_signed(vy)
193 # svy is now negative (upward), apply energy loss on magnitude
194 if svy < -1:
195 svy += (-svy) >> BOUNCE_SHIFT # reduce magnitude
196 # Kill tiny bounces
197 if abs(svy) < 2:
198 svy = 0
199 vy = to_unsigned(svy)
200
201 # --- Ceiling bounce ---
202 if py <= CEILING:
203 py = CEILING
204 bounced = True
205 bounce_speed = abs(to_signed(vy))
206 vy = negate(vy)
207 svy = to_signed(vy)
208 if abs(svy) > 1:
209 svy_abs = abs(svy)
210 loss = svy_abs >> BOUNCE_SHIFT
211 if svy > 0:
212 svy -= loss
213 else:
214 svy += loss
215 vy = to_unsigned(svy)
216
217 # --- Left wall bounce ---
218 if px <= LEFT_WALL:
219 px = LEFT_WALL
220 bounced = True; bounce_wall = True
221 bounce_speed = abs(to_signed(vx))
222 vx = negate(vx)
223 svx = to_signed(vx)
224 if svx > 1:
225 svx -= svx >> BOUNCE_SHIFT
226 vx = to_unsigned(svx)
227
228 # --- Right wall bounce ---
229 if px >= RIGHT_WALL:
230 px = RIGHT_WALL
231 bounced = True; bounce_wall = True
232 bounce_speed = abs(to_signed(vx))
233 vx = negate(vx)
234 svx = to_signed(vx)
235 if svx < -1:
236 svx += (-svx) >> BOUNCE_SHIFT
237 vx = to_unsigned(svx)
238
239 # --- Obstacle collisions ---
240 for (ol, ot, orr, ob) in OBSTACLES:
241 c_left = px - SIZE
242 c_right = px + SIZE
243 c_top = py - SIZE
244 c_bot = py + SIZE
245
246 if c_right >= ol and c_left <= orr and c_bot >= ot and c_top <= ob:
247 # Compute overlap from velocity direction
248 svx_now = to_signed(vx)
249 svy_now = to_signed(vy)
250
251 if svx_now >= 0:
252 overlap_x = c_right - ol
253 else:
254 overlap_x = orr - c_left
255
256 if svy_now >= 0:
257 overlap_y = c_bot - ot
258 else:
259 overlap_y = ob - c_top
260
261 if overlap_y <= overlap_x:
262 # Vertical resolution
263 if svy_now >= 0: # moving down
264 py = ot - SIZE
265 grounded = True
266 else: # moving up
267 py = ob + SIZE
268 bounced = True
269 bounce_speed = abs(svy_now)
270 vy = negate(vy)
271 svy = to_signed(vy)
272 # Energy loss on magnitude
273 if abs(svy) > 1:
274 svy_abs = abs(svy)
275 loss = svy_abs >> BOUNCE_SHIFT
276 if svy > 0:
277 svy -= loss
278 else:
279 svy += loss
280 if abs(svy) < 2:
281 svy = 0
282 vy = to_unsigned(svy)
283 else:
284 # Horizontal resolution
285 if svx_now >= 0:
286 px = ol - SIZE
287 else:
288 px = orr + SIZE
289 bounced = True; bounce_wall = True
290 bounce_speed = abs(svx_now)
291 vx = negate(vx)
292 svx = to_signed(vx)
293 if svx > 1:
294 svx -= svx >> BOUNCE_SHIFT
295 elif svx < -1:
296 svx += (-svx) >> BOUNCE_SHIFT
297 vx = to_unsigned(svx)
298
299 # Commit
300 char_x = px
301 char_y = py
302 vel_x = vx
303 vel_y = vy
304
305 # On ground
306 on_ground = grounded or (py >= GROUND - 1)
307
308 # Squish — only on impacts with real velocity, not idle ground contact
309 if bounced and bounce_speed > 3:
310 squish = min(bounce_speed, 8)
311 squish_h = bounce_wall
312 elif squish > 0:
313 squish -= 1
314
315 # Trail
316 if show_trail:
317 trail.append((char_x, char_y))
318 if len(trail) > 300:
319 trail.pop(0)
320
321 # =============================================================
322 # Rendering
323 # =============================================================
324 screen.fill(COLOR_SKY)
325
326 # Ceiling
327 pygame.draw.rect(screen, COLOR_CEIL, (0, 0, SCREEN_W, CEIL_BOT))
328
329 # Ground
330 pygame.draw.rect(screen, COLOR_GROUND,
331 (0, GROUND_TOP, SCREEN_W, SCREEN_H - GROUND_TOP))
332
333 # Walls
334 pygame.draw.rect(screen, COLOR_WALL, (0, 0, LEFT_WALL, SCREEN_H))
335 pygame.draw.rect(screen, COLOR_WALL,
336 (RIGHT_WALL, 0, SCREEN_W - RIGHT_WALL, SCREEN_H))
337
338 # Obstacles
339 for (ol, ot, orr, ob) in OBSTACLES:
340 pygame.draw.rect(screen, COLOR_OBS,
341 (ol, ot, orr - ol, ob - ot))
342
343 # Trail
344 if show_trail and len(trail) > 1:
345 for i, (tx, ty) in enumerate(trail):
346 alpha = int(80 * i / len(trail))
347 s = pygame.Surface((3, 3))
348 s.set_alpha(alpha)
349 s.fill((255, 100, 100))
350 screen.blit(s, (tx - 1, ty - 1))
351
352 # Character with squish
353 # Vertical squish (floor/ceil): wider + shorter
354 # Horizontal squish (walls): taller + narrower
355 if not squish_h:
356 cw = SIZE + squish
357 ch = SIZE - squish // 2
358 else:
359 cw = SIZE - squish // 2
360 ch = SIZE + squish
361 pygame.draw.rect(screen, COLOR_CHAR,
362 (char_x - cw, char_y - ch, cw * 2, ch * 2))
363
364 # HUD
365 svx = to_signed(vel_x)
366 svy = to_signed(vel_y)
367 info = [
368 f"pos: ({char_x}, {char_y}) vel: ({svx}, {svy})",
369 f"squish: {squish} ground: {'yes' if on_ground else 'no'}",
370 "",
371 "WASD: move/jump R: reset T: trail Q: quit",
372 ]
373 for i, line in enumerate(info):
374 surf = font.render(line, True, (255, 255, 255))
375 screen.blit(surf, (LEFT_WALL + 4, CEIL_BOT + 4 + i * 16))
376
377 pygame.display.flip()
378 clock.tick(FPS)
379
380 pygame.quit()
381
382
383if __name__ == "__main__":
384 main()