nightrunner_lib 0.4.0

A parser library for making text adventure games
Documentation
use cursive::align::{HAlign, VAlign};
use cursive::event::EventResult;
use cursive::theme::{BaseColor, BorderStyle, Color, ColorStyle, Style};
use cursive::utils::span::SpannedString;
use cursive::view::{Nameable, Resizable, ScrollStrategy};
use cursive::views::{
    Dialog, DummyView, EditView, Layer, LinearLayout, NamedView, PaddedView, ResizedView,
    ScrollView, TextContent, TextView,
};
use cursive::{Cursive, View};
use cursive_aligned_view::Alignable;
use nightrunner_lib::parser::interpreter::{EventMessage, MessageParts};
use nightrunner_lib::{NightRunner, NightRunnerBuilder, ParsingResult};

static THEME_COLORS: [(&str, Color); 10] = [
    ("background", Color::Rgb(17, 17, 17)),
    ("shadow", Color::Dark(BaseColor::Green)),
    ("view", Color::Rgb(17, 17, 17)),
    ("primary", Color::Dark(BaseColor::Green)),
    ("secondary", Color::Dark(BaseColor::Green)),
    ("tertiary", Color::Dark(BaseColor::Green)),
    ("title_primary", Color::Rgb(255, 0, 127)),
    ("title_secondary", Color::Dark(BaseColor::Green)),
    ("highlight", Color::Dark(BaseColor::Green)),
    ("highlight_text", Color::Dark(BaseColor::Black)),
];

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let nr = NightRunnerBuilder::new()
        .with_path_for_config("./fixtures/")
        .build()?;
    let intro_text = nr.game_intro();
    let first_room_text = nr.first_room_text()?.message;
    let mut siv = cursive::default();
    let mut theme = siv.current_theme().clone();
    theme.borders = BorderStyle::Simple;
    theme.shadow = false;
    for tc in THEME_COLORS.iter() {
        theme.palette.set_color(tc.0, tc.1)
    }
    siv.set_theme(theme);
    siv.set_user_data(nr);

    let main_view = Dialog::around(PaddedView::lrtb(
        0,
        0,
        1,
        0,
        ScrollView::new(
            LinearLayout::vertical().child(TextView::new(first_room_text).with_name("room_text")),
        )
        .scroll_strategy(ScrollStrategy::StickToBottom)
        .with_name("scroll_area"),
    ))
    .title("Cursive Example")
    .title_position(HAlign::Center)
    .with_name("main_content")
    .fixed_width(90)
    .fixed_height(25);

    let input_view = Dialog::around(
        LinearLayout::horizontal().child(TextView::new("> ")).child(
            EditView::new()
                .filler(" ")
                .on_submit(on_submit)
                .style(ColorStyle::new(
                    Color::Rgb(17, 17, 17),
                    Color::Light(BaseColor::Green),
                ))
                .with_name("input")
                .full_width(),
        ),
    )
    .fixed_width(90);
    let view = ResizedView::with_full_screen(
        LinearLayout::vertical()
            .child(main_view)
            .child(input_view)
            .fixed_height(28)
            .align_center(),
    );
    siv.add_fullscreen_layer(PaddedView::lrtb(2, 2, 2, 1, view));
    siv.add_fullscreen_layer(PaddedView::lrtb(2, 2, 2, 1, intro(intro_text)));

    siv.run();
    Ok(())
}

