soukoban 0.1.5

A library provides the implementation of some algorithms and data structures related to Sokoban
Documentation
use std::str::FromStr;

use soukoban::{prelude::*, solver::*};

mod utils;
use utils::*;

#[test]
fn a_star_search() {
    fn search(mut level: Level, strategy: Strategy) -> Actions {
        let solver = Solver::new(level.map().clone(), strategy);
        let solution = solver.search(Algorithm::AStar).unwrap();

        let directions = solution.iter().map(|action| action.direction());
        level.execute_batch(directions).unwrap();
        assert!(level.is_solved());

        solution
    }

    search(
        load_level_from_file("assets/BoxWorld_100.xsb", 1),
        Strategy::Quick,
    );
    search(
        load_level_from_file("assets/BoxWorld_100.xsb", 2),
        Strategy::Quick,
    );
    search(
        load_level_from_file("assets/BoxWorld_100.xsb", 3),
        Strategy::Quick,
    );

    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 1),
            Strategy::PushOptimal,
        )
        .shifts(),
        Actions::from_str("DuLLrUUdrR").unwrap().shifts()
    );
    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 1),
            Strategy::MoveOptimal,
        )
        .moves(),
        Actions::from_str("DuLLrUUdrR").unwrap().moves()
    );

    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 2),
            Strategy::PushOptimal,
        )
        .shifts(),
        Actions::from_str(
            "rr4DrddlluRdrUl5ulldRur4D3RdrUUd3lddlluRdrUl4ulldRur3D3RdrU3lddlluRdrUlu3R"
        )
        .unwrap()
        .shifts()
    );
    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 2),
            Strategy::MoveOptimal,
        )
        .moves(),
        Actions::from_str(
            "rr4DrddlluRdrUl5ulldRur4D3RdrUUd3lddlluRdrUl4ulldRur3D3RdrU3lddlluRdrUlu3R"
        )
        .unwrap()
        .moves()
    );

    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 3),
            Strategy::PushOptimal,
        )
        .shifts(),
        Actions::from_str(
            "rRRddrruULuu4l3D3u4rdd3L3ruu4ldDldRu6ruLd5luu4rDrd4LDu3ruu4ldDldRu3rddrUru4L3ruu4ldD"
        )
        .unwrap()
        .shifts()
    );
    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 3),
            Strategy::MoveOptimal,
        )
        .shifts(),
        Actions::from_str(
            "rRRddrruULuu4l3D3u4rdd3L3ruu4ldDldRu6ruLd5luu4rDrd4LDu3ruu4ldDldRu3rddrUru4L3ruu4ldD"
        )
        .unwrap()
        .shifts()
    );

    search(
        load_level_from_file("assets/Aymeric_Du_Peloux_282.xsb", 67),
        Strategy::Quick,
    );
    search(
        load_level_from_file("assets/Aymeric_Du_Peloux_282.xsb", 78),
        Strategy::Quick,
    );
}

