Skip to content

Instantly share code, notes, and snippets.

@Pablito2020
Created September 24, 2023 18:50
Show Gist options
  • Save Pablito2020/18b7ab220493c2ec0fb460ef53fa5621 to your computer and use it in GitHub Desktop.
Save Pablito2020/18b7ab220493c2ec0fb460ef53fa5621 to your computer and use it in GitHub Desktop.
Map
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;
}
}
@Pablito2020
Copy link
Author

Pablito2020 commented Sep 24, 2023

using System;

class Program
{
    static void Main()
    {
        Map tileMap = new Map(16, 31, @"
        ||||||||||||||||
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |.........||||||
        |.........||||||
        |.........||||||
        |.........||||||
        |.........||||||
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        |...............
        ||||||||||||||||
        ");

        // verbosity option (-v)
        if (args.Length > 0 && args[0] == "-v")
        {
            tileMap.Verbose = true;
        }

        // generate map by adding walls until there's no more room
        while (tileMap.AddWallObstacle(extend: true))
        {
        }

        // reflect the first 14 columns to print the map
        string[] lines = tileMap.ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
        foreach (string line in lines)
        {
            string s = line.Substring(0, 14);
            Console.WriteLine(s + new string(s.Reverse().ToArray()));
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment