klondike-rs 1.1.0

CLI Klondike Solitaire written in Rust
Documentation
use std::{convert::TryFrom, error::Error, fs};

use log::{info, LevelFilter};
use num_traits::ToPrimitive;
use simplelog::{ConfigBuilder, WriteLogger};
use termion::{event::Key, input::TermRead};

use klondike_lib::{
    display::DisplayState,
    engine::{GameEngineBuilder, Update},
    model::{game::Action, AreaId, Settings, Suit},
    terminal::Terminal,
};

static LOG_FILE: &str = "klondike.log";

fn main() -> Result<(), Box<dyn Error>> {
    WriteLogger::init(
        LevelFilter::Debug,
        ConfigBuilder::new().set_time_to_local(true).build(),
        fs::File::create(LOG_FILE)?,
    )?;
    log_panics::init();

    info!("STARTING KLONDIKE");

    let terminal = Terminal::new()?;
    let input = terminal.input()?;
    let output = terminal.output()?;

    let settings = Settings::read_from_system()?;

    let mut engine = GameEngineBuilder::builder(&settings.game, input.keys(), output)
        .input_mapper(DisplayState::Playing, handle_playing_input)
        .input_mapper(DisplayState::HelpMessageOpen, handle_help_input)
        .input_mapper(DisplayState::WinMessageOpen, handle_win_input)
        .start()?;

    while engine.tick()? {}

    info!("QUITTING KLONDIKE");

    Ok(())
}

fn handle_playing_input(key: Key) -> Option<Update> {
    match key {
        Key::Char('q') => Some(Update::State(DisplayState::Quitting)),
        Key::Char('?') => Some(Update::State(DisplayState::HelpMessageOpen)),

        Key::Char('s') => Some(Update::Action(Action::MoveTo(AreaId::Stock))),
        Key::Char('t') => Some(Update::Action(Action::MoveTo(AreaId::Talon))),

        Key::Char('f') => Some(Update::Action(Action::MoveToFoundation)),

        Key::Char('h') | Key::Left => Some(Update::Action(Action::MoveLeft)),
        Key::Char('j') | Key::Down => Some(Update::Action(Action::SelectLess)),
        Key::Char('k') | Key::Up => Some(Update::Action(Action::SelectMore)),
        Key::Char('l') | Key::Right => Some(Update::Action(Action::MoveRight)),

        Key::Char(c @ '1'..='9') => {
            if let Some(index) = c.to_digit(10) {
                let area_id = AreaId::Tableaux(index.to_u8()? - 1);
                Some(Update::Action(Action::MoveTo(area_id)))
            } else {
                None
            }
        }

        Key::F(i @ 1..=4) => {
            let area_id = AreaId::Foundation(Suit::try_from(i.to_u8()? - 1).unwrap());
            Some(Update::Action(Action::MoveTo(area_id)))
        }

        Key::Char('-') => Some(Update::Action(Action::MoveBack)),
        Key::Esc => Some(Update::Action(Action::ReturnHeld)),

        Key::Char(' ') | Key::Char('\n') => Some(Update::Action(Action::Activate)),

        _ => None,
    }
}

fn handle_help_input(key: Key) -> Option<Update> {
    match key {
        _ => Some(Update::State(DisplayState::Playing)),
    }
}

fn handle_win_input(key: Key) -> Option<Update> {
    match key {
        Key::Char('y') => Some(Update::NewGame),
        Key::Char('n') => Some(Update::State(DisplayState::Quitting)),
        _ => None,
    }
}