fn on_submit(siv: &mut Cursive, query: &str) {
    let nr = siv.user_data::<NightRunner>().unwrap();

    if query == "!rewind" {
        match nr.rewind_state() {
            Ok(rewind_message) => {
                siv.add_layer(
                    Dialog::around(TextView::new(rewind_message.to_string()))
                        .dismiss_button("OK")
                        .h_align(HAlign::Center)
                        .align_center(),
                );
            }
            Err(e) => {
                siv.add_layer(
                    Dialog::around(TextView::new(format!("{}", e)))
                        .dismiss_button("OK")
                        .h_align(HAlign::Center)
                        .align_center(),
                );
            }
        }
    } else if query == "!advance" {
        match nr.fast_forward_state() {
            Ok(fast_forward_message) => {
                siv.add_layer(
                    Dialog::around(TextView::new(fast_forward_message.to_string()))
                        .dismiss_button("OK")
                        .h_align(HAlign::Center)
                        .align_center(),
                );
            }
            Err(e) => {
                siv.add_layer(
                    Dialog::around(TextView::new(format!("{}", e)))
                        .dismiss_button("OK")
                        .h_align(HAlign::Center)
                        .align_center(),
                );
            }
        }
    } else {
        match nr.parse_input(query) {
            Ok(parsing_result) => match parsing_result {
                ParsingResult::NewItem(item_message) => {
                    siv.call_on_name("room_text", |view: &mut TextView| view.append(item_message))
                        .unwrap();
                }
                ParsingResult::DropItem(drop_message) => {
                    siv.call_on_name("room_text", |view: &mut TextView| view.append(drop_message))
                        .unwrap();
                }
                ParsingResult::Look(text) => {
                    siv.add_layer(
                        Dialog::around(TextView::new(text))
                            .dismiss_button("OK")
                            .h_align(HAlign::Center)
                            .align_center(),
                    );
                }
                ParsingResult::Inventory(inventory) => {
                    siv.add_layer(
                        Dialog::around(TextView::new(inventory))
                            .dismiss_button("OK")
                            .h_align(HAlign::Center)
                            .align_center(),
                    );
                }
                ParsingResult::EventSuccess(event_message) => {
                    let EventMessage {
                        message,
                        message_parts,
                        templated_words: _,
                    } = event_message;
                    siv.call_on_name("room_text", |view: &mut TextView| {
                        view.set_content(message);
                        view.append(&message_parts.get(&MessageParts::EventText).unwrap().clone());
                    })
                    .unwrap();
                }
                ParsingResult::SubjectNoEvent(subject_text) => {
                    siv.call_on_name("room_text", |view: &mut TextView| {
                        view.append("\n".to_owned() + &subject_text);
                    })
                    .unwrap();
                }
                ParsingResult::Help(help_text) => {
                    siv.add_fullscreen_layer(ResizedView::with_full_screen(
                        Layer::new(
                            Dialog::around(TextView::new(help_text))
                                .dismiss_button("OK")
                                .h_align(HAlign::Center)
                                .fixed_height(28)
                                .fixed_width(90),
                        )
                        .align_center(),
                    ));
                }
                ParsingResult::Quit => {
                    siv.quit();
                }
            },
            Err(e) => {
                siv.add_layer(
                    Dialog::around(TextView::new(format!("{}", e)))
                        .dismiss_button("OK")
                        .h_align(HAlign::Center)
                        .align_center(),
                );
            }
        }
    }

    siv.call_on_name("input", |view: &mut EditView| view.set_content(""));
    siv.call_on_name(
        "scroll_area",
        |view: &mut ScrollView<NamedView<TextView>>| {
            if !view.is_at_bottom() {
                view.set_scroll_strategy(ScrollStrategy::StickToBottom)
            } else {
                EventResult::Ignored
            }
        },
    );
}

pub fn intro(intro_text: String) -> impl View {
    let title: TextContent = TextContent::new(intro_text);

    let mut styled_body: SpannedString<Style> = SpannedString::new();
    styled_body.append("Press Enter to begin your journey.\nType ");
    styled_body.append_styled("quit", Style::from(Color::Rgb(255, 0, 127)));
    styled_body.append(" to exit and ");
    styled_body.append_styled("help", Style::from(Color::Rgb(255, 0, 127)));
    styled_body.append(" for instructions");
    let body: TextContent = TextContent::new(styled_body);

    ResizedView::with_full_screen(
        Layer::new(
            Dialog::around(
                LinearLayout::vertical()
                    .child(PaddedView::lrtb(
                        0,
                        0,
                        1,
                        0,
                        TextView::new_with_content(title).h_align(HAlign::Center),
                    ))
                    .child(DummyView.fixed_height(2))
                    .child(ResizedView::with_full_height(
                        TextView::new_with_content(body)
                            .v_align(VAlign::Center)
                            .h_align(HAlign::Center),
                    )),
            )
            .dismiss_button("Start")
            .h_align(HAlign::Center)
            .fixed_height(28)
            .fixed_width(90),
        )
        .align_center(),
    )
}