#[test]
fn ida_star_search() {
    fn search(mut level: Level, strategy: Strategy) -> Actions {
        let solver = Solver::new(level.map().clone(), strategy);
        let solution = solver.search(Algorithm::IDAStar).unwrap();

        let directions = solution.iter().map(|action| action.direction());
        level.execute_batch(directions).unwrap();
        assert!(level.is_solved());

        solution
    }

    search(
        load_level_from_file("assets/BoxWorld_100.xsb", 1),
        Strategy::Quick,
    );
    search(
        load_level_from_file("assets/BoxWorld_100.xsb", 2),
        Strategy::Quick,
    );
    search(
        load_level_from_file("assets/BoxWorld_100.xsb", 3),
        Strategy::Quick,
    );

    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 1),
            Strategy::PushOptimal,
        )
        .shifts(),
        Actions::from_str("DuLLrUUdrR").unwrap().shifts()
    );
    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 1),
            Strategy::MoveOptimal,
        )
        .moves(),
        Actions::from_str("DuLLrUUdrR").unwrap().moves()
    );

    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 2),
            Strategy::PushOptimal,
        )
        .shifts(),
        Actions::from_str(
            "rr4DrddlluRdrUl5ulldRur4D3RdrUUd3lddlluRdrUl4ulldRur3D3RdrU3lddlluRdrUlu3R"
        )
        .unwrap()
        .shifts()
    );
    assert_eq!(
        search(
            load_level_from_file("assets/BoxWorld_100.xsb", 2),
            Strategy::MoveOptimal,
        )
        .moves(),
        Actions::from_str(
            "rr4DrddlluRdrUl5ulldRur4D3RdrUUd3lddlluRdrUl4ulldRur3D3RdrU3lddlluRdrUlu3R"
        )
        .unwrap()
        .moves()
    );

    // FIXME: Potential infinite loop occurs.
    // assert_eq!(
    //     search(
    //         load_level_from_file("assets/BoxWorld_100.xsb", 3),
    //         Strategy::OptimalPush,
    //     )
    //     .pushes(),
    //     Actions::from_str(
    //         "rRRddrruULuu4l3D3u4rdd3L3ruu4ldDldRu6ruLd5luu4rDrd4LDu3ruu4ldDldRu3rddrUru4L3ruu4ldD"
    //     )
    //     .unwrap()
    //     .pushes()
    // );
    // assert_eq!(
    //     search(
    //         load_level_from_file("assets/BoxWorld_100.xsb", 3),
    //         Strategy::OptimalMove,
    //     )
    //     .pushes(),
    //     Actions::from_str(
    //         "rRRddrruULuu4l3D3u4rdd3L3ruu4ldDldRu6ruLd5luu4rDrd4LDu3ruu4ldDldRu3rddrUru4L3ruu4ldD"
    //     )
    //     .unwrap()
    //     .pushes()
    // );
}

#[test]
fn bfs_search() {
    fn search(mut level: Level, strategy: Strategy) -> Actions {
        let solver = Solver::new(level.map().clone(), strategy);
        let solution = solver.search(Algorithm::Bfs).unwrap();

        let directions = solution.iter().map(|action| action.direction());
        level.execute_batch(directions).unwrap();
        assert!(level.is_solved());

        solution
    }

    search(
        load_level_from_file("assets/BoxWorld_100.xsb", 1),
        Strategy::Quick,
    );
    search(
        load_level_from_file("assets/BoxWorld_100.xsb", 2),
        Strategy::Quick,
    );
    search(
        load_level_from_file("assets/BoxWorld_100.xsb", 3),
        Strategy::Quick,
    );
}

#[test]
fn request_stop() {
    let level = load_level_from_file("assets/Grigr2001_100.xsb", 1);
    let solver = Solver::new(level.into(), Strategy::Quick);

    let handler = std::thread::spawn({
        let solver = solver.clone();
        move || solver.search(Algorithm::AStar)
    });

    std::thread::sleep(std::time::Duration::from_secs(1));
    solver.request_stop();

    let result = handler.join().unwrap();
    assert_eq!(result, Err(SearchError::Interrupted));
}

#[test]
fn min_costs() {
    let level = load_level_from_file("assets/Aymeric_Du_Peloux_282.xsb", 67);
    let solver = Solver::new(level.into(), Strategy::Quick);
    assert_eq!(solver.context().min_costs().len(), 8);

    let level = load_level_from_file("assets/Aymeric_Du_Peloux_282.xsb", 78);
    let solver = Solver::new(level.into(), Strategy::Quick);
    assert_eq!(solver.context().min_costs().len(), 8);
}

#[test]
fn tunnels() {
    let level = load_level_from_file("assets/BoxWorld_100.xsb", 2);
    let solver = Solver::new(level.into(), Strategy::Quick);
    assert_eq!(solver.context().tunnels().len(), 4);
}