Skip to content

Commit 465a455

Browse files
committed
Improve platformer example
1 parent 4b6736f commit 465a455

2 files changed

Lines changed: 227 additions & 38 deletions

File tree

vm/examples/platformer.asm

Lines changed: 213 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# Space/Up/W : jump (only while standing on a platform)
88
# Escape : quit
99
#
10-
# Goal: climb the platforms and touch the gold flag at the top.
10+
# Goal: climb the platforms and reach the door at the top.
1111
#
1212
# Physics is done in 1/256-pixel fixed point so that gravity and
1313
# velocities have sub-pixel precision. Pixel coordinates are obtained
@@ -30,7 +30,7 @@ WINDOW_TITLE:
3030
.stringz "UVM Platformer";
3131

3232
WIN_STR:
33-
.stringz "You reached the goal!\n";
33+
.stringz "You win!\n";
3434

3535
# Platform table: 5 platforms, each is { x, y, w, h } in pixels (i32).
3636
.align 4;
@@ -59,7 +59,7 @@ PLATFORMS:
5959
# L6 key_right 1 if right is held (integer flag)
6060
# L7 running 1 while the game loop runs (integer flag)
6161
# L8 i platform loop index (integer)
62-
# L9 won 1 while overlapping the goal (integer flag)
62+
# L9 won 0 = playing, >0 = win-animation frame counter
6363
# L10 gx scratch: platform x / base address
6464
# L11 gy scratch: platform y
6565
# L12 gw scratch: platform w
@@ -177,6 +177,9 @@ POLL_DONE:
177177
# Quit if requested
178178
get_local 7; jz EXIT;
179179

180+
# If the win animation is playing, freeze input/physics and skip ahead
181+
get_local 9; jnz WIN_UPDATE;
182+
180183
# =====================================================================
181184
# Compute horizontal velocity from held keys
182185
# =====================================================================
@@ -287,28 +290,38 @@ get_local 1; push 8; rshift_i64; push 500; gt_i64; jz NO_RESPAWN;
287290
NO_RESPAWN:
288291

289292
# =====================================================================
290-
# Goal check
293+
# Goal check: reaching the door (pixels 400,136,24,40 -> * 256)
291294
# =====================================================================
292-
# Goal flag rectangle in pixels: x=405, y=144, w=16, h=32 -> * 256
293295
get_local 0; get_local 1; push 6144; push 8192;
294-
push 103680; push 36864; push 4096; push 8192;
296+
push 102400; push 34816; push 6144; push 10240;
295297
call AABB_OVERLAP, 8;
296-
jz GOAL_OUT;
297-
get_local 9; jnz GOAL_DONE; # already announced this touch
298-
push 1; set_local 9;
298+
jz RENDER;
299+
get_local 9; jnz RENDER; # win animation already started
300+
push 1; set_local 9; # start it (frame counter = 1)
299301
push WIN_STR; syscall print_str;
300-
jmp GOAL_DONE;
301-
GOAL_OUT:
302+
jmp RENDER;
303+
304+
# =====================================================================
305+
# Win animation update: physics is frozen, just advance the timer.
306+
# When the animation ends, respawn the player for another go.
307+
# =====================================================================
308+
WIN_UPDATE:
309+
get_local 9; push 1; add_u64; set_local 9;
310+
get_local 9; push 180; lt_i64; jnz RENDER;
311+
push 10240; set_local 0;
312+
push 89600; set_local 1;
313+
push 0; set_local 2;
314+
push 0; set_local 3;
302315
push 0; set_local 9;
303-
GOAL_DONE:
304316

305317
# =====================================================================
306318
# Render
307319
# =====================================================================
320+
RENDER:
308321
# Clear to sky blue (0xFF87CEEB in BGRA-as-u32)
309322
push PIXEL_BUFFER; push_u32 0xFF87CEEB; push 307200; syscall memset32;
310323

