Created
September 24, 2023 18:50
-
-
Save Pablito2020/18b7ab220493c2ec0fb460ef53fa5621 to your computer and use it in GitHub Desktop.
Map
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
class Map | |
{ | |
private List<char> tiles; | |
private int w; | |
private int h; | |
private bool verbose; | |
public Map(int w, int h, string tile_str = null) | |
{ | |
if (tile_str == null) | |
{ | |
// just create a clear map | |
tiles = new List<char>(); | |
this.w = w; | |
this.h = h; | |
for (int i = 0; i < w * h; i++) | |
{ | |
tiles.Add('.'); | |
} | |
} | |
else | |
{ | |
SetMap(w, h, tile_str); | |
} | |
// sets logging verbosity (on|off) | |
verbose = false; | |
} | |
// create a map from a tile string | |
public void SetMap(int w, int h, string tile_str) | |
{ | |
this.w = w; | |
this.h = h; | |
tiles = new List<char>(FormatMapStr(tile_str, "")); | |
} | |
// creates a string of the current map | |
public override string ToString() | |
{ | |
string s = "\n"; | |
int i = 0; | |
for (int y = 0; y < h; y++) | |
{ | |
for (int x = 0; x < w; x++) | |
{ | |
s += tiles[i]; | |
i++; | |
} | |
s += "\n"; | |
} | |
return s; | |
} | |
// converts x,y to index | |
private int XyToI(int x, int y) | |
{ | |
return x + y * w; | |
} | |
// converts index to x,y | |
private Tuple<int, int> IToXy(int i) | |
{ | |
return Tuple.Create(i % w, i / w); | |
} | |
// validates x,y | |
private bool XyValid(int x, int y) | |
{ | |
return x >= 0 && x < w && y >= 0 && y < h; | |
} | |
// gets tile at x,y or returns None if invalid | |
private char? GetTile(int x, int y) | |
{ | |
if (!XyValid(x, y)) | |
{ | |
return null; | |
} | |
return tiles[XyToI(x, y)]; | |
} | |
// adds a single wall tile at x,y | |
private void AddWallTile(int x, int y) | |
{ | |
if (XyValid(x, y)) | |
{ | |
tiles[XyToI(x, y)] = '|'; | |
} | |
} | |
private bool IsWallBlockFilled(int x, int y) | |
{ | |
for (int dy = 1; dy < 3; dy++) | |
{ | |
for (int dx = 1; dx < 3; dx++) | |
{ | |
if (GetTile(x + dx, y + dy) != '|') | |
{ | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
// adds a 2x2 block inside the 4x4 block at the given x,y coordinate | |
private void AddWallBlock(int x, int y) | |
{ | |
AddWallTile(x + 1, y + 1); | |
AddWallTile(x + 2, y + 1); | |
AddWallTile(x + 1, y + 2); | |
AddWallTile(x + 2, y + 2); | |
} | |
// determines if a 2x2 block can fit inside the 4x4 block at the given x,y coordinate | |
// (the whole 4x4 block must be empty) | |
private bool CanNewBlockFit(int x, int y) | |
{ | |
if (!(XyValid(x, y) && XyValid(x + 3, y + 3))) | |
{ | |
return false; | |
} | |
for (int y0 = y; y0 < y + 4; y0++) | |
{ | |
for (int x0 = x; x0 < x + 4; x0++) | |
{ | |
if (GetTile(x0, y0) != '.') | |
{ | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
// create a list of valid starting positions | |
private List<Tuple<int, int>> UpdatePosList() | |
{ | |
List<Tuple<int, int>> posList = new List<Tuple<int, int>>(); | |
for (int y = 0; y < h; y++) | |
{ | |
for (int x = 0; x < w; x++) | |
{ | |
if (CanNewBlockFit(x, y)) | |
{ | |
posList.Add(Tuple.Create(x, y)); | |
} | |
} | |
} | |
return posList; | |
} | |
// A connection is a sort of dependency of one tile block on another. | |
// If a valid starting position is against another wall, then add this tile | |
// to other valid start positions' that intersect this one so that they fill | |
// it when they are chosen. This filling is a heuristic to eliminate gaps. | |
private Dictionary<Tuple<int, int>, List<Tuple<int, int>>> UpdateConnections() | |
{ | |
Dictionary<Tuple<int, int>, List<Tuple<int, int>>> connections = new Dictionary<Tuple<int, int>, List<Tuple<int, int>>>(); | |
for (int y = 0; y < h; y++) | |
{ | |
for (int x = 0; x < w; x++) | |
{ | |
Tuple<int, int> pos = Tuple.Create(x, y); | |
if (posList.Contains(pos)) | |
{ | |
if (Enumerable.Range(0, 4).Any(y0 => GetTile(x - 1, y + y0) == '|')) AddConnection(x, y, 1, 0, connections); | |
if (Enumerable.Range(0, 4).Any(y0 => GetTile(x + 4, y + y0) == '|')) AddConnection(x, y, -1, 0, connections); | |
if (Enumerable.Range(0, 4).Any(x0 => GetTile(x + x0, y - 1) == '|')) AddConnection(x, y, 0, 1, connections); | |
if (Enumerable.Range(0, 4).Any(x0 => GetTile(x + x0, y + 4) == '|')) AddConnection(x, y, 0, -1, connections); | |
} | |
} | |
} | |
return connections; | |
} | |
// the block at x,y is against a wall, so make intersecting blocks in the direction of | |
// dx,dy fill the block at x,y if they are filled first. | |
private void AddConnection(int x, int y, int dx, int dy, Dictionary<Tuple<int, int>, List<Tuple<int, int>>> connections) | |
{ | |
void Connect(int x0, int y0) | |
{ | |
Tuple<int, int> src = Tuple.Create(x, y); | |
Tuple<int, int> dest = Tuple.Create(x0, y0); | |
if (!connections.ContainsKey(dest)) | |
{ | |
connections[dest] = new List<Tuple<int, int>>(); | |
} | |
connections[dest].Add(src); | |
} | |
Tuple<int, int> pos = Tuple.Create(x, y); | |
if (posList.Contains(pos)) | |
{ | |
Connect(x + dx, y + dy); | |
Connect(x + 2 * dx, y + 2 * dy); | |
if (!posList.Contains(Tuple.Create(x - dy, y - dx))) Connect(x + dx - dy, y + dy - dx); | |
if (!posList.Contains(Tuple.Create(x + dy, y + dx))) Connect(x + dx + dy, y + dy + dx); | |
if (!posList.Contains(Tuple.Create(x + dx - dy, y + dy - dx))) Connect(x + 2 * dx - dy, y + 2 * dy - dx); | |
if (!posList.Contains(Tuple.Create(x + dx + dy, y + dy + dx))) Connect(x + 2 * dx + dy, y + 2 * dy + dx); | |
} | |
} | |
// update the starting positions and dependencies | |
private void Update() | |
{ | |
posList = UpdatePosList(); | |
connections = UpdateConnections(); | |
} | |
// expand a wall block at the given x,y | |
// return number of tiles added | |
private int ExpandWall(int x, int y) | |
{ | |
List<Tuple<int, int>> visited = new List<Tuple<int, int>>(); | |
int Expand(int x, int y) | |
{ | |
int count = 0; | |
Tuple<int, int> src = Tuple.Create(x, y); | |
if (visited.Contains(src)) | |
{ | |
return 0; | |
} | |
visited.Add(src); | |
if (connections.ContainsKey(src)) | |
{ | |
foreach (Tuple<int, int> dest in connections[src]) | |
{ | |
if (!IsWallBlockFilled(dest.Item1, dest.Item2)) | |
{ | |
count++; | |
AddWallBlock(dest.Item1, dest.Item2); | |
} | |
count += Expand(dest.Item1, dest.Item2); | |
} | |
} | |
return count; | |
} | |
return Expand(x, y); | |
} | |
private Tuple<int, int> GetMostOpenDir(int x, int y) | |
{ | |
Tuple<int, int>[] dirs = new Tuple<int, int>[] { Tuple.Create(0, -1), Tuple.Create(0, 1), Tuple.Create(1, 0), Tuple.Create(-1, 0) }; | |
Tuple<int, int> maxDir = dirs[new Random().Next(0, dirs.Length)]; | |
int maxLen = 0; | |
foreach (Tuple<int, int> dir in dirs) | |
{ | |
int len = 0; | |
int dx = dir.Item1; | |
int dy = dir.Item2; | |
while (posList.Contains(Tuple.Create(x + dx * len, y + dy * len))) | |
{ | |
len++; | |
} | |
if (len > maxLen) | |
{ | |
maxDir = dir; | |
maxLen = len; | |
} | |
} | |
return maxDir; | |
} | |
// start a wall at block x,y | |
public bool AddWallObstacle(int? x = null, int? y = null, bool extend = false) | |
{ | |
Update(); | |
if (posList.Count == 0) | |
{ | |
return false; | |
} | |
// choose random valid starting position if none provided | |
if (!x.HasValue || !y.HasValue) | |
{ | |
Tuple<int, int> randomPos = posList[new Random().Next(0, posList.Count)]; | |
x = randomPos.Item1; | |
y = randomPos.Item2; | |
} | |
// add first block | |
AddWallBlock(x.Value, y.Value); | |
// initialize verbose print lines | |
string[] firstLines = ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); | |
string[] growLines = new string[h + 2]; | |
string[] extendLines = new string[h + 2]; | |
// mandatory grow phase | |
int count = ExpandWall(x.Value, y.Value); | |
if (count > 0) | |
{ | |
growLines = ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); | |
} | |
// extend phase | |
if (extend) | |
{ | |
// desired maximum block size | |
int maxBlocks = 4; | |
// 35% chance of forcing the block to turn | |
// turn means the turn has been taken | |
// turnBlocks is the number of blocks traveled before turning | |
bool turn = false; | |
int turnBlocks = maxBlocks; | |
if (new Random().NextDouble() <= 0.35) | |
{ | |
turnBlocks = 4; | |
maxBlocks += turnBlocks; | |
} | |
// choose a random direction | |
Tuple<int, int>[] dirs = new Tuple<int, int>[] { Tuple.Create(0, -1), Tuple.Create(0, 1), Tuple.Create(1, 0), Tuple.Create(-1, 0) }; | |
Tuple<int, int> origDir = dirs[new Random().Next(0, dirs.Length)]; | |
Tuple<int, int> dir = origDir; | |
int i = 0; | |
while (count < maxBlocks) | |
{ | |
int x0 = x.Value + dir.Item1 * i; | |
int y0 = y.Value + dir.Item2 * i; | |
// turn if we're past turning point or at a dead end | |
if ((!turn && count >= turnBlocks) || !posList.Contains(Tuple.Create(x0, y0))) | |
{ | |
turn = true; | |
dir = Tuple.Create(-dir.Item2, dir.Item1); // rotate | |
i = 1; | |
// stop if we've come full circle | |
if (origDir.Equals(dir)) | |
{ | |
break; | |
} | |
else | |
{ | |
continue; | |
} | |
} | |
// add wall block and grow to fill gaps | |
if (!IsWallBlockFilled(x0, y0)) | |
{ | |
AddWallBlock(x0, y0); | |
count += 1 + ExpandWall(x0, y0); | |
} | |
i++; | |
} | |
extendLines = ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); | |
} | |
// print the map states after each phase for debugging | |
if (verbose) | |
{ | |
Console.WriteLine("added block at {0},{1}", x, y); | |
for (int a = 0; a < firstLines.Length; a++) | |
{ | |
Console.WriteLine(firstLines[a] + " " + growLines[a] + " " + extendLines[a]); | |
} | |
} | |
return true; | |
} | |
// Helper method to convert the tile string format | |
private static string FormatMapStr(string tileStr, string format) | |
{ | |
// Implement the format conversion logic here | |
// For example, you can replace '.' with format | |
// based on your requirements. | |
// This is a placeholder for your implementation. | |
return tileStr; | |
} | |
} |
Author
Pablito2020
commented
Sep 24, 2023
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment