TERMINAL GAMES

real games, written in Rust | played in your browser | nothing to install

These are the real games from this repository, compiled to WebAssembly and played right here. The pixel art is made of half-block characters, exactly as it is drawn in a real terminal.

loading the games ...

> THE GAMES

 

 

click to play

Keys work while the screen has focus, so click it first.

> THE TUNER

Drag a slider and the running game changes. Games choose which numbers to expose: see Tuning in the crate docs.

> THE SOURCE

This is examples/glow.rs in the repository. Run it with cargo run --example glow.





> MAKE YOUR OWN

Copy a game into src/main.rs and run it in your own terminal, with your own keyboard and mouse. A game is plain Rust: a Game trait with update and draw. To test one without a terminal, use Headless, which plays it for you.

$ cargo add terminal_graphics_interactive
$ cargo run
//! QUICKGAME: steer a `@` with the arrows or WASD and collect the `*` stars
//! for 20 seconds. Enter starts, q quits, Ctrl+C always quits.

use terminal_graphics_interactive::game::*;

fn main() -> std::io::Result<()> {
    run(Title { score: None })
}

/// The title (public so the website can start the game here). After a round it shows the score.
pub struct Title {
    pub score: Option<u32>,
}

impl Game for Title {
    fn update(&mut self, ctx: &mut Context<'_>) -> Next {
        if ctx.keys.pressed(KeyCode::Enter) {
            Next::switch(Play::new(ctx.seed))
        } else if ctx.keys.pressed(KeyCode::Char('q')) {
            Next::quit()
        } else {
            Next::stay()
        }
    }

    fn draw(&self, screen: &mut Screen) {
        screen.clear(Colour::Black);
        screen.text_centred(8, "QUICKGAME", Colour::BrightYellow);
        let line = match self.score {
            Some(score) => format!("time up: {score} stars"),
            None => "collect the stars before time runs out".to_string(),
        };
        screen.text_centred(10, &line, Colour::White);
        screen.text_centred(12, "press Enter to play", Colour::BrightWhite);
    }
}

/// One round: steer, collect stars, beat the clock.
struct Play {
    rng: Rng,
    x: f32,
    y: f32,
    score: u32,
    time_left: f32,
    stars: Pool<(i32, i32)>,
}

impl Play {
    fn new(seed: u64) -> Play {
        Play {
            rng: Rng::new(seed),
            x: 20.0,
            y: 8.0,
            score: 0,
            time_left: 20.0,
            stars: Pool::with_capacity(12),
        }
    }
}

impl Game for Play {
    fn update(&mut self, ctx: &mut Context<'_>) -> Next {
        if ctx.keys.pressed(KeyCode::Char('q')) {
            return Next::quit();
        }
        let dx = ctx.keys.axis(KeyCode::Left, KeyCode::Right)
            + ctx.keys.axis(KeyCode::Char('a'), KeyCode::Char('d'));
        let dy = ctx.keys.axis(KeyCode::Up, KeyCode::Down)
            + ctx.keys.axis(KeyCode::Char('w'), KeyCode::Char('s'));
        // rows move half as fast: a cell is about twice as tall as it is wide
        let (cols, rows) = (ctx.size.0 as i32, ctx.size.1 as i32);
        let max_x = (cols - 1).max(0) as f32;
        let max_y = (rows - 1).max(0) as f32;
        self.x = (self.x + dx.clamp(-1, 1) as f32 * 24.0 * ctx.dt).clamp(0.0, max_x);
        self.y = (self.y + dy.clamp(-1, 1) as f32 * 12.0 * ctx.dt).clamp(0.0, max_y);

        let player = (self.x.round() as i32, self.y.round() as i32);
        let before = self.stars.len();
        self.stars.retain(|star| *star != player);
        self.score += (before - self.stars.len()) as u32;
        if ctx.tick % 36 == 0 {
            let star = (self.rng.range_i32(0, cols), self.rng.range_i32(0, rows));
            let _ = self.stars.push(star); // a full pool gives the star back
        }

        self.time_left -= ctx.dt;
        if self.time_left <= 0.0 {
            return Next::switch(Title {
                score: Some(self.score),
            });
        }
        Next::stay()
    }

    fn draw(&self, screen: &mut Screen) {
        screen.clear(Colour::Black);
        for &(x, y) in self.stars.iter() {
            screen.put(x, y, '*', Colour::BrightYellow);
        }
        let (x, y) = (self.x.round() as i32, self.y.round() as i32);
        screen.put(x, y, '@', Colour::BrightCyan);
        let hud = format!("score {}   time {}", self.score, self.time_left.ceil());
        screen.text(0, 0, &hud, Colour::White);
    }
}

> MULTIPLAYER

Multiplayer is not on this page yet. Two people can already share one keyboard in a game you write. Playing over a network is planned (lockstep over TCP first, then in the browser) and is not built.

> LINKS