at main 448 lines 16 kB view raw
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()