Example: Text adventure

This page contains a sample implementation of a simple story reader using inkling. It only uses plain text and a terminal.

The full code can be found as the player.rs example on Github.

Design

The player requires this functionality:

  • Reading a story from a file
  • A game loop
  • Presenting the text to the player
  • Asking the player for a choice at branches

Reading

A simple function that attempts to read the story from a file at a given path. If errors are encountered they should be handled.


#![allow(unused)]
fn main() {
extern crate inkling;
use std::io::Write;
use inkling::{error::parse::print_read_error, read_story_from_string, Story};

fn read_story(path: &std::path::Path) -> Result<Story, std::io::Error> {
    let content = std::fs::read_to_string(path)?;

    match read_story_from_string(&content) {
        Ok(story) => Ok(story),
        Err(error) => {
            // If the story could not be parsed, write the list of errors to stderr
            write!(
                std::io::stderr(),
                "{}",
                print_read_error(&error).unwrap()
            )
            .unwrap();

            std::process::exit(1);
        }
    }
}
}

Game loop

The main loop implements the standard pattern.


#![allow(unused)]
fn main() {
extern crate inkling;
use inkling::{InklingError, Prompt, Story};

fn play_story(mut story: Story) -> Result<(), InklingError> {
    let mut line_buffer = Vec::new();

    while let Prompt::Choice(choices) = story.resume(&mut line_buffer)? {
        print_lines(&line_buffer);
        line_buffer.clear();

        let choice = ask_user_for_choice(&choices).unwrap_or_else(|| {
            println!("Exiting program.");
            std::process::exit(0);
        });

        println!("");
        story.make_choice(choice)?;
    }

    Ok(())
}

// Mock the following functions
fn print_lines(buffer: &inkling::LineBuffer) { unimplemented!(); }
fn ask_user_for_choice(choices: &[inkling::Choice]) -> Option<usize> { unimplemented!(); }
}

Printing story text

Simply iterate through the list of lines and print the text. Add an extra newline if there is a paragraph break.


#![allow(unused)]
fn main() {
extern crate inkling;
use inkling::LineBuffer;

fn print_lines(lines: &LineBuffer) {
    for line in lines {
        print!("{}", line.text);

        if line.text.ends_with('\n') {
            print!("\n");
        }
    }
}
}

Asking the player for a choice

Print the available choices one by one, then ask for a selection.


#![allow(unused)]
fn main() {
extern crate inkling;
use std::io;
use inkling::Choice;

fn ask_user_for_choice(choices: &[Choice]) -> Option<usize> {
    println!("Choose:");

    for (i, choice) in choices.iter().enumerate() {
        println!("  {}. {}", i + 1, choice.text);
    }

    println!("     ---");
    println!("  0. Exit story");
    println!("");

    let index = get_choice(choices.len())?;
    Some(index)
}

fn get_choice(num_choices: usize) -> Option<usize> {
    loop {
        let mut input = String::new();
        std::io::stdin().read_line(&mut input).unwrap();

        match input.trim().parse::<usize>() {
            Ok(0) => {
                return None;
            }
            Ok(i) if i > 0 && i <= num_choices => {
                return Some(i - 1);
            }
            _ => {
                println!("Not a valid option, try again:");
            }
        }
    }
}
}