board parsing and display
This commit is contained in:
commit
9919ac8ab5
4 changed files with 370 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
189
Cargo.lock
generated
Normal file
189
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitvec"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||||
|
dependencies = [
|
||||||
|
"funty",
|
||||||
|
"radium",
|
||||||
|
"tap",
|
||||||
|
"wyz",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deprecate-until"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a3767f826efbbe5a5ae093920b58b43b01734202be697e1354914e862e8e704"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"semver",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "funty"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "integer-sqrt"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathfinding"
|
||||||
|
version = "4.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ac35caa284c08f3721fb33c2741b5f763decaf42d080c8a6a722154347017e"
|
||||||
|
dependencies = [
|
||||||
|
"deprecate-until",
|
||||||
|
"indexmap",
|
||||||
|
"integer-sqrt",
|
||||||
|
"num-traits",
|
||||||
|
"rustc-hash",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "radium"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sokoban"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bitvec",
|
||||||
|
"pathfinding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tap"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wyz"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||||
|
dependencies = [
|
||||||
|
"tap",
|
||||||
|
]
|
||||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "sokoban"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitvec = "1.0.1"
|
||||||
|
pathfinding = "4.14.0"
|
||||||
172
src/main.rs
Normal file
172
src/main.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fmt::{self, Display},
|
||||||
|
io::{self, prelude::*},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bitvec::vec::BitVec;
|
||||||
|
|
||||||
|
type Pos = (i16, i16);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct BoardConsts {
|
||||||
|
width: i16,
|
||||||
|
walls: BitVec,
|
||||||
|
goals: BitVec,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Board {
|
||||||
|
pub player: Pos,
|
||||||
|
boxes: BitVec,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum BoardParseError {
|
||||||
|
NoPlayer,
|
||||||
|
DuplicatePlayer(Pos, Pos),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BoardParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
BoardParseError::NoPlayer => write!(f, "no player found"),
|
||||||
|
BoardParseError::DuplicatePlayer((x0, y0), (x1, y1)) => {
|
||||||
|
write!(f, "player found at ({x0}, {y0}) and ({x1}, {y1})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for BoardParseError {}
|
||||||
|
|
||||||
|
#[allow(
|
||||||
|
clippy::cast_possible_truncation,
|
||||||
|
clippy::cast_possible_wrap,
|
||||||
|
clippy::cast_sign_loss
|
||||||
|
)]
|
||||||
|
impl Board {
|
||||||
|
pub fn height(&self, consts: &BoardConsts) -> i16 {
|
||||||
|
self.boxes.len() as i16 / consts.width
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn in_bounds(&self, consts: &BoardConsts, (x, y): Pos) -> bool {
|
||||||
|
(0..consts.width).contains(&x) && (0..self.height(consts)).contains(&y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_of(&self, consts: &BoardConsts, (x, y): Pos) -> usize {
|
||||||
|
(x + y * consts.width) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wall_at(&self, consts: &BoardConsts, pos: Pos) -> bool {
|
||||||
|
if !self.in_bounds(consts, pos) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
*consts.walls.get(self.index_of(consts, pos)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn goal_at(&self, consts: &BoardConsts, pos: Pos) -> bool {
|
||||||
|
if !self.in_bounds(consts, pos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*consts.goals.get(self.index_of(consts, pos)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn box_at(&self, consts: &BoardConsts, pos: Pos) -> bool {
|
||||||
|
if !self.in_bounds(consts, pos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.boxes.get(self.index_of(consts, pos)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(src: &str) -> Result<(BoardConsts, Board), BoardParseError> {
|
||||||
|
let width = src.lines().map(str::len).max().unwrap_or(0) as i16;
|
||||||
|
let mut player = Err(BoardParseError::NoPlayer);
|
||||||
|
let mut walls = BitVec::new();
|
||||||
|
let mut goals = BitVec::new();
|
||||||
|
let mut boxes = BitVec::new();
|
||||||
|
|
||||||
|
for (y, line) in src.lines().enumerate() {
|
||||||
|
let padding = (0..(width - line.len() as i16)).map(|_| b' ');
|
||||||
|
for (x, b) in line.bytes().chain(padding).enumerate() {
|
||||||
|
let pos = (x as i16, y as i16);
|
||||||
|
let (has_player, has_wall, has_goal, has_box) = match b {
|
||||||
|
b'.' => (false, false, true, false),
|
||||||
|
b'@' => (true, false, false, false),
|
||||||
|
b'+' => (true, false, true, false),
|
||||||
|
b'$' => (false, false, false, true),
|
||||||
|
b'*' => (false, false, true, true),
|
||||||
|
b'#' => (false, true, false, false),
|
||||||
|
// ignore invalid characters
|
||||||
|
_ => (false, false, false, false),
|
||||||
|
};
|
||||||
|
if has_player {
|
||||||
|
if let Ok(player) = player {
|
||||||
|
return Err(BoardParseError::DuplicatePlayer(player, pos));
|
||||||
|
}
|
||||||
|
player = Ok(pos);
|
||||||
|
}
|
||||||
|
walls.push(has_wall);
|
||||||
|
goals.push(has_goal);
|
||||||
|
boxes.push(has_box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let consts = BoardConsts {
|
||||||
|
width,
|
||||||
|
walls,
|
||||||
|
goals,
|
||||||
|
};
|
||||||
|
let board = Board {
|
||||||
|
player: player?,
|
||||||
|
boxes,
|
||||||
|
};
|
||||||
|
Ok((consts, board))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&self, consts: &BoardConsts, f: &mut impl Write) -> Result<(), Box<dyn Error>> {
|
||||||
|
for y in 0..self.height(consts) {
|
||||||
|
for x in 0..consts.width {
|
||||||
|
if self.player == (x, y) {
|
||||||
|
if self.goal_at(consts, (x, y)) {
|
||||||
|
write!(f, "+")?;
|
||||||
|
} else {
|
||||||
|
write!(f, "@")?;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match (
|
||||||
|
self.wall_at(consts, (x, y)),
|
||||||
|
self.goal_at(consts, (x, y)),
|
||||||
|
self.box_at(consts, (x, y))
|
||||||
|
) {
|
||||||
|
(true, false, false) => '#',
|
||||||
|
(false, true, false) => '.',
|
||||||
|
(false, false, true) => '$',
|
||||||
|
(false, true, true) => '*',
|
||||||
|
(false, false, false) => ' ',
|
||||||
|
(wall, goal, box_here) => panic!(
|
||||||
|
"bad board state at ({x}, {y}) with wall={wall} goal={goal} box={box_here}"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let src = io::read_to_string(io::stdin())?;
|
||||||
|
let (consts, board) = Board::parse(&src)?;
|
||||||
|
board.write(&consts, &mut io::stdout())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue