From 8b1143de74c02cce1f62c79e0f895c5d59db60e9 Mon Sep 17 00:00:00 2001 From: Edgar Luque Date: Mon, 14 Mar 2022 21:08:13 +0100 Subject: [PATCH] reorganize code, add cli interface --- Cargo.lock | 128 +++++++++++++++++++++++- Cargo.toml | 1 + src/cli.rs | 28 ++++++ src/generators/fly.rs | 98 ++++++++++++++++++ src/generators/maze.rs | 53 ++++++++++ src/generators/mod.rs | 83 ++++++++++++++++ src/main.rs | 219 +---------------------------------------- 7 files changed, 391 insertions(+), 219 deletions(-) create mode 100644 src/cli.rs create mode 100644 src/generators/fly.rs create mode 100644 src/generators/maze.rs create mode 100644 src/generators/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3497356..17e4bdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,12 +152,42 @@ dependencies = [ "ansi_term", "atty", "bitflags", - "strsim", - "textwrap", + "strsim 0.8.0", + "textwrap 0.11.0", "unicode-width", "vec_map", ] +[[package]] +name = "clap" +version = "3.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim 0.10.0", + "termcolor", + "textwrap 0.15.0", +] + +[[package]] +name = "clap_derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "color-eyre" version = "0.6.1" @@ -248,6 +278,7 @@ dependencies = [ name = "ddnet-map-gen" version = "0.1.0" dependencies = [ + "clap 3.1.6", "color-eyre", "eyre", "irrgarten", @@ -366,6 +397,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -400,6 +443,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -635,6 +688,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbb993947f111397c2bc536944f8dac7f54a4e73383d478efe1990b56404b60" +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + [[package]] name = "owo-colors" version = "3.2.0" @@ -671,6 +733,30 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.36" @@ -887,6 +973,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "structview" version = "1.1.0" @@ -933,6 +1025,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -942,6 +1043,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + [[package]] name = "thiserror" version = "1.0.30" @@ -1042,7 +1149,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf21dc5f97009d394d536107e15193b7ce8ee035faa5a907eab8f10c1bb42432" dependencies = [ "bitflags", - "clap", + "clap 2.34.0", "flexi_logger", "image", "itertools 0.9.0", @@ -1096,6 +1203,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -1124,6 +1237,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 217e502..7c6132f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "3.1.6", features = ["cargo", "derive"] } color-eyre = "0.6.1" eyre = "0.6.7" irrgarten = "0.1.1" diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..918bc4e --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,28 @@ +use crate::generators::{fly::FlyGenerator, maze::MazeGenerator, MapGenerator}; +use clap::{arg, command, Command}; +use eyre::Result; +use std::path::Path; + +pub fn run_cli() -> Result<()> { + let matches = command!() + .about("A DDraceNetwork map generator.") + .arg_required_else_help(true) + .subcommand_required(true) + .arg(arg!( "The output file").required(true)) + .subcommand(Command::new("maze").about("Generate a maze-like map.")) + .subcommand(Command::new("fly").about("Generate a map for fly techniques.")) + .get_matches(); + + let output = matches.value_of("FILE").expect("output is required"); + let output = Path::new(output); + + let mut rng = rand::thread_rng(); + + match matches.subcommand() { + Some(("maze", _sub_m)) => MazeGenerator::save_file(&mut rng, output)?, + Some(("fly", _sub_m)) => FlyGenerator::save_file(&mut rng, output)?, + _ => panic!("invalid command"), + } + + Ok(()) +} diff --git a/src/generators/fly.rs b/src/generators/fly.rs new file mode 100644 index 0000000..5f7c81a --- /dev/null +++ b/src/generators/fly.rs @@ -0,0 +1,98 @@ +use super::*; +use eyre::Result; +use ndarray::Array2; +use rand::Rng; + +pub struct FlyGenerator; + +impl MapGenerator for FlyGenerator { + fn generate(rng: &mut R) -> Result { + let mut map = create_initial_map()?; + + const HEIGHT: usize = 1000; + const WIDTH: usize = 100; + + let mut tiles = Array2::from_shape_simple_fn((HEIGHT, WIDTH), || { + GameTile::new(TILE_EMPTY, TileFlags::empty()) + }); + let mut front_tiles = Array2::from_shape_simple_fn((HEIGHT, WIDTH), || { + GameTile::new(TILE_EMPTY, TileFlags::empty()) + }); + + let mut unhookable_tiles = + Array2::from_shape_simple_fn((HEIGHT, WIDTH), || Tile::new(0, TileFlags::empty())); + let mut freeze_tiles = + Array2::from_shape_simple_fn((HEIGHT, WIDTH), || Tile::new(0, TileFlags::empty())); + + tiles + .row_mut(HEIGHT - 2) + .iter_mut() + .for_each(|tile| tile.id = TILE_UNHOOKABLE); + unhookable_tiles + .row_mut(HEIGHT - 2) + .iter_mut() + .for_each(|tile| tile.id = 1); + + tiles[(HEIGHT - 3, WIDTH / 2)].id = TILE_SPAWN; + + for x in 0..WIDTH { + front_tiles[(HEIGHT - 6, x)].id = TILE_START; + front_tiles[(10, x)].id = TILE_FINISH; + } + + let mut center: i64 = WIDTH as i64 / 2; + let mut fly_width: i64 = 10; + + for y in (0..=(HEIGHT - 3)).rev() { + let direction: i64 = rng.gen_range(-1..=1); + let width_change: i64 = rng.gen_range(-1..=1); + center += direction; + fly_width += width_change; + center = center.clamp(fly_width, WIDTH as i64 - fly_width); + fly_width = fly_width.clamp(2, 12); + + for x in ((center + fly_width) as usize)..WIDTH { + tiles[(y, x)].id = TILE_FREEZE; + freeze_tiles[(y, x)].id = 4; + } + + for x in 0..=((center - fly_width) as usize) { + tiles[(y, x)].id = TILE_FREEZE; + freeze_tiles[(y, x)].id = 4; + } + } + + let game_layer = GameLayer { + tiles: CompressedData::Loaded(tiles), + }; + + let front_layer = FrontLayer { + tiles: CompressedData::Loaded(front_tiles), + }; + + let mut unhook_tiles_layer = TilesLayer::new((HEIGHT, WIDTH)); + unhook_tiles_layer.image = Some(0); + unhook_tiles_layer.tiles = CompressedData::Loaded(unhookable_tiles); + + let mut freeze_tiles_layer = TilesLayer::new((HEIGHT, WIDTH)); + freeze_tiles_layer.image = Some(1); + freeze_tiles_layer.tiles = CompressedData::Loaded(freeze_tiles); + freeze_tiles_layer.color = Color { + r: 0, + g: 0, + b: 0, + a: 200, + }; + + let mut physics = Group::physics(); + physics.layers.push(Layer::Game(game_layer)); + physics.layers.push(Layer::Front(front_layer)); + physics.layers.push(Layer::Tiles(unhook_tiles_layer)); + physics.layers.push(Layer::Tiles(freeze_tiles_layer)); + + map.groups.push(quads_sky()); + map.groups.push(physics); + + Ok(map) + } +} diff --git a/src/generators/maze.rs b/src/generators/maze.rs new file mode 100644 index 0000000..5461a78 --- /dev/null +++ b/src/generators/maze.rs @@ -0,0 +1,53 @@ +use super::*; +use eyre::Result; +use irrgarten::Maze; +use ndarray::Array2; + +pub struct MazeGenerator; + +impl MapGenerator for MazeGenerator { + fn generate(rng: &mut R) -> Result { + let mut map = create_initial_map()?; + let maze = Maze::new(1001, 1001).unwrap().generate(rng); + + let mut tiles = Array2::from_shape_fn((1001, 1001), |(x, y)| { + GameTile::new(maze[x][y], TileFlags::empty()) + }); + + // Put spawn and start tile on top left most tile. + let mut added_spawn = false; + 'outerStart: for y in 0..101 { + for x in 0..101 { + let tile = &mut tiles[(x, y)]; + if tile.id == 0 && !added_spawn { + *tile = GameTile::new(TILE_SPAWN, TileFlags::empty()); + added_spawn = true; + } else if tile.id == 0 && added_spawn { + *tile = GameTile::new(TILE_START, TileFlags::empty()); + break 'outerStart; + } + } + } + + // Put finish tile on bottom right most tile. + 'outerFinish: for y in (0..1001).rev() { + for x in (0..1001).rev() { + let tile = &mut tiles[(x, y)]; + if tile.id == 0 { + *tile = GameTile::new(TILE_FINISH, TileFlags::empty()); + break 'outerFinish; + } + } + } + + let game_layer = GameLayer { + tiles: CompressedData::Loaded(tiles), + }; + + let mut physics = Group::physics(); + physics.layers.push(Layer::Game(game_layer)); + map.groups.push(physics); + + Ok(map) + } +} diff --git a/src/generators/mod.rs b/src/generators/mod.rs new file mode 100644 index 0000000..28144af --- /dev/null +++ b/src/generators/mod.rs @@ -0,0 +1,83 @@ +use std::path::Path; + +use eyre::Result; +use rand::Rng; +use twmap::*; + +pub mod fly; +pub mod maze; + +pub const TILE_EMPTY: u8 = 0; +pub const TILE_HOOKABLE: u8 = 1; +pub const TILE_UNHOOKABLE: u8 = 3; +pub const TILE_FREEZE: u8 = 9; +pub const TILE_UNFREEZE: u8 = 1; +pub const TILE_START: u8 = 33; +pub const TILE_FINISH: u8 = 34; +pub const TILE_SPAWN: u8 = 192; + +pub trait MapGenerator { + fn generate(rng: &mut R) -> Result; + + fn save_file(rng: &mut R, path: &Path) -> Result<()> { + let mut map = Self::generate(rng)?; + map.save_file(path)?; + Ok(()) + } +} + +pub fn create_initial_map() -> Result { + let mut map = TwMap::empty(Version::DDNet06); + map.info.author = "github.com/edg-l/ddnet-map-gen".to_string(); + map.info.credits = "github.com/edg-l/ddnet-map-gen".to_string(); + map.images.push(Image::External(ExternalImage { + name: "generic_unhookable".to_string(), + width: 1024, + height: 1024, + })); + map.images.push(Image::Embedded(EmbeddedImage::from_file( + "mapres/basic_freeze.png", + )?)); + Ok(map) +} + +// Creates the sky quad from the editor. +pub fn quads_sky() -> Group { + let mut quads_group = Group::default(); + let mut quads_layer = QuadsLayer::default(); + quads_group.parallax_x = 0; + quads_group.parallax_y = 0; + + let mut quad = Quad::new(50 * 2i32.pow(15), 30 * 2i32.pow(15)); + quad.colors = [ + Color { + r: 94, + g: 132, + b: 174, + a: 255, + }, + Color { + r: 94, + g: 132, + b: 174, + a: 255, + }, + Color { + r: 204, + g: 232, + b: 255, + a: 255, + }, + Color { + r: 204, + g: 232, + b: 255, + a: 255, + }, + ]; + + quads_layer.quads.push(quad); + + quads_group.layers.push(Layer::Quads(quads_layer)); + quads_group +} diff --git a/src/main.rs b/src/main.rs index 9d64d4c..65a1427 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,223 +1,10 @@ use color_eyre::Result; -use irrgarten::Maze; -use ndarray::Array2; -use rand::Rng; -use twmap::*; -const TILE_EMPTY: u8 = 0; -const TILE_HOOKABLE: u8 = 1; -const TILE_UNHOOKABLE: u8 = 3; -const TILE_FREEZE: u8 = 9; -const TILE_UNFREEZE: u8 = 1; -const TILE_START: u8 = 33; -const TILE_FINISH: u8 = 34; -const TILE_SPAWN: u8 = 192; +mod cli; +pub mod generators; fn main() -> Result<()> { color_eyre::install()?; - - gen_flymap()?; - + cli::run_cli()?; Ok(()) } - -fn create_initial_map() -> Result { - let mut map = TwMap::empty(Version::DDNet06); - map.info.author = "Ryozuki's Map Gen".to_string(); - map.info.credits = "Ryozuki's Map Gen".to_string(); - map.images.push(Image::External(ExternalImage { - name: "generic_unhookable".to_string(), - width: 1024, - height: 1024, - })); - map.images.push(Image::Embedded(EmbeddedImage::from_file( - "mapres/basic_freeze.png", - )?)); - Ok(map) -} - -fn gen_flymap() -> Result<()> { - let mut rng = rand::thread_rng(); - - let mut map = create_initial_map()?; - - const HEIGHT: usize = 1000; - const WIDTH: usize = 100; - - let mut tiles = Array2::from_shape_simple_fn((HEIGHT, WIDTH), || { - GameTile::new(TILE_EMPTY, TileFlags::empty()) - }); - let mut front_tiles = Array2::from_shape_simple_fn((HEIGHT, WIDTH), || { - GameTile::new(TILE_EMPTY, TileFlags::empty()) - }); - - let mut unhookable_tiles = - Array2::from_shape_simple_fn((HEIGHT, WIDTH), || Tile::new(0, TileFlags::empty())); - let mut freeze_tiles = - Array2::from_shape_simple_fn((HEIGHT, WIDTH), || Tile::new(0, TileFlags::empty())); - - tiles - .row_mut(HEIGHT - 2) - .iter_mut() - .for_each(|tile| tile.id = TILE_UNHOOKABLE); - unhookable_tiles - .row_mut(HEIGHT - 2) - .iter_mut() - .for_each(|tile| tile.id = 1); - - tiles[(HEIGHT - 3, WIDTH / 2)].id = TILE_SPAWN; - - for x in 0..WIDTH { - front_tiles[(HEIGHT - 6, x)].id = TILE_START; - front_tiles[(10, x)].id = TILE_FINISH; - } - - let mut center: i64 = WIDTH as i64 / 2; - let mut fly_width: i64 = 10; - - for y in (0..=(HEIGHT - 3)).rev() { - let direction: i64 = rng.gen_range(-1..=1); - let width_change: i64 = rng.gen_range(-1..=1); - center += direction; - fly_width += width_change; - center = center.clamp(fly_width, WIDTH as i64 - fly_width); - fly_width = fly_width.clamp(2, 12); - - for x in ((center + fly_width) as usize)..WIDTH { - tiles[(y, x)].id = TILE_FREEZE; - freeze_tiles[(y, x)].id = 4; - } - - for x in 0..=((center - fly_width) as usize) { - tiles[(y, x)].id = TILE_FREEZE; - freeze_tiles[(y, x)].id = 4; - } - } - - let game_layer = GameLayer { - tiles: CompressedData::Loaded(tiles), - }; - - let front_layer = FrontLayer { - tiles: CompressedData::Loaded(front_tiles), - }; - - let mut unhook_tiles_layer = TilesLayer::new((HEIGHT, WIDTH)); - unhook_tiles_layer.image = Some(0); - unhook_tiles_layer.tiles = CompressedData::Loaded(unhookable_tiles); - - let mut freeze_tiles_layer = TilesLayer::new((HEIGHT, WIDTH)); - freeze_tiles_layer.image = Some(1); - freeze_tiles_layer.tiles = CompressedData::Loaded(freeze_tiles); - freeze_tiles_layer.color = Color { - r: 0, - g: 0, - b: 0, - a: 200, - }; - - let mut physics = Group::physics(); - physics.layers.push(Layer::Game(game_layer)); - physics.layers.push(Layer::Front(front_layer)); - physics.layers.push(Layer::Tiles(unhook_tiles_layer)); - physics.layers.push(Layer::Tiles(freeze_tiles_layer)); - - map.groups.push(quads_sky()); - map.groups.push(physics); - - map.save_file("server/maps/generated.map")?; - - Ok(()) -} - -fn gen_maze() -> Result<()> { - let mut rng = rand::thread_rng(); - - let mut map = create_initial_map()?; - - let maze = Maze::new(1001, 1001).unwrap().generate(&mut rng); - - let mut tiles = Array2::from_shape_fn((1001, 1001), |(x, y)| { - GameTile::new(maze[x][y], TileFlags::empty()) - }); - - // Put spawn and start tile on top left most tile. - let mut added_spawn = false; - 'outerStart: for y in 0..101 { - for x in 0..101 { - let tile = &mut tiles[(x, y)]; - if tile.id == 0 && !added_spawn { - *tile = GameTile::new(TILE_SPAWN, TileFlags::empty()); - added_spawn = true; - } else if tile.id == 0 && added_spawn { - *tile = GameTile::new(TILE_START, TileFlags::empty()); - break 'outerStart; - } - } - } - - // Put finish tile on bottom right most tile. - 'outerFinish: for y in (0..1001).rev() { - for x in (0..1001).rev() { - let tile = &mut tiles[(x, y)]; - if tile.id == 0 { - *tile = GameTile::new(TILE_FINISH, TileFlags::empty()); - break 'outerFinish; - } - } - } - - let game_layer = GameLayer { - tiles: CompressedData::Loaded(tiles), - }; - - let mut physics = Group::physics(); - physics.layers.push(Layer::Game(game_layer)); - map.groups.push(physics); - - map.save_file("server/maps/generated.map")?; - - Ok(()) -} - -// Creates the sky quad from the editor. -fn quads_sky() -> Group { - let mut quads_group = Group::default(); - let mut quads_layer = QuadsLayer::default(); - quads_group.parallax_x = 0; - quads_group.parallax_y = 0; - - let mut quad = Quad::new(1600, 800); - dbg!(&quad); - quad.colors = [ - Color { - r: 94, - g: 206, - b: 174, - a: 255, - }, - Color { - r: 94, - g: 206, - b: 174, - a: 255, - }, - Color { - r: 204, - g: 232, - b: 255, - a: 255, - }, - Color { - r: 204, - g: 232, - b: 255, - a: 255, - }, - ]; - - quads_layer.quads.push(quad); - - quads_group.layers.push(Layer::Quads(quads_layer)); - quads_group -} \ No newline at end of file