bitsy-parser/src/main.rs

425 lines
11 KiB
Rust
Raw Normal View History

2020-04-05 17:58:04 +00:00
use std::fmt;
2020-04-05 20:02:45 +00:00
use std::collections::HashMap;
2020-04-05 17:58:04 +00:00
2020-04-05 21:26:47 +00:00
const IMAGE_DIMENSION_SD: usize = 8;
const IMAGE_DIMENSION_HD: usize = 16;
2020-04-05 17:58:04 +00:00
#[derive(Eq, PartialEq)]
struct Colour {
red: u8,
green: u8,
blue: u8,
}
#[derive(Eq, PartialEq)]
struct Palette {
id: String, // base36 string (why??)
name: Option<String>,
colours: Vec<Colour>,
}
#[derive(Eq, PartialEq)]
struct Image {
pixels: Vec<bool>, // 64 for SD, 256 for HD
}
#[derive(Eq, PartialEq)]
struct Tile {
id: String, // base36 string
name: Option<String>,
wall: bool,
animation_frames: Vec<Image>,
}
2020-04-05 20:02:45 +00:00
#[derive(Eq, PartialEq, Hash)]
2020-04-05 19:46:55 +00:00
struct Position {
room: String, // id. is room id int or base36 string?
x: u8,
y: u8,
}
#[derive(Eq, PartialEq)]
struct Dialogue {
id: String,
contents: String,
}
#[derive(Eq, PartialEq)]
struct Sprite {
id: String, // lowercase base36
name: Option<String>,
animation_frames: Vec<Image>,
dialogue: Option<Dialogue>,
position: Position,
}
2020-04-05 20:02:45 +00:00
/// avatar is a "sprite" in the game data but with a specific ID
2020-04-05 19:46:55 +00:00
#[derive(Eq, PartialEq)]
struct Avatar {
animation_frames: Vec<Image>,
position: Position,
}
#[derive(Eq, PartialEq)]
struct Item {
id: String,
animation_frames: Vec<Image>,
name: Option<String>,
dialogue: Option<Dialogue>,
}
2020-04-05 20:02:45 +00:00
#[derive(Eq, PartialEq)]
struct Exit {
/// destination
room: String, /// id
position: Position,
}
// same as a dialogue basically
#[derive(Eq, PartialEq)]
struct Ending {
contents: String,
}
#[derive(Eq, PartialEq)]
struct Room {
id: String,
palette: String, /// id
name: Option<String>,
items: HashMap<Position, Item>,
exits: HashMap<Position, Exit>,
endings: HashMap<Position, Ending>,
}
2020-04-05 17:58:04 +00:00
#[derive(PartialEq)]
struct Game {
name: String,
version: f64,
room_format: bool,
palettes: Vec<Palette>,
}
impl fmt::Debug for Colour {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Colour")
.field("red", &self.red)
.field("green", &self.green)
.field("blue", &self.blue)
.finish()
}
}
impl fmt::Debug for Palette {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Palette")
.field("id", &self.id)
.field("name", &self.name)
.field("colours", &self.colours)
.finish()
}
}
impl fmt::Debug for Image {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Image")
.field("pixels", &self.pixels)
.finish()
}
}
impl fmt::Debug for Tile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tile")
.field("id", &self.id)
.field("name", &self.name)
.field("wall", &self.wall)
.field("animation_frames", &self.animation_frames)
.finish()
}
}
fn image_from_string(string: String) -> Image {
let string = string.replace("\n", "");
let pixels: Vec<&str> = string.split("").collect();
// the above seems to add an extra "" at the start and end of the vec, so strip them below
let pixels: Vec<bool> = pixels[1..(pixels.len() - 1)].iter().map(|&char| {char == "1"}).collect();
Image { pixels }
}
fn image_to_string(image: Image) -> String {
2020-04-05 21:26:47 +00:00
image_to_string_opts(image, true)
}
fn image_to_string_opts(image: Image, hd: bool) -> String {
2020-04-05 17:58:04 +00:00
let mut string = String::new();
2020-04-05 21:26:47 +00:00
let chunk_size = if hd {IMAGE_DIMENSION_HD} else {IMAGE_DIMENSION_SD};
2020-04-05 17:58:04 +00:00
2020-04-05 21:26:47 +00:00
for line in image.pixels.chunks(chunk_size) {
2020-04-05 17:58:04 +00:00
for pixel in line {
2020-04-05 21:26:47 +00:00
string.push(*pixel as char);
2020-04-05 17:58:04 +00:00
}
string.push('\n');
}
string.pop(); // remove trailing newline
string
}
#[test]
fn test_image_from_string() {
let output = image_from_string(
"11111111\n11001111\n10111111\n11111111\n11111111\n11111111\n11111111\n11111111".to_string()
);
let expected = Image {
pixels: vec![
true,true,true,true,true,true,true,true,
true,true,false,false,true,true,true,true,
true,false,true,true,true,true,true,true,
true,true,true,true,true,true,true,true,
true,true,true,true,true,true,true,true,
true,true,true,true,true,true,true,true,
true,true,true,true,true,true,true,true,
true,true,true,true,true,true,true,true,
]
};
assert_eq!(output, expected)
}
#[test]
fn test_image_to_string() {
let output = image_to_string(Image {
pixels: vec![
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
]
});
let expected = "10101010\n01010101\n10101010\n01010101\n10101010\n01010101\n10101010\n01010101".to_string();
assert_eq!(output, expected);
}
fn tile_from_string(string: String) -> Tile {
let mut lines: Vec<&str> = string.split("\n").collect();
let id = lines[0].replace("TIL ", "");
let last_line = lines.pop().unwrap();
let wall = match last_line == "WAL true" {
true => true,
false => {
lines.push(last_line);
false
}
};
let last_line = lines.pop().unwrap();
let name = match last_line.starts_with("NAME") {
true => Some(last_line.replace("NAME ", "").to_string()),
false => {
lines.push(last_line);
None
}
};
let animation_frames = lines[1..].join("");
let animation_frames: Vec<&str> = animation_frames.split("\n>\n").collect();
let animation_frames: Vec<Image> = animation_frames.iter().map(|&frame| {
image_from_string(frame.to_string())
}).collect();
Tile {id, name, wall, animation_frames}
}
#[test]
fn test_tile_from_string() {
let output = tile_from_string("TIL z\n11111111\n11111111\n11111111\n11111111\n11111111\n11111111\n11111111\n11111111\nNAME concrete 1\nWAL true".to_string());
let expected = Tile {
id: "z".to_string(),
name: Some("concrete 1".to_string()),
wall: true,
animation_frames: vec![
Image {
pixels: vec![true; 64]
}
],
};
assert_eq!(output, expected);
}
fn tile_to_string(tile: Tile) -> String {
let mut animation_frames = String::new();
let last_frame = tile.animation_frames.len() - 1;
2020-04-05 17:58:04 +00:00
for (i, frame) in tile.animation_frames.into_iter().enumerate() {
animation_frames.push_str(&image_to_string(frame));
if i < last_frame {
2020-04-05 17:58:04 +00:00
animation_frames.push_str(&"\n>\n".to_string());
}
}
format!(
"TIL {}\n{}{}{}",
tile.id,
animation_frames,
if tile.name.is_some() {format!("\nNAME {}", tile.name.unwrap())} else {"".to_string()},
if tile.wall {"\nWAL true"} else {""}
)
}
#[test]
fn test_tile_to_string() {
let output = tile_to_string(Tile {
id: "7a".to_string(),
name: Some("chequers".to_string()),
wall: false,
animation_frames: vec![
Image { pixels: vec![
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
]},
Image { pixels: vec![
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
false,true,false,true,false,true,false,true,
true,false,true,false,true,false,true,false,
]},
]
});
let expected = "TIL 7a\n10101010\n01010101\n10101010\n01010101\n10101010\n01010101\n10101010\n01010101\n>\n01010101\n10101010\n01010101\n10101010\n01010101\n10101010\n01010101\n10101010\nNAME chequers".to_string();
assert_eq!(output, expected);
}
2020-04-05 17:58:04 +00:00
// todo support Bitsy HD?
// todo impl (Game::from_string etc.)
fn colour_from_string(colour: String) -> Colour {
let values: Vec<&str> = colour.split(',').collect();
let red: u8 = values[0].parse().unwrap_or(0);
let green: u8 = values[1].parse().unwrap_or(0);
let blue: u8 = values[2].parse().unwrap_or(0);
Colour { red, green, blue }
}
fn colour_to_string(colour: Colour) -> String {
format!("{},{},{}", colour.red, colour.green, colour.blue)
}
#[test]
fn test_colour_from_string() {
assert_eq!(
colour_from_string("0,255,0".to_string()),
Colour { red: 0, green: 255, blue: 0 }
);
}
#[test]
fn test_colour_to_string() {
assert_eq!(
colour_to_string(Colour { red: 22, green: 33, blue: 44 }),
"22,33,44".to_string()
);
}
fn palette_from_string(palette: String) -> Palette {
let lines: Vec<&str> = palette.split('\n').collect();
let id = lines[0].replace("PAL ", "");
let name = match lines[1].starts_with("NAME") {
true => Some(lines[1].replace("NAME ", "").to_string()),
false => None,
};
let colour_start_index = if name.is_some() {2} else {1};
let colours = lines[colour_start_index..].iter().map(|&line| {
colour_from_string(line.to_string())
}).collect();
Palette { id, name, colours }
}
#[test]
fn test_palette_from_string() {
let output = palette_from_string(
"PAL 1\nNAME lamplight\n45,45,59\n66,60,39\n140,94,1".to_string()
);
let expected = Palette {
id: "1".to_string(),
name: Some("lamplight".to_string()),
colours: vec![
Colour {red: 45, green: 45, blue: 59},
Colour {red: 66, green: 60, blue: 39},
Colour {red: 140, green: 94, blue: 1 },
],
};
assert_eq!(output, expected);
}
#[test]
fn test_palette_from_string_no_name() {
let output = palette_from_string(
"PAL 9\n45,45,59\n66,60,39\n140,94,1".to_string()
);
let expected = Palette {
id: "9".to_string(),
name: None,
colours: vec![
Colour {red: 45, green: 45, blue: 59},
Colour {red: 66, green: 60, blue: 39},
Colour {red: 140, green: 94, blue: 1 },
],
};
assert_eq!(output, expected);
}
2020-04-05 19:46:55 +00:00
// fn get_avatar(game: Game) -> Sprite {
// // get the sprite with an id of A
// }
2020-04-05 17:58:04 +00:00
// fn game_from_string(game: String ) -> Game {
2020-04-05 19:46:55 +00:00
// // probably needs to split the game data into different segments starting from the end
// // e.g. VAR... then END... then DLG...
// // then split all these up into their individual items
2020-04-05 17:58:04 +00:00
// }
//
// fn game_to_string(game: Game) -> String {
//
// }
fn main() {
println!("why am I here?")
}