311-
# Draw platforms (green)
324+
# Draw platforms (with bevel shading for depth)
312325
push 0; set_local 8;
313326
RLOOP:
314327
get_local 8; push 5; ge_i64; jnz RLOOP_END;
@@ -317,21 +330,32 @@ get_local 10; load_u32; # x
317330
get_local 10; push 4; add_u64; load_u32; # y
318331
get_local 10; push 8; add_u64; load_u32; # w
319332
get_local 10; push 12; add_u64; load_u32; # h
320-
push_u32 0xFF3CB44B; # green
321-
call FILL_RECT, 5; pop;
333+
call DRAW_PLATFORM, 4; pop;
322334
get_local 8; push 1; add_u64; set_local 8;
323335
jmp RLOOP;
324336
RLOOP_END:
325337

326-
# Draw the goal as a little door
327-
push 405; push 144;
328-
call DRAW_DOOR, 2; pop;
338+
# When winning, draw the celebration instead of the normal door/guy
339+
get_local 9; jnz RENDER_WIN;
329340

330-
# Draw the player as a little guy. Convert fixed point -> pixels.
341+
# Door ground shadow, then the door
342+
push 404; push 176; push 24; push 4; push_u32 0xFF2A7A34; call FILL_RECT, 5; pop;
343+
push 400; push 136; call DRAW_DOOR, 2; pop;
344+
345+
# Player cast shadow, then the little guy
346+
get_local 0; push 8; rshift_i64;
347+
get_local 1; push 8; rshift_i64;
348+
call DRAW_SHADOW, 2; pop;
331349
get_local 0; push 8; rshift_i64;
332350
get_local 1; push 8; rshift_i64;
333351
call DRAW_GUY, 2; pop;
352+
jmp RENDER_PRESENT;
334353

354+
RENDER_WIN:
355+
get_local 9;
356+
call DRAW_WIN, 1; pop;
357+
358+
RENDER_PRESENT:
335359
# Present the frame
336360
push 0; push PIXEL_BUFFER; syscall window_draw_frame;
337361

