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]
name = "ddnet-map-gen"
authors = ["Edgar <git@edgarluque.com>"]
description = "A DDraceNetwork map generator."
version = "0.1.0"
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

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.
<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
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<()> {
let matches = command!()
.about("A DDraceNetwork map generator.")
.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."))
.arg(arg!(<FILE> "The output map file").required(true))
.subcommand(
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();
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();
match matches.subcommand() {
Some(("maze", _sub_m)) => MazeGenerator::save_file(&mut rng, output)?,
Some(("fly", _sub_m)) => FlyGenerator::save_file(&mut rng, output)?,
Some(("maze", 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());
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"),
}

View File

@ -6,52 +6,58 @@ use rand::Rng;
pub struct 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()?;
const HEIGHT: usize = 1000;
const WIDTH: usize = 100;
let mut tiles = Array2::from_shape_simple_fn((HEIGHT, WIDTH), || {
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), || {
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()));
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()));
Array2::from_shape_simple_fn((height, width), || Tile::new(0, TileFlags::empty()));
tiles
.row_mut(HEIGHT - 2)
.row_mut(height - 2)
.iter_mut()
.for_each(|tile| tile.id = TILE_UNHOOKABLE);
unhookable_tiles
.row_mut(HEIGHT - 2)
.row_mut(height - 2)
.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 {
front_tiles[(HEIGHT - 6, x)].id = TILE_START;
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 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 max_steps = (height as f64 * 0.30f64).round() as i64;
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);
center += direction;
fly_width += width_change;
center = center.clamp(fly_width, WIDTH as i64 - fly_width);
fly_width = fly_width.clamp(2, 12);
fly_width = fly_width.clamp(3, 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;
freeze_tiles[(y, x)].id = 4;
}
@ -60,6 +66,8 @@ impl MapGenerator for FlyGenerator {
tiles[(y, x)].id = TILE_FREEZE;
freeze_tiles[(y, x)].id = 4;
}
direction_steps -= 1;
}
let game_layer = GameLayer {
@ -70,11 +78,11 @@ impl MapGenerator for FlyGenerator {
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.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.tiles = CompressedData::Loaded(freeze_tiles);
freeze_tiles_layer.color = Color {

View File

@ -6,33 +6,55 @@ 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);
fn generate<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize) -> Result<TwMap> {
// Must be odd.
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())
});
// 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());
'outerStart: for y in 0..height {
for x in 0..width {
let tile = &mut tiles[(y, x)];
if tile.id == 0 {
tile.id = TILE_SPAWN;
replace_around_gametile(&mut tiles, x, y, TILE_EMPTY, TILE_START);
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)];
'outerFinish: for y in (0..height).rev() {
for x in (0..width).rev() {
let tile = &mut tiles[(y, x)];
if tile.id == 0 {
*tile = GameTile::new(TILE_FINISH, TileFlags::empty());
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 {
tiles: CompressedData::Loaded(tiles),
};
let mut physics = Group::physics();
physics.layers.push(Layer::Game(game_layer));
physics.layers.push(Layer::Tiles(hook_tiles_layer));
map.groups.push(quads_sky());
map.groups.push(physics);
Ok(map)

View File

@ -1,6 +1,7 @@
use std::path::Path;
use eyre::Result;
use ndarray::{Array2};
use rand::Rng;
use twmap::*;
@ -17,10 +18,10 @@ 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 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<()> {
let mut map = Self::generate(rng)?;
fn save_file<R: Rng + ?Sized>(rng: &mut R, width: usize, height: usize, path: &Path) -> Result<()> {
let mut map = Self::generate(rng, width, height)?;
map.save_file(path)?;
Ok(())
}
@ -81,3 +82,32 @@ pub fn quads_sky() -> Group {
quads_group.layers.push(Layer::Quads(quads_layer));
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);
}
}
}