reorganize code, add cli interface

This commit is contained in:
Edgar 2022-03-14 21:08:13 +01:00
parent e6a00cda97
commit 8b1143de74
No known key found for this signature in database
GPG key ID: 8731E6C0166EAA85
7 changed files with 391 additions and 219 deletions

128
Cargo.lock generated
View file

@ -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"

View file

@ -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"

28
src/cli.rs Normal file
View file

@ -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!(<FILE> "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(())
}

98
src/generators/fly.rs Normal file
View file

@ -0,0 +1,98 @@
use super::*;
use eyre::Result;
use ndarray::Array2;
use rand::Rng;
pub struct FlyGenerator;
impl MapGenerator for FlyGenerator {
fn generate<R: Rng + ?Sized>(rng: &mut R) -> Result<TwMap> {
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)
}
}

53
src/generators/maze.rs Normal file
View file

@ -0,0 +1,53 @@
use super::*;
use eyre::Result;
use irrgarten::Maze;
use ndarray::Array2;
pub struct MazeGenerator;
impl MapGenerator for MazeGenerator {
fn generate<R: Rng + ?Sized>(rng: &mut R) -> Result<TwMap> {
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)
}
}

83
src/generators/mod.rs Normal file
View file

@ -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<R: Rng + ?Sized>(rng: &mut R) -> Result<TwMap>;
fn save_file<R: Rng + ?Sized>(rng: &mut R, path: &Path) -> Result<()> {
let mut map = Self::generate(rng)?;
map.save_file(path)?;
Ok(())
}
}
pub fn create_initial_map() -> Result<TwMap> {
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
}

View file

@ -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<TwMap> {
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
}