Tutorial: Building a 60 FPS Real-Time Snake Arcade Game

Construct a high-framerate, deterministic arcade game in Nyx. Learn how zero-garbage-collection region memory eliminates frame drops and micro-stutters during high-speed rendering.

🎯 Key Concepts Covered:

1. Complete Public Source Code (std/apps/snake.nyx)

The entire source code is available in the public Nyx repository under examples/apps/snake.nyx:

module std.apps.snake

import std.collections
import std.math

pub enum Direction {
    Up,
    Down,
    Left,
    Right,
}

pub struct Point {
    pub x: Int,
    pub y: Int,
}

pub struct SnakeGame {
    pub grid_width: Int,
    pub grid_height: Int,
    pub snake: Vec[Point],
    pub food: Point,
    pub current_dir: Direction,
    pub score: Int,
    pub high_score: Int,
    pub is_game_over: Bool,
    pub is_paused: Bool,
}

impl SnakeGame {
    pub fn new(width: Int, height: Int) -> SnakeGame {
        let mut snake = Vec.new();
        // Initial 3-segment snake
        snake.push(Point { x: 10, y: 10 });
        snake.push(Point { x: 9, y: 10 });
        snake.push(Point { x: 8, y: 10 });

        SnakeGame {
            grid_width: width,
            grid_height: height,
            snake: snake,
            food: Point { x: 15, y: 10 },
            current_dir: Direction.Right,
            score: 0,
            high_score: 0,
            is_game_over: false,
            is_paused: false,
        }
    }

    pub fn change_direction(self: &mut Self, new_dir: Direction) {
        match (self.current_dir, new_dir) {
            (Direction.Up, Direction.Down) => {},
            (Direction.Down, Direction.Up) => {},
            (Direction.Left, Direction.Right) => {},
            (Direction.Right, Direction.Left) => {},
            _ => { self.current_dir = new_dir; },
        }
    }

    pub fn tick(self: &mut Self) {
        if self.is_game_over || self.is_paused {
            return;
        }

        let head = self.snake.get(0).unwrap();
        let mut new_x = head.x;
        let mut new_y = head.y;

        match self.current_dir {
            Direction.Up => { new_y = new_y - 1; },
            Direction.Down => { new_y = new_y + 1; },
            Direction.Left => { new_x = new_x - 1; },
            Direction.Right => { new_x = new_x + 1; },
        }

        // Wall Collision Check
        if new_x < 0 || new_x >= self.grid_width || new_y < 0 || new_y >= self.grid_height {
            self.is_game_over = true;
            return;
        }

        // Self Collision Check
        let mut i = 0;
        while i < self.snake.len() {
            let seg = self.snake.get(i).unwrap();
            if seg.x == new_x && seg.y == new_y {
                self.is_game_over = true;
                return;
            }
            i = i + 1;
        }

        // Move head forward
        let new_head = Point { x: new_x, y: new_y };
        self.snake.insert(0, new_head);

        // Check Food Collision
        if new_x == self.food.x && new_y == self.food.y {
            self.score = self.score + 10;
            if self.score > self.high_score {
                self.high_score = self.score;
            }
            self.spawn_food();
        } else {
            // Remove tail segment if food was not eaten
            self.snake.pop();
        }
    }

    pub fn spawn_food(self: &mut Self) {
        // Pseudo-random deterministic placement
        let next_x = (self.food.x * 7 + 13) % self.grid_width;
        let next_y = (self.food.y * 11 + 17) % self.grid_height;
        self.food = Point { x: next_x, y: next_y };
    }

    pub fn reset(self: &mut Self) {
        let mut snake = Vec.new();
        snake.push(Point { x: 10, y: 10 });
        snake.push(Point { x: 9, y: 10 });
        snake.push(Point { x: 8, y: 10 });

        self.snake = snake;
        self.food = Point { x: 15, y: 10 };
        self.current_dir = Direction.Right;
        self.score = 0;
        self.is_game_over = false;
        self.is_paused = false;
    }
}

2. Why Nyx Outperforms Managed Game Engines

In traditional managed languages (e.g. JavaScript Canvas games or C# Unity scripts), continuous allocations inside the 60 Hz / 120 Hz render loop eventually trigger the garbage collector, causing dropped frames (stutter). In Nyx:

3. Compiling & Packaging the Arcade Game

# Run game in desktop window
nyx run std/apps/snake.nyx

# Build native executable for NyxOS or Windows
nyx build --release std/apps/snake.nyx -o bin/snake-arcade.exe