MapBuilder.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Maze
{
    public class MapBuilder
    {
        private Random rng = new Random();

        [Flags]
        public enum Direction
        {
            None = 0,
            North = 1,
            South = 2,
            East = 4,
            West = 8,
        }

        public enum TileTypes
        {
            HallDeadEnd = 0,
            HallStraight = 1,
            HallCurved = 2,
            HallTee = 3,
            HallIntersection = 4,
            RoomDeadEnd = 5,
            RoomStraight = 6,
            RoomCurved = 7,
            RoomTee = 8,
            RoomIntersection = 9,
            RoomVault = 10,
        }

        private class Point
        {
            public int x { get; set; }
            public int y { get; set; }
            public Point(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
            public override bool Equals(object obj)
            {
                return obj is Point ? (obj as Point).x == this.x && (obj as Point).y == y : false;
            }
        }

        //─│┌┐└┘├┤┬┴┼˂˃˄˅═║╔╗╚╝╠╣╦╩╬□█
        //─│┌┐└┘├┤┬┴┼<>^v═║╔╗╚╝╠╣╦╩╬o#
        private const string MasterTiles = "─│┌┐└┘├┤┬┴┼<>^v═║╔╗╚╝╠╣╦╩╬$.";
        private static char HallStraightCharEW = MasterTiles[0];
        private static char HallStraightCharNS = MasterTiles[1];
        private static char HallCurvedCharSE = MasterTiles[2];
        private static char HallCurvedCharSW = MasterTiles[3];
        private static char HallCurvedCharNE = MasterTiles[4];
        private static char HallCurvedCharNW = MasterTiles[5];
        private static char HallTeeCharNSE = MasterTiles[6];
        private static char HallTeeCharNSW = MasterTiles[7];
        private static char HallTeeCharSEW = MasterTiles[8];
        private static char HallTeeCharNEW = MasterTiles[9];
        private static char HallIntersectionChar = MasterTiles[10];
        private static char RoomDeadEndCharW = MasterTiles[11];
        private static char RoomDeadEndCharE = MasterTiles[12];
        private static char RoomDeadEndCharN = MasterTiles[13];
        private static char RoomDeadEndCharS = MasterTiles[14];
        private static char RoomStraightCharEW = MasterTiles[15];
        private static char RoomStraightCharNS = MasterTiles[16];
        private static char RoomCurvedCharSE = MasterTiles[17];
        private static char RoomCurvedCharSW = MasterTiles[18];
        private static char RoomCurvedCharNE = MasterTiles[19];
        private static char RoomCurvedCharNW = MasterTiles[20];
        private static char RoomTeeCharNSE = MasterTiles[21];
        private static char RoomTeeCharNSW = MasterTiles[22];
        private static char RoomTeeCharSEW = MasterTiles[23];
        private static char RoomTeeCharNEW = MasterTiles[24];
        private static char RoomIntersectionChar = MasterTiles[25];
        private static char RoomVaultChar = MasterTiles[26];
        private static char HallDeadEndChar = MasterTiles[27];


        private static char[] HallDeadEnd = new char[] { HallDeadEndChar };
        private static char[] HallStraight = new char[] { HallStraightCharEW, HallStraightCharNS };
        private static char[] HallCurved =
 new char[] { HallCurvedCharSE, HallCurvedCharSW, HallCurvedCharNE, HallCurvedCharNW };
        private static char[] HallTee =
 new char[] { HallTeeCharNSE, HallTeeCharNSW, HallTeeCharSEW, HallTeeCharNEW };
        private static char[] HallIntersection = new char[] { HallIntersectionChar };
        private static char[] RoomDeadEnd =
 new char[] { RoomDeadEndCharW, RoomDeadEndCharE, RoomDeadEndCharN, RoomDeadEndCharS };
        private static char[] RoomStraight = new char[] { RoomStraightCharEW, RoomStraightCharNS };
        private static char[] RoomCurved =
 new char[] { RoomCurvedCharSE, RoomCurvedCharSW, RoomCurvedCharNE, RoomCurvedCharNW };
        private static char[] RoomTee =
 new char[] { RoomTeeCharNSE, RoomTeeCharNSW, RoomTeeCharSEW, RoomTeeCharNEW };
        private static char[] RoomIntersection = new char[] { RoomIntersectionChar };
        private static char[] RoomVault = new char[] { RoomVaultChar };
        private static Dictionary<TileTypes, char[]> Tiles = new Dictionary<TileTypes, char[]>
        {
            { TileTypes.HallDeadEnd, HallDeadEnd},
            { TileTypes.HallStraight, HallStraight}, 
            { TileTypes.HallCurved, HallCurved}, 
            { TileTypes.HallTee, HallTee}, 
            { TileTypes.HallIntersection, HallIntersection},
            { TileTypes.RoomDeadEnd, RoomDeadEnd}, 
            { TileTypes.RoomStraight, RoomStraight}, 
            { TileTypes.RoomCurved, RoomCurved}, 
            { TileTypes.RoomTee, RoomTee}, 
            { TileTypes.RoomIntersection, RoomIntersection},
            { TileTypes.RoomVault, RoomVault},
        };

        private static Dictionary<Direction, char[]> HallDeadEndDirectionToChar =
 new Dictionary<Direction, char[]> { 
            { Direction.North, new char[] { HallDeadEndChar } },
            { Direction.South, new char[] { HallDeadEndChar } },
            { Direction.East, new char[] { HallDeadEndChar } },
            { Direction.West, new char[] { HallDeadEndChar } },
        };
        private static Dictionary<Direction, char[]> HallStraightDirectionToChar =
 new Dictionary<Direction, char[]> {
            { Direction.North, new char[] { HallStraightCharNS } },
            { Direction.South, new char[] { HallStraightCharNS } },
            { Direction.East, new char[] { HallStraightCharEW } },
            { Direction.West, new char[] { HallStraightCharEW } },
        };
        private static Dictionary<Direction, char[]> HallCurvedDirectionToChar =
 new Dictionary<Direction, char[]> {
            { Direction.North, new char[] { HallCurvedCharNE, HallCurvedCharNW } },
            { Direction.South, new char[] { HallCurvedCharSE, HallCurvedCharSW } },
            { Direction.East, new char[] { HallCurvedCharSE, HallCurvedCharNE } },
            { Direction.West, new char[] { HallCurvedCharSW, HallCurvedCharNW } },
        };
        private static Dictionary<Direction, char[]> HallTeeDirectionToChar =
 new Dictionary<Direction, char[]> {            
            { Direction.North, new char[] { HallTeeCharNSE, HallTeeCharNSW, HallTeeCharNEW } },
            { Direction.South, new char[] { HallTeeCharNSE, HallTeeCharNSW, HallTeeCharSEW } },
            { Direction.East, new char[] { HallTeeCharNSE, HallTeeCharSEW, HallTeeCharNEW } },
            { Direction.West, new char[] { HallTeeCharNSW, HallTeeCharSEW, HallTeeCharNEW } },
        };
        private static Dictionary<Direction, char[]> HallIntersectionDirectionToChar =
 new Dictionary<Direction, char[]> {        
            { Direction.North, new char[] { HallIntersectionChar } },
            { Direction.South, new char[] { HallIntersectionChar } },
            { Direction.East, new char[] { HallIntersectionChar } },
            { Direction.West, new char[] { HallIntersectionChar } },            
        };
        private static Dictionary<Direction, char[]> RoomDeadEndDirectionToChar =
 new Dictionary<Direction, char[]> {                
            { Direction.North, new char[] { RoomDeadEndCharN } },
            { Direction.South, new char[] { RoomDeadEndCharS } },
            { Direction.East, new char[] { RoomDeadEndCharE } },
            { Direction.West, new char[] { RoomDeadEndCharW } },                        
        };
        private static Dictionary<Direction, char[]> RoomStraightDirectionToChar =
 new Dictionary<Direction, char[]> {
            { Direction.North, new char[] { RoomStraightCharNS } },
            { Direction.South, new char[] { RoomStraightCharNS } },
            { Direction.East, new char[] { RoomStraightCharEW } },
            { Direction.West, new char[] { RoomStraightCharEW } },            
        };
        private static Dictionary<Direction, char[]> RoomCurvedDirectionToChar =
 new Dictionary<Direction, char[]> {
            { Direction.North, new char[] { RoomCurvedCharNE, RoomCurvedCharNW } },
            { Direction.South, new char[] { RoomCurvedCharSE, RoomCurvedCharSW } },
            { Direction.East, new char[] { RoomCurvedCharSE, RoomCurvedCharNE } },
            { Direction.West, new char[] { RoomCurvedCharSW, RoomCurvedCharNW } },                        
        };
        private static Dictionary<Direction, char[]> RoomTeeDirectionToChar =
 new Dictionary<Direction, char[]> {
            { Direction.North, new char[] { RoomTeeCharNSE, RoomTeeCharNSW, RoomTeeCharNEW } },
            { Direction.South, new char[] { RoomTeeCharNSE, RoomTeeCharNSW, RoomTeeCharSEW } },
            { Direction.East, new char[] { RoomTeeCharNSE, RoomTeeCharSEW, RoomTeeCharNEW } },
            { Direction.West, new char[] { RoomTeeCharNSW, RoomTeeCharSEW, RoomTeeCharNEW } },                        
        };
        private static Dictionary<Direction, char[]> RoomIntersectionDirectionToChar =
 new Dictionary<Direction, char[]> {
            { Direction.North, new char[] { RoomIntersectionChar } },
            { Direction.South, new char[] { RoomIntersectionChar } },
            { Direction.East, new char[] { RoomIntersectionChar } },
            { Direction.West, new char[] { RoomIntersectionChar } },                        
        };
        private static Dictionary<Direction, char[]> RoomVaultDirectionToChar =
 new Dictionary<Direction, char[]> {
            { Direction.None, new char[] { RoomVaultChar } },            
        };
        private static Dictionary<char, Direction> TilesToDirections = new Dictionary<char, Direction>
        {
            {'\0', Direction.None },
            {HallStraightCharEW, Direction.East | Direction.West },
            {HallStraightCharNS, Direction.North | Direction.South },
            {HallCurvedCharSE, Direction.South | Direction.East },
            {HallCurvedCharSW, Direction.South | Direction.West },
            {HallCurvedCharNE, Direction.North | Direction.East },
            {HallCurvedCharNW, Direction.North | Direction.West },
            {HallTeeCharNSE, Direction.North | Direction.South | Direction.East },
            {HallTeeCharNSW, Direction.North | Direction.South | Direction.West },
            {HallTeeCharSEW, Direction.East | Direction.South | Direction.West },
            {HallTeeCharNEW, Direction.East | Direction.North | Direction.West },
            {HallIntersectionChar, Direction.North | Direction.South | Direction.East | Direction.West },
            {RoomDeadEndCharW, Direction.West },
            {RoomDeadEndCharE, Direction.East },
            {RoomDeadEndCharN, Direction.North },
            {RoomDeadEndCharS, Direction.South },
            {RoomStraightCharEW, Direction.East | Direction.West },
            {RoomStraightCharNS, Direction.North | Direction.South },
            {RoomCurvedCharSE, Direction.South | Direction.East },
            {RoomCurvedCharSW, Direction.South | Direction.West },
            {RoomCurvedCharNE, Direction.North | Direction.East },
            {RoomCurvedCharNW, Direction.North | Direction.West },
            {RoomTeeCharNSE, Direction.North | Direction.South | Direction.East },
            {RoomTeeCharNSW, Direction.North | Direction.South | Direction.West },
            {RoomTeeCharSEW, Direction.East | Direction.South | Direction.West },
            {RoomTeeCharNEW, Direction.East | Direction.North | Direction.West },
            {RoomIntersectionChar, Direction.North | Direction.South | Direction.East | Direction.West },
            {RoomVaultChar, Direction.None },
            {HallDeadEndChar, Direction.None },
        };

        private char[,] Level;
        private Stack<Point> FutureEndpoints = new Stack<Point>();

        public void Build(int xSize, int ySize, int depth)
        {
            for (int j = 0; j < depth; j++)
            {
                Level = new char[xSize, ySize];

                TileTypes nextType = GetRoomType();

                Point loc = new Point(rng.Next(0, xSize), rng.Next(0, ySize));

                Direction restrictions = Direction.None;

                Stack<TileTypes> LevelTiles =
 new Stack<TileTypes>(GetLevelTiles(xSize * ySize - 1, depth));

                for (int i = 1; i < xSize * ySize; i++)
                {
                    //Console.Out.WriteLine(string.Format("Found {0}.", nextType.ToString()));
                    PlaceTile(nextType, loc, restrictions);
                    //PrintMap();

                    FindFutureEndpoints(loc);

                    if (FutureEndpoints.Count == 0)
                    {
                        FutureEndpoints.Push(FindSecretPassage());
                        //Console.Out.WriteLine("Found secret passage.");
                    }

                    loc = FutureEndpoints.Pop();

                    restrictions = FindRestrictions(loc);

                    nextType = LevelTiles.Pop();
                }

                //Console.Out.WriteLine(string.Format("Found {0}.", nextType.ToString()));
                PlaceTile(nextType, loc, restrictions);
                PrintMap();
                Console.Out.WriteLine();
            }
        }
        
        private IEnumerable<TileTypes> GetLevelTiles(int count, int depth)
        {
            List<TileTypes> tts = new List<TileTypes>();

            if (rng.Next(100) > 35) { tts.Add(TileTypes.RoomVault); }
            tts.Add(GetRoomType());

            while (tts.Count < count)
            {
                tts.Add(rng.Next(0, 12) < 7 ? GetRoomType() : GetHallType());
            }

            tts.Shuffle();
            return tts;
        }

        private void PrintMap()
        {
            for (int y = 0; y < Level.GetLength(1); y++)
            {
                for (int x = 0; x < Level.GetLength(0); x++)
                {
                    Console.Out.Write(Level[x, y]);
                }
                Console.Out.WriteLine();
            }
        }

        private Point FindSecretPassage()
        {
            for (int y = 0; y < Level.GetLength(1); y++)
            {
                for (int x = 0; x < Level.GetLength(0); x++)
                {
                    if (Level[x, y] == '\0' &&
                        ((IsLegalX(x + 1) && Level[x + 1, y] != '\0') ||
                        (IsLegalX(x - 1) && Level[x - 1, y] != '\0') ||
                        (IsLegalY(y + 1) && Level[x, y + 1] != '\0') ||
                        (IsLegalY(y - 1) && Level[x, y - 1] != '\0')))
                    {
                        return new Point(x, y);
                    }
                }
            }

            throw new Exception("What?");
        }

        private Direction FindDesires(Point current)
        {
            return
                (IsLegalX(current.x + 1) && Level[current.x + 1, current.y] == '\0' ?
 Direction.East : Direction.None) |
                (IsLegalX(current.x - 1) && Level[current.x - 1, current.y] == '\0' ?
 Direction.West : Direction.None) |
                (IsLegalY(current.y + 1) && Level[current.x, current.y + 1] == '\0' ?
 Direction.South : Direction.None) |
                (IsLegalY(current.y - 1) && Level[current.x, current.y - 1] == '\0' ?
 Direction.North : Direction.None);
        }

        private Direction FindRestrictions(Point current)
        {
            return
                (IsLegalX(current.x + 1) &&
 TilesToDirections[Level[current.x + 1, current.y]].HasFlag(Direction.West) ?
 Direction.East : Direction.None) |
                (IsLegalX(current.x - 1) &&
 TilesToDirections[Level[current.x - 1, current.y]].HasFlag(Direction.East) ?
 Direction.West : Direction.None) |
                (IsLegalY(current.y + 1) &&
 TilesToDirections[Level[current.x, current.y + 1]].HasFlag(Direction.North) ?
 Direction.South : Direction.None) |
                (IsLegalY(current.y - 1) &&
 TilesToDirections[Level[current.x, current.y - 1]].HasFlag(Direction.South) ?
 Direction.North : Direction.None);
        }

        private void FindFutureEndpoints(Point loc)
        {
            Direction directions = TilesToDirections[Level[loc.x, loc.y]];
            if (directions.HasFlag(Direction.North) && IsLegalLocation(loc.x, loc.y - 1))
            {
                AddEndpointIfNew(new Point(loc.x, loc.y - 1));
            } // Not an else if because a tile can have multiple flags set
            if (directions.HasFlag(Direction.South) && IsLegalLocation(loc.x, loc.y + 1))
            {
                AddEndpointIfNew(new Point(loc.x, loc.y + 1));
            }
            if (directions.HasFlag(Direction.East) && IsLegalLocation(loc.x + 1, loc.y))
            {
                AddEndpointIfNew(new Point(loc.x + 1, loc.y));
            }
            if (directions.HasFlag(Direction.West) && IsLegalLocation(loc.x - 1, loc.y))
            {
                AddEndpointIfNew(new Point(loc.x - 1, loc.y));
            }
        }

        private void AddEndpointIfNew(Point point)
        {
            if (!FutureEndpoints.Contains(point) && Level[point.x, point.y] == '\0')
            {
                FutureEndpoints.Push(point);
            }
        }

        private bool IsLegalLocation(int x, int y)
        {
            return x >= 0 && y >= 0 && x < Level.GetLength(0) && y < Level.GetLength(1);
        }

        private bool IsLegalX(int x)
        {
            return x >= 0 && x < Level.GetLength(0);
        }

        private bool IsLegalY(int y)
        {
            return y >= 0 && y < Level.GetLength(1);
        }

        private void PlaceTile(TileTypes nextType, Point loc, Direction direction = Direction.None)
        {
            var tiles = Tiles[nextType];
            var vals = new Tuple<int, int>[tiles.Length];
            int highVal = 0;
            for (int i = 0; i < tiles.Length; i++)
            {
                var tile = tiles[i];
                vals[i] = new Tuple<int, int>(i, (int)(TilesToDirections[tile] & direction));
                if (vals[i].Item2 > highVal) { highVal = vals[i].Item2; }
            }
            var highTiles = vals.Where(y => y.Item2 == highVal).Select(x => tiles[x.Item1]).ToArray();

            if (highTiles.Count() > 1)
            {
                var desire = FindDesires(loc);
                highVal = 0;
                vals = new Tuple<int, int>[highTiles.Length];
                for (int i = 0; i < highTiles.Length; i++)
                {
                    var tile = highTiles[i];
                    vals[i] = new Tuple<int, int>(i, (int)(TilesToDirections[tile] & desire));
                    if (vals[i].Item2 > highVal) { highVal = vals[i].Item2; }
                }
                highTiles = vals.Where(y => y.Item2 == highVal).Select(x => highTiles[x.Item1]).ToArray();
            }

            Level[loc.x, loc.y] = SelectFromArray(highTiles);
        }

        private char SelectFromArray(char[] array)
        {
            return array[rng.Next(0, array.Length)];
        }

        private TileTypes GetRoomType()
        {
            return rng.Next(10) == 0 ? TileTypes.RoomDeadEnd : GetTile(6, 10);
        }

        private TileTypes GetHallType()
        {
            return rng.Next(10) == 0 ? TileTypes.HallDeadEnd : GetTile(1, 5);
        }

        private TileTypes GetTile(int low, int highEx)
        {
            return (TileTypes)rng.Next(low, highEx);
        }
    }
}

Comments

Popular posts from this blog

The marshmallow cream trainwreck

The start of morels - 2018

In which we blacken someone's name