more progress

This commit is contained in:
Edgar 2022-03-22 08:04:04 +01:00
parent 8b1143de74
commit ff49a772de
No known key found for this signature in database
GPG key ID: 8731E6C0166EAA85
6 changed files with 153 additions and 48 deletions

View file

@ -1,7 +1,11 @@
[package] [package]
name = "ddnet-map-gen" name = "ddnet-map-gen"
authors = ["Edgar <git@edgarluque.com>"]
description = "A DDraceNetwork map generator."
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
license = "AGPL-3-only"
keywords = ["ddnet", "teeworlds", "mapgen"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -630,7 +630,7 @@ state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.> <one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author> Copyright (C) 2022 Edgar Luque
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published it under the terms of the GNU Affero General Public License as published

View file

@ -5,12 +5,38 @@ use std::path::Path;
pub fn run_cli() -> Result<()> { pub fn run_cli() -> Result<()> {
let matches = command!() let matches = command!()
.about("A DDraceNetwork map generator.") .about("A DDraceNetwork map generator")
.arg_required_else_help(true) .arg_required_else_help(true)
.subcommand_required(true) .subcommand_required(true)
.arg(arg!(<FILE> "The output file").required(true)) .arg(arg!(<FILE> "The output map file").required(true))
.subcommand(Command::new("maze").about("Generate a maze-like map.")) .subcommand(
.subcommand(Command::new("fly").about("Generate a map for fly techniques.")) Command::new("maze")
.about("Generate a maze-like map")
.arg(
arg!(--width <WIDTH> "The width of the map")
.default_value("1000")
.required(false),
)
.arg(
arg!(--height <HEIGHT> "The height of the map")
.default_value("1000")
.required(false),
),
)
.subcommand(
Command::new("fly")
.about("Generate a map for fly techniques")
.arg(
arg!(--width <WIDTH> "The width of the map")
.default_value("1000")
.required(false),
)
.arg(
arg!(--height <HEIGHT> "The height of the map")
.default_value("1000")
.required(false),
),
)
.get_matches(); .get_matches();
let output = matches.value_of("FILE").expect("output is required"); let output = matches.value_of("FILE").expect("output is required");
@ -19,8 +45,16 @@ pub fn run_cli() -> Result<()> {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
match matches.subcommand() { match matches.subcommand() {
Some(("maze", _sub_m)) => MazeGenerator::save_file(&mut rng, output)?, Some(("maze", sub_m)) => {
Some(("fly", _sub_m)) => FlyGenerator::save_file(&mut rng, output)?, let width: usize = sub_m.value_of_t("width").unwrap_or_else(|e| e.exit());
let height: usize = sub_m.value_of_t("height").unwrap_or_else(|e| e.exit());
MazeGenerator::save_file(&mut rng, width, height, output)?
}
Some(("fly", sub_m)) => {
let width: usize = sub_m.value_of_t("width").unwrap_or_else(|e| e.exit());
let height: usize = sub_m.value_of_t("height").unwrap_or_else(|e| e.exit());
FlyGenerator::save_file(&mut rng, width, height, output)?
}
_ => panic!("invalid command"), _ => panic!("invalid command"),
} }

View file

@ -6,52 +6,58 @@ use rand::Rng;
pub struct FlyGenerator; pub struct FlyGenerator;
impl MapGenerator for FlyGenerator { impl MapGenerator for FlyGenerator {
fn generate<R: Rng + ?Sized>(rng: &mut R) -> Result<TwMap> { fn generate<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize) -> Result<TwMap> {
let mut map = create_initial_map()?; let mut map = create_initial_map()?;
const HEIGHT: usize = 1000; let mut tiles = Array2::from_shape_simple_fn((height, width), || {
const WIDTH: usize = 100;
let mut tiles = Array2::from_shape_simple_fn((HEIGHT, WIDTH), || {
GameTile::new(TILE_EMPTY, TileFlags::empty()) GameTile::new(TILE_EMPTY, TileFlags::empty())
}); });
let mut front_tiles = Array2::from_shape_simple_fn((HEIGHT, WIDTH), || { let mut front_tiles = Array2::from_shape_simple_fn((height, width), || {
GameTile::new(TILE_EMPTY, TileFlags::empty()) GameTile::new(TILE_EMPTY, TileFlags::empty())
}); });
let mut unhookable_tiles = let mut unhookable_tiles =
Array2::from_shape_simple_fn((HEIGHT, WIDTH), || Tile::new(0, TileFlags::empty())); Array2::from_shape_simple_fn((height, width), || Tile::new(0, TileFlags::empty()));
let mut freeze_tiles = let mut freeze_tiles =
Array2::from_shape_simple_fn((HEIGHT, WIDTH), || Tile::new(0, TileFlags::empty())); Array2::from_shape_simple_fn((height, width), || Tile::new(0, TileFlags::empty()));
tiles tiles
.row_mut(HEIGHT - 2) .row_mut(height - 2)
.iter_mut() .iter_mut()
.for_each(|tile| tile.id = TILE_UNHOOKABLE); .for_each(|tile| tile.id = TILE_UNHOOKABLE);
unhookable_tiles unhookable_tiles
.row_mut(HEIGHT - 2) .row_mut(height - 2)
.iter_mut() .iter_mut()
.for_each(|tile| tile.id = 1); .for_each(|tile| tile.id = 2);
tiles[(HEIGHT - 3, WIDTH / 2)].id = TILE_SPAWN; tiles[(height - 3, width / 2)].id = TILE_SPAWN;
for x in 0..WIDTH { for x in 0..width {
front_tiles[(HEIGHT - 6, x)].id = TILE_START; front_tiles[(height - 6, x)].id = TILE_START;
front_tiles[(10, x)].id = TILE_FINISH; front_tiles[(10, x)].id = TILE_FINISH;
} }
let mut center: i64 = WIDTH as i64 / 2; let mut center: i64 = width as i64 / 2;
let mut fly_width: i64 = 10; let mut fly_width: i64 = 10;
for y in (0..=(HEIGHT - 3)).rev() { let max_steps = (height as f64 * 0.30f64).round() as i64;
let direction: i64 = rng.gen_range(-1..=1);
let mut direction_steps: i64 = rng.gen_range((max_steps / 2)..=max_steps);
let mut direction: i64 = rng.gen_range(-1..=1);
for y in (0..=(height - 3)).rev() {
if direction_steps == 0 {
direction_steps = rng.gen_range(1..=10);
direction = rng.gen_range(-1..=1);
}
let width_change: i64 = rng.gen_range(-1..=1); let width_change: i64 = rng.gen_range(-1..=1);
center += direction; center += direction;
fly_width += width_change; fly_width += width_change;
center = center.clamp(fly_width, WIDTH as i64 - fly_width); fly_width = fly_width.clamp(3, 12);
fly_width = fly_width.clamp(2, 12); center = center.clamp(fly_width, width as i64 - fly_width - 1);
for x in ((center + fly_width) as usize)..WIDTH { for x in ((center + fly_width) as usize)..width {
tiles[(y, x)].id = TILE_FREEZE; tiles[(y, x)].id = TILE_FREEZE;
freeze_tiles[(y, x)].id = 4; freeze_tiles[(y, x)].id = 4;
} }
@ -60,6 +66,8 @@ impl MapGenerator for FlyGenerator {
tiles[(y, x)].id = TILE_FREEZE; tiles[(y, x)].id = TILE_FREEZE;
freeze_tiles[(y, x)].id = 4; freeze_tiles[(y, x)].id = 4;
} }
direction_steps -= 1;
} }
let game_layer = GameLayer { let game_layer = GameLayer {
@ -70,11 +78,11 @@ impl MapGenerator for FlyGenerator {
tiles: CompressedData::Loaded(front_tiles), tiles: CompressedData::Loaded(front_tiles),
}; };
let mut unhook_tiles_layer = TilesLayer::new((HEIGHT, WIDTH)); let mut unhook_tiles_layer = TilesLayer::new((height, width));
unhook_tiles_layer.image = Some(0); unhook_tiles_layer.image = Some(0);
unhook_tiles_layer.tiles = CompressedData::Loaded(unhookable_tiles); unhook_tiles_layer.tiles = CompressedData::Loaded(unhookable_tiles);
let mut freeze_tiles_layer = TilesLayer::new((HEIGHT, WIDTH)); let mut freeze_tiles_layer = TilesLayer::new((height, width));
freeze_tiles_layer.image = Some(1); freeze_tiles_layer.image = Some(1);
freeze_tiles_layer.tiles = CompressedData::Loaded(freeze_tiles); freeze_tiles_layer.tiles = CompressedData::Loaded(freeze_tiles);
freeze_tiles_layer.color = Color { freeze_tiles_layer.color = Color {

View file

@ -6,33 +6,55 @@ use ndarray::Array2;
pub struct MazeGenerator; pub struct MazeGenerator;
impl MapGenerator for MazeGenerator { impl MapGenerator for MazeGenerator {
fn generate<R: Rng + ?Sized>(rng: &mut R) -> Result<TwMap> { fn generate<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize) -> Result<TwMap> {
let mut map = create_initial_map()?; // Must be odd.
let maze = Maze::new(1001, 1001).unwrap().generate(rng); let width = {
if width % 2 == 0 {
width + 1
} else {
width
}
};
let height = {
if height % 2 == 0 {
height + 1
} else {
height
}
};
let mut tiles = Array2::from_shape_fn((1001, 1001), |(x, y)| { let mut map = create_initial_map()?;
let maze = Maze::new(width, height).unwrap().generate(rng);
let hookable_tiles =
Array2::from_shape_fn((height, width), |(y, x)| {
let mut t = 0;
if maze[x][y] == 1 {
t = 9;
}
Tile::new(t, TileFlags::empty())
});
let mut tiles = Array2::from_shape_fn((width, height), |(y, x)| {
GameTile::new(maze[x][y], TileFlags::empty()) GameTile::new(maze[x][y], TileFlags::empty())
}); });
// Put spawn and start tile on top left most tile. // Put spawn and start tile on top left most tile.
let mut added_spawn = false; 'outerStart: for y in 0..height {
'outerStart: for y in 0..101 { for x in 0..width {
for x in 0..101 { let tile = &mut tiles[(y, x)];
let tile = &mut tiles[(x, y)]; if tile.id == 0 {
if tile.id == 0 && !added_spawn { tile.id = TILE_SPAWN;
*tile = GameTile::new(TILE_SPAWN, TileFlags::empty()); replace_around_gametile(&mut tiles, x, y, TILE_EMPTY, TILE_START);
added_spawn = true;
} else if tile.id == 0 && added_spawn {
*tile = GameTile::new(TILE_START, TileFlags::empty());
break 'outerStart; break 'outerStart;
} }
} }
} }
// Put finish tile on bottom right most tile. // Put finish tile on bottom right most tile.
'outerFinish: for y in (0..1001).rev() { 'outerFinish: for y in (0..height).rev() {
for x in (0..1001).rev() { for x in (0..width).rev() {
let tile = &mut tiles[(x, y)]; let tile = &mut tiles[(y, x)];
if tile.id == 0 { if tile.id == 0 {
*tile = GameTile::new(TILE_FINISH, TileFlags::empty()); *tile = GameTile::new(TILE_FINISH, TileFlags::empty());
break 'outerFinish; break 'outerFinish;
@ -40,12 +62,19 @@ impl MapGenerator for MazeGenerator {
} }
} }
let mut hook_tiles_layer = TilesLayer::new((height, width));
hook_tiles_layer.image = Some(0);
hook_tiles_layer.tiles = CompressedData::Loaded(hookable_tiles);
let game_layer = GameLayer { let game_layer = GameLayer {
tiles: CompressedData::Loaded(tiles), tiles: CompressedData::Loaded(tiles),
}; };
let mut physics = Group::physics(); let mut physics = Group::physics();
physics.layers.push(Layer::Game(game_layer)); physics.layers.push(Layer::Game(game_layer));
physics.layers.push(Layer::Tiles(hook_tiles_layer));
map.groups.push(quads_sky());
map.groups.push(physics); map.groups.push(physics);
Ok(map) Ok(map)

View file

@ -1,6 +1,7 @@
use std::path::Path; use std::path::Path;
use eyre::Result; use eyre::Result;
use ndarray::{Array2};
use rand::Rng; use rand::Rng;
use twmap::*; use twmap::*;
@ -17,10 +18,10 @@ pub const TILE_FINISH: u8 = 34;
pub const TILE_SPAWN: u8 = 192; pub const TILE_SPAWN: u8 = 192;
pub trait MapGenerator { pub trait MapGenerator {
fn generate<R: Rng + ?Sized>(rng: &mut R) -> Result<TwMap>; fn generate<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize) -> Result<TwMap>;
fn save_file<R: Rng + ?Sized>(rng: &mut R, path: &Path) -> Result<()> { fn save_file<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize, path: &Path) -> Result<()> {
let mut map = Self::generate(rng)?; let mut map = Self::generate(rng, width, height)?;
map.save_file(path)?; map.save_file(path)?;
Ok(()) Ok(())
} }
@ -81,3 +82,32 @@ pub fn quads_sky() -> Group {
quads_group.layers.push(Layer::Quads(quads_layer)); quads_group.layers.push(Layer::Quads(quads_layer));
quads_group quads_group
} }
// Changed the id of the tile if matches oldid.
pub fn replace_gametile(tiles: &mut Array2<GameTile>, x: usize, y: usize, oldid: u8, newid: u8) {
if tiles[(y, x)].id == oldid {
tiles[(y, x)].id = newid;
}
}
pub fn replace_around_gametile(tiles: &mut Array2<GameTile>, x: usize, y: usize, oldid: u8, newid: u8) {
let width = tiles.ncols();
let height = tiles.nrows();
let directions = [-1, 0, 1];
for diry in directions {
for dirx in directions {
if dirx == 0 && diry == 0 {
continue;
}
if (y as i64) + diry < 0 || (y as i64) + diry >= height as i64 {
continue;
}
if (x as i64) + dirx < 0 || (x as i64) + dirx >= width as i64 {
continue;
}
replace_gametile(tiles, ((x as i64) + dirx) as usize, ((y as i64) + diry) as usize, oldid, newid);
}
}
}