Assembly & C Systems Programming — Final Project

DANGER
DASH

A terminal-based endless runner in x86 NASM + C

NASM x86 C / ncursesw pthreads 32-bit ELF
Benjamin  ·  Muhammad  ·  Ethan
Overview

What is Danger Dash?

GAME_DESCRIPTION.txt
$ ./game.out

// A terminal-based 2D endless runner
// built from scratch using:

Languages → C + x86 NASM Assembly
Rendering → ncursesw (terminal UI)
Threading → POSIX pthreads
Target → 32-bit Linux ELF
Build → GCC + NASM Makefile

// Player dodges obstacles in a
// scrolling world that speeds up
// as your score increases.
  • Scrolling world — map shifts left each frame, new obstacles spawn from the right
  • Jump mechanics — player can jump over ground obstacles, duck under air hazards
  • Collision detection — per-cell map lookup in assembly (check_for_collision)
  • Dynamic difficulty — frame rate speeds up as score climbs
  • Multi-threaded — separate threads for game loop, input, and player movement
  • Unicode player characters rendered with mvwaddwstr
System Design

Architecture

🖥️
C Layer
Init, rendering, input loop, threading, ncursesw windows
⇅ shared memory / function calls
⚙️
ASM Layer
Player movement, collision, obstacle logic, score updates
⇅ map array (char**)
🗺️
Shared State
Game struct in heap — both sides read/write via pointer

File breakdown

  • src/game.c — core game engine, rendering, threading
  • src/game.asm — movement, collision, map logic (67% of codebase)
  • src/driver.c — C main(), calls asm_main()
  • src/asm_io.asm — I/O wrappers (printf, scanf bridges)
  • inc/game.h — all structs, constants, prototypes
  • Makefile — 4-step build: asm → obj → link
67%
Assembly
30%
C Code
42
Commits
3
Threads
Team Breakdown

Who Did What

🥷
Benjamin
Lead Developer
Game loop & update cycle ncursesw rendering engine Obstacle map & scrolling Threading architecture Score / speed system Game Over screen Title / start screen
🕵️
Muhammad
Input Systems
__keypress__ thread Keyboard event routing Pause / resume logic Multi-player key mapping ESC / Backspace handling Player state transitions
👨
Ethan
Initialization
init() — game bootstrap ncurses color pairs setup Window layout (3-panel) Player struct creation Memory allocation deinit() cleanup
Concurrency

Threading Model

MAIN THREAD
game loop → update() → usleep(frame_rate)
↓ pthread_create
INPUT THREAD — __keypress__
Muhammad · blocks on getch() with timeout(50ms)
↓ pthread_create (per keypress)
MOVEMENT THREAD — __player_effect__
Detached · animates jump arc frame by frame

Mutex protection

  • game→lock guards global game state (ACTIVE / IDLE / INACTIVE)
  • player→lock guards per-player x, y, state
  • All reads/writes to shared state go through pthread_mutex_lock
  • Movement thread detaches itself — no join needed on cleanup
  • Input thread joined in end() before teardown
state machine
// Game state transitions
INACTIVE ACTIVE // on start
ACTIVE IDLE // on pause
IDLE ACTIVE // on resume
ACTIVE INACTIVE // collision
Assembly Deep Dive

The ASM Side — game.asm

game.asm — key functions
; Entry point called from driver.c
global asm_main

; Exported to C (declared in game.h)
global move_player
global check_for_collision

; move_player args (cdecl 32-bit):
; int* new_yx, char key,
; int y, int x, int rows, int cols

; Uses enter/leave stack frames
; 32-bit cdecl calling convention
; EAX = return value

Assembly responsibilities

  • move_player — takes key input, calculates target Y/X position, writes into int* array
  • check_for_collision — reads the map char array at player's (y,x), returns 1 if obstacle
  • All gameplay math in registers — EAX, EBX, ECX, EDX
  • Stack frames via enter N, 0 / leave — compatible with C's cdecl
  • Arguments pushed right-to-left by C, popped off in ASM via [ebp+8], [ebp+12]
cdecl calling convention 32-bit EBP frames EAX return values
Build Pipeline

Compilation Chain

asm_io.asm
NASM -f elf32
asm_io.o
32-bit obj
+
driver.c
gcc -m32
+
game.asm
NASM -f elf
+
game.c
gcc -m32
game.out
32-bit ELF
Makefile commands
# Step 1 — assemble I/O support layer
nasm -f elf32 -d ELF_TYPE -g -F dwarf src/asm_io.asm -I inc -o asm_io.o

# Step 2 — compile C driver
gcc -no-pie -g -m32 -znoexecstack -c src/driver.c -I inc

# Step 3 — assemble game logic
nasm -f elf -F dwarf -g src/game.asm -I inc -o game.o

# Step 4 — link everything
gcc -no-pie -g -m32 -znoexecstack asm_io.o driver.o game.o src/game.c -I inc -lncursesw -lpthread -o game.out
Lessons Learned

Challenges We Hit

Technical

  • UTF-8 obstacle chars (▓▲◆) overflow char[3] — each is 3 bytes. Fixed with ASCII fallback
  • Race conditions between input thread and game loop — solved with per-player mutex
  • ncurses crashing on game over when a window was left blank — all 3 panels must be drawn before refresh
  • 32-bit cdecl stack alignment — arguments pushed in reverse, accessed via [ebp+N]
  • Player Y spawn offset — being off by 1 broke the jump arc calculation in ASM

Process

  • C↔ASM interface — agreeing on who owns what state and how to pass pointers cleanly
  • Detached threads — movement threads needed pthread_detach to avoid memory leaks on rapid keypresses
  • __initialize_curses__ accidentally duplicated inside init() — corrupted the whole function body
  • Debugging ncurses with GDB requires -g -F dwarf in both NASM and GCC flags
Thank You

DEMO
TIME

C
Init & Render
ASM
Logic & Move
Endless Run
github.com/K3rn4lp4n1c/Danger-Dash
Benjamin — Engine Muhammad — Input Ethan — Init