@@ -443,16 +467,25 @@ ret;
443467
# hair, head, eyes, shirt, arms, legs and shoes. Returns 0.
444468
#
445469
DRAW_GUY:
446-
# hair
447-
get_arg 0; push 6; add_u64; get_arg 1; push 0; add_u64; push 12; push 3; push_u32 0xFF5A3A1A; call FILL_RECT, 5; pop;
448-
# head
449-
get_arg 0; push 6; add_u64; get_arg 1; push 3; add_u64; push 12; push 8; push_u32 0xFFF0C8A0; call FILL_RECT, 5; pop;
470+
# head (skin)
471+
get_arg 0; push 6; add_u64; get_arg 1; push 2; add_u64; push 12; push 9; push_u32 0xFFF0C8A0; call FILL_RECT, 5; pop;
472+
# hair crown (slightly wider than the head, drawn over its top)
473+
get_arg 0; push 5; add_u64; get_arg 1; push 0; add_u64; push 14; push 5; push_u32 0xFF5A3A1A; call FILL_RECT, 5; pop;
474+
# left sideburn
475+
get_arg 0; push 5; add_u64; get_arg 1; push 5; add_u64; push 2; push 3; push_u32 0xFF5A3A1A; call FILL_RECT, 5; pop;
476+
# right sideburn
477+
get_arg 0; push 17; add_u64; get_arg 1; push 5; add_u64; push 2; push 3; push_u32 0xFF5A3A1A; call FILL_RECT, 5; pop;
478+
# swept fringe (longer on the left, short flick on the right)
479+
get_arg 0; push 6; add_u64; get_arg 1; push 5; add_u64; push 7; push 2; push_u32 0xFF5A3A1A; call FILL_RECT, 5; pop;
480+
get_arg 0; push 13; add_u64; get_arg 1; push 5; add_u64; push 5; push 1; push_u32 0xFF5A3A1A; call FILL_RECT, 5; pop;
450481
# left eye
451-
get_arg 0; push 9; add_u64; get_arg 1; push 6; add_u64; push 2; push 2; push_u32 0xFF202020; call FILL_RECT, 5; pop;
482+
get_arg 0; push 8; add_u64; get_arg 1; push 7; add_u64; push 2; push 2; push_u32 0xFF202020; call FILL_RECT, 5; pop;
452483
# right eye
453-
get_arg 0; push 13; add_u64; get_arg 1; push 6; add_u64; push 2; push 2; push_u32 0xFF202020; call FILL_RECT, 5; pop;
484+
get_arg 0; push 13; add_u64; get_arg 1; push 7; add_u64; push 2; push 2; push_u32 0xFF202020; call FILL_RECT, 5; pop;
454485
# shirt / torso
455486
get_arg 0; push 4; add_u64; get_arg 1; push 11; add_u64; push 16; push 10; push_u32 0xFFE63C3C; call FILL_RECT, 5; pop;
487+
# shirt shading (darker lower edge)
488+
get_arg 0; push 4; add_u64; get_arg 1; push 18; add_u64; push 16; push 3; push_u32 0xFFB02C2C; call FILL_RECT, 5; pop;
456489
# left arm
457490
get_arg 0; push 1; add_u64; get_arg 1; push 11; add_u64; push 3; push 9; push_u32 0xFFF0C8A0; call FILL_RECT, 5; pop;
458491
# right arm
@@ -471,20 +504,169 @@ ret;
471504
#
472505
# DRAW_DOOR(i64 x, i64 y)
473506
#
474-
# Draw a little wooden door inside the 16 x 32 goal box whose top-left
507+
# Draw a little wooden door inside the 24 x 40 goal box whose top-left
475508
# corner is at the given pixel coordinates: outer frame, recessed inner
476509
# panel, two sub-panels and a gold knob. Returns 0.
477510
#
478511
DRAW_DOOR:
479512
# frame
480-
get_arg 0; push 0; add_u64; get_arg 1; push 0; add_u64; push 16; push 32; push_u32 0xFF8B5A2B; call FILL_RECT, 5; pop;
513+
get_arg 0; push 0; add_u64; get_arg 1; push 0; add_u64; push 24; push 40; push_u32 0xFF8B5A2B; call FILL_RECT, 5; pop;
481514
# recessed inner panel
482-
get_arg 0; push 2; add_u64; get_arg 1; push 2; add_u64; push 12; push 28; push_u32 0xFF6B3F1A; call FILL_RECT, 5; pop;
515+
get_arg 0; push 3; add_u64; get_arg 1; push 3; add_u64; push 18; push 34; push_u32 0xFF6B3F1A; call FILL_RECT, 5; pop;
483516
# upper sub-panel
484-
get_arg 0; push 4; add_u64; get_arg 1; push 4; add_u64; push 8; push 10; push_u32 0xFF8B5A2B; call FILL_RECT, 5; pop;
517+
get_arg 0; push 6; add_u64; get_arg 1; push 6; add_u64; push 12; push 12; push_u32 0xFF8B5A2B; call FILL_RECT, 5; pop;
485518
# lower sub-panel
486-
get_arg 0; push 4; add_u64; get_arg 1; push 16; add_u64; push 8; push 12; push_u32 0xFF8B5A2B; call FILL_RECT, 5; pop;
519+
get_arg 0; push 6; add_u64; get_arg 1; push 22; add_u64; push 12; push 13; push_u32 0xFF8B5A2B; call FILL_RECT, 5; pop;
487520
# door knob
488-
get_arg 0; push 11; add_u64; get_arg 1; push 18; add_u64; push 2; push 3; push_u32 0xFFFFD700; call FILL_RECT, 5; pop;
521+
get_arg 0; push 18; add_u64; get_arg 1; push 20; add_u64; push 3; push 4; push_u32 0xFFFFD700; call FILL_RECT, 5; pop;
522+
push 0;
523+
ret;
524+
525+
#
526+
# DRAW_PLATFORM(i64 x, i64 y, i64 w, i64 h)
527+
#
528+
# Draw a platform with a lighter top highlight and a darker bottom edge so
529+
# it reads as a solid 3D block rather than a flat rectangle. Returns 0.
530+
#
531+
DRAW_PLATFORM:
532+
# body
533+
get_arg 0; get_arg 1; get_arg 2; get_arg 3; push_u32 0xFF3CB44B; call FILL_RECT, 5; pop;
534+
# top highlight (3px)
535+
get_arg 0; get_arg 1; get_arg 2; push 3; push_u32 0xFF5AD06A; call FILL_RECT, 5; pop;
536+
# bottom shadow (4px)
537+
get_arg 0; get_arg 1; get_arg 3; add_u64; push 4; sub_u64; get_arg 2; push 4; push_u32 0xFF2A8038; call FILL_RECT, 5; pop;
538+
push 0;
539+
ret;
540+
541+
#
542+
# DRAW_SHADOW(i64 x, i64 y)
543+
#
544+
# Draw a soft contact shadow for the player on the nearest platform below
545+
# its feet. The shadow shrinks as the player rises, giving a sense of
546+
# height while jumping. x,y are the player's pixel top-left. Returns 0.
547+
#
548+
# Locals:
549+
# L0 i platform index
550+
# L1 best y of the closest platform top at or below the feet
551+
# L2 feet y + 32
552+
# L3 cx x + 12 (horizontal centre)
553+
# L4 gx scratch: platform x
554+
# L5 gy scratch: platform y
555+
# L6 gw scratch: platform w
556+
# L7 sw shadow width
557+
# L8 dist feet-to-platform distance
558+
# L9 sx shadow left edge
559+
#
560+
DRAW_SHADOW:
561+
push 0; push 0; push 0; push 0; push 0; push 0; push 0; push 0; push 0; push 0;
562+
push 0; set_local 0;
563+
push 100000; set_local 1;
564+
get_arg 1; push 32; add_u64; set_local 2; # feet
565+
get_arg 0; push 12; add_u64; set_local 3; # cx
566+
DS_LOOP:
567+
get_local 0; push 5; ge_i64; jnz DS_DONE;
568+
get_local 0; push 16; mul_u64; push PLATFORMS; add_u64; # base
569+
dup; load_u32; set_local 4; # gx
570+
dup; push 4; add_u64; load_u32; set_local 5; # gy
571+
push 8; add_u64; load_u32; set_local 6; # gw
572+
# horizontal overlap: x < gx+gw && x+24 > gx
573+
get_arg 0; get_local 4; get_local 6; add_u64; lt_i64;
574+
get_arg 0; push 24; add_u64; get_local 4; gt_i64;
575+
and_u64;
576+
jz DS_NEXT;
577+
# platform top at or below the feet?
578+
get_local 5; get_local 2; push 2; sub_u64; ge_i64; jz DS_NEXT;
579+
# nearest one so far?
580+
get_local 5; get_local 1; lt_i64; jz DS_NEXT;
581+
get_local 5; set_local 1;
582+
DS_NEXT:
583+
get_local 0; push 1; add_u64; set_local 0;
584+
jmp DS_LOOP;
585+
DS_DONE:
586+
# no platform below -> nothing to draw
587+
get_local 1; push 100000; ge_i64; jnz DS_RET;
588+
# dist = max(0, best - feet)
589+
get_local 1; get_local 2; sub_u64; set_local 8;
590+
get_local 8; push 0; lt_i64; jz DS_DIST_OK;
591+
push 0; set_local 8;
592+
DS_DIST_OK:
593+
# sw = max(6, 24 - dist/5)
594+
get_local 8; push 5; div_i64; push 24; swap; sub_u64; set_local 7;
595+
get_local 7; push 6; lt_i64; jz DS_SW_OK;
596+
push 6; set_local 7;
597+
DS_SW_OK:
598+
# sx = cx - sw/2
599+
get_local 3; get_local 7; push 2; div_i64; sub_u64; set_local 9;
600+
get_local 9; get_local 1; get_local 7; push 3; push_u32 0xFF2A7A34;
601+
call FILL_RECT, 5; pop;
602+
DS_RET:
603+
push 0;
604+
ret;
605+
606+
#
607+
# DRAW_WIN(i64 t)
608+
#
609+
# Draw the victory celebration for animation frame t: the door swings open
610+
# to reveal warm light, the little guy steps inside, and confetti rains
611+
# down over the whole scene. Returns 0.
612+
#
613+
# Locals:
614+
# L0 ow width of the door opening
615+
# L1 gx walking guy's x
616+
# L2 i confetti index
617+
# L3 - unused scratch
618+
#
619+
DRAW_WIN:
620+
push 0; push 0; push 0; push 0;
621+
622+
# the door itself (closed frame as a backdrop)
623+
push 400; push 136; call DRAW_DOOR, 2; pop;
624+
625+
# opening width ow = min(20, t/2)
626+
get_arg 0; push 2; div_i64;
627+
dup; push 20; gt_i64; jz DW_OW;
628+
pop; push 20;
629+
DW_OW:
630+
set_local 0;
631+
632+
# warm light spilling out of the growing opening
633+
push 400; push 24; get_local 0; sub_u64; push 2; div_i64; add_u64; # lx = 400 + (24-ow)/2
634+
push 140;
635+
get_local 0;
636+
push 32;
637+
push_u32 0xFFFFE9A0;
638+
call FILL_RECT, 5; pop;
639+
640+
# the little guy steps into the doorway during the first ~48 frames
641+
get_arg 0; push 48; lt_i64; jz DW_NOGUY;
642+
push 372; get_arg 0; push 2; mul_u64; push 3; div_i64; add_u64; set_local 1; # gx = 372 + t*2/3
643+
get_local 1; push 144; call DRAW_GUY, 2; pop;
644+
DW_NOGUY:
645+
646+
# confetti: 16 falling squares whose colour cycles
647+
push 0; set_local 2;
648+
DW_CONF:
649+
get_local 2; push 16; ge_i64; jnz DW_CONF_END;
650+
# x = (i*53 + 40) mod 600
651+
get_local 2; push 53; mul_u64; push 40; add_u64; push 600; mod_u64;
652+
# y = (t*4 + i*37) mod 460
653+
get_arg 0; push 4; mul_u64; get_local 2; push 37; mul_u64; add_u64; push 460; mod_u64;
654+
# size
655+
push 5; push 5;
656+
# colour by i mod 3
657+
get_local 2; push 3; mod_u64;
658+
dup; push 0; eq_u64; jnz DW_C0;
659+
dup; push 1; eq_u64; jnz DW_C1;
660+
pop; push_u32 0xFF4CC3FF; jmp DW_CDRAW;
661+
DW_C0:
662+
pop; push_u32 0xFFE63C3C; jmp DW_CDRAW;
663+
DW_C1:
664+
pop; push_u32 0xFFFFD700;
665+
DW_CDRAW:
666+
call FILL_RECT, 5; pop;
667+
get_local 2; push 1; add_u64; set_local 2;
668+
jmp DW_CONF;
669+
DW_CONF_END:
670+
489671
push 0;
490672
ret;

vm/src/asm.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,12 +1374,19 @@ mod tests
13741374
#[test]
13751375
fn parse_files()
13761376
{
1377-
parse_file("examples/empty.asm");
1378-
parse_file("examples/factorial.asm");
1379-
parse_file("examples/fib.asm");
1380-
parse_file("examples/fizzbuzz.asm");
1381-
parse_file("examples/loop.asm");
1382-
parse_file("examples/memcpy.asm");
1383-
parse_file("examples/gradient.asm");
1377+
// Parse every example program in the examples directory so that new
1378+
// examples are covered automatically without updating this test.
1379+
let mut paths: Vec<_> = std::fs::read_dir("examples")
1380+
.expect("could not read examples directory")
1381+
.map(|entry| entry.unwrap().path())
1382+
.filter(|path| path.extension().map_or(false, |ext| ext == "asm"))
1383+
.collect();
1384+
paths.sort();
1385+
1386+
assert!(!paths.is_empty(), "no .asm examples found");
1387+
1388+
for path in paths {
1389+
parse_file(path.to_str().unwrap());
1390+
}
13841391
}
13851392
}

0 commit comments

Comments
 (0)