Beispiel #1
0
        protected override void _runAlgorithm(IAlgorithmContext context)
        {
            DungeonTiles workingTiles = context.D.Tiles;

            bool[,] algMask = context.Mask;
            _ctx            = context;

            if (this.WallStrategy == WallFormation.Tiles)
            {
                throw new NotImplementedException();
            }

            bool[,] existingDataMask = new bool[workingTiles.Height, workingTiles.Width];

            for (int y = 0; y < workingTiles.Height; ++y)
            {
                for (int x = 0; x < workingTiles.Width; ++x)
                {
                    existingDataMask[y, x] = algMask[y, x] && (workingTiles[y, x].Physics == Tile.MoveType.Wall);
                }
            }

            // Prime the dungeon tiles by opening them all up (as appropriate).
            // "Ignore" shouldn't wipe existing data, but...
            switch (BuildStrategy)
            {
            case ExistingDataHandling.Ignore:
            case ExistingDataHandling.Avoid:
                workingTiles.SetAllToo(Tile.MoveType.Open_HORIZ, existingDataMask);
                break;

            case ExistingDataHandling.Overwrite:
                workingTiles.SetAllToo(Tile.MoveType.Open_HORIZ, algMask);
                break;

            default:
                throw new NotImplementedException();
            }

            // Run algorithm with the appropriate mask
            // ... "Ignore" SHOULD build walls over existing data
            Rectangle startRegion = new Rectangle(0, 0, workingTiles.Width, workingTiles.Height);

            switch (BuildStrategy)
            {
            case ExistingDataHandling.Avoid:
                this.Divide(workingTiles, startRegion, existingDataMask, algMask, context.R);
                break;

            case ExistingDataHandling.Ignore:
            case ExistingDataHandling.Overwrite:
                this.Divide(workingTiles, startRegion, algMask, algMask, context.R);
                break;

            default:
                throw new NotImplementedException();
            }

            _ctx = null;
        }
Beispiel #2
0
        protected override void _runAlgorithm(IAlgorithmContext context)
        {
            DungeonTiles workingDungeon = context.D.Tiles;
            ISet <Tile>  deadEnds       = new HashSet <Tile>();

            for (int i = 0; i < this.FillPasses; ++i)
            {
                for (int y = 0; y < workingDungeon.Height; ++y)
                {
                    for (int x = 0; x < workingDungeon.Width; ++x)
                    {
                        if (!context.Mask[y, x] || workingDungeon[y, x].Physics == Tile.MoveType.Wall)
                        {
                            continue;
                        }
                        bool physicsDeadEnd   = (workingDungeon[y, x].Physics.SidesOpened() == 1);
                        bool adjacentsdeadEnd = (workingDungeon.GetAdjacentOpensFor(x, y) == 1);
                        if (!physicsDeadEnd && !adjacentsdeadEnd)
                        {
                            continue;
                        }
                        workingDungeon[y, x].Physics = workingDungeon[y, x].Physics.CloseOff(Tile.MoveType.Open_HORIZ);
                    }
                }
                workingDungeon.Parent.CreateGroup(deadEnds);
            }
        }
    private void Awake()
    {
        GenerateDungeon();
        tileGenerator = GetComponent <DungeonTiles>();
        foreach (Room r in DungeonRooms)
        {
//          print("Tiling Room " + r);
            tileGenerator.GenerateDungeonTiles(r);
        }
    }
Beispiel #4
0
        /// <summary>
        /// Checks what color the top-left corner of tile x,y should be,
        /// and draws it at point px, py on the provided graphics object.
        /// </summary>
        private void DrawTopLeftPointFor(DungeonTiles tiles, int x, int y, int px, int py, Graphics g)
        {
            bool useWallColor = tiles.WallExists(x, y, Tile.MoveType.Open_NORTH) ||
                                tiles.WallExists(x, y, Tile.MoveType.Open_WEST) ||
                                tiles.WallExists(x - 1, y, Tile.MoveType.Open_NORTH) ||
                                tiles.WallExists(x, y - 1, Tile.MoveType.Open_WEST);

            Brush cornerBrush = useWallColor ? this.WallBorder_Brush : this.OpenBorder_Brush;

            g.FillRectangle(cornerBrush, px, py, 1, 1);
        }
Beispiel #5
0
        public Image Render(DungeonTiles tiles)
        {
            int tileSz_px   = this.TileSize_Pixels;
            int totalWidth  = (tiles.Width * tileSz_px) + 1;
            int totalHeight = (tiles.Height * tileSz_px) + 1;

            Image img = new Bitmap(totalWidth, totalHeight);

            using (Graphics g = Graphics.FromImage(img))
            {
                this.Render(tiles, g);
            }

            return(img);
        }
Beispiel #6
0
        public static Dungeon Generate(DungeonGeneratorOptions options, Dungeon starterDungeon = null)
        {
            List <AlgorithmRun> algRuns = new List <AlgorithmRun>();
            AlgorithmRandom     r       = AlgorithmRandom.RandomInstance();

            if (null != options)
            {
                if (options.Width == 0 || options.Height == 0)
                {
                    throw new ArgumentException("Neither Width nor Height can be 0");
                }

                algRuns.AddRange(options.AlgRuns);
            }

            // Input validation

            if (null == algRuns)
            {
                throw new ArgumentNullException();
            }

            // Prepare context for each algorithm run appropriately.

            Dungeon workingDungeon = PrepareWorkingDungeon(options, starterDungeon);

            // Prepare algorithm runs to work on the dungeon
            foreach (var run in algRuns)
            {
                run.PrepareFor(workingDungeon);
            }

            // Generate terrain
            DungeonTiles tiles = workingDungeon.Tiles;

            for (int i = 0; i < algRuns.Count; ++i)
            {
                bool        canSkip  = true;
                ISet <Tile> algTiles = new HashSet <Tile>();
                for (int y = 0; y < tiles.Height; ++y)
                {
                    for (int x = 0; x < tiles.Width; ++x)
                    {
                        if (algRuns[i].Context.Mask[y, x])
                        {
                            algTiles.Add(tiles[y, x]);
                            canSkip = false;
                        }
                    }
                }

                // If this algorithm is totally masked out, don't bother running it
                if (canSkip)
                {
                    continue;
                }

                if (null != options.Callbacks && options.Callbacks.Count > 0)
                {
                    foreach (var cb in options.Callbacks)
                    {
                        algRuns[i].Alg.AttachCallback(cb);
                    }
                }
                algRuns[i].RunAlgorithm();
                workingDungeon.Runs.Add(algRuns[i].ToInfo());
            }

            // TODO Generate infestations

            return(workingDungeon);
        }
Beispiel #7
0
        private void Divide(DungeonTiles d, Rectangle topRegion, bool[,] totalMask, bool[,] algMask, Random r)
        {
            if (null == d || null == topRegion || null == totalMask)
            {
                return;
            }
            Stack <Rectangle> subregions = new Stack <Rectangle>();

            subregions.Push(topRegion);
            if (null == r)
            {
                r = new Random();
            }

            while (subregions.Count > 0)
            {
                Rectangle currentRegion = subregions.Pop();

                int roomSize = this.RoomSize - (this.RoomSizeVariability / 2) + r.Next(this.RoomSizeVariability);
                roomSize = roomSize < 1 ? 1 : roomSize;

                if (currentRegion.Width == 1 || currentRegion.Height == 1)
                {
                    continue;
                }
                if (currentRegion.Width * currentRegion.Height <= roomSize)
                {
                    d.Parent.CreateGroup(currentRegion, TileCategory.Room);
                    continue;
                }

                bool doHoriz = DoUseHorizontalOrientation(currentRegion.Width, currentRegion.Height, r);

                // Origin of new wall - TODO is there more efficient math for this?
                int wx_offset_base = (int)((1.0 - Variability) / 2.0 * currentRegion.Width);
                int wy_offset_base = (int)((1.0 - Variability) / 2.0 * currentRegion.Height);
                int wx_offset_rand = ((int)Math.Floor(Variability * r.NextDouble() * (currentRegion.Width - 1)));
                int wy_offset_rand = ((int)Math.Floor(Variability * r.NextDouble() * (currentRegion.Height - 1)));
                int wx_offset      = wx_offset_base + wx_offset_rand;
                int wy_offset      = wy_offset_base + wy_offset_rand;
                int wx             = currentRegion.X + (doHoriz ? 0 : wx_offset);
                int wy             = currentRegion.Y + (doHoriz ? wy_offset : 0);

                // Directionality
                int dx = doHoriz ? 1 : 0;
                int dy = doHoriz ? 0 : 1;

                int len = doHoriz ? currentRegion.Width : currentRegion.Height;

                // Break new wall into as many sub-walls as are appropriate (based on mask)
                HashSet <List <Tile> > subWalls       = new HashSet <List <Tile> >();
                List <Tile>            currentSubWall = new List <Tile>();
                for (int i = 0; i < len; ++i, wx += dx, wy += dy)
                {
                    if (algMask[wy, wx] && (totalMask[wy, wx] || this.BuildStrategy == ExistingDataHandling.Ignore))
                    {
                        currentSubWall.Add(d[wy, wx]);
                    }
                    else
                    {
                        if (currentSubWall.Count > 0)
                        {
                            subWalls.Add(currentSubWall);
                            currentSubWall = new List <Tile>();
                        }
                        continue;
                    }
                }
                subWalls.Add(currentSubWall);

                Tile.MoveType newWalls = DetermineClosureMethod(doHoriz);

                // Build division wall, honoring mask & leaving a passage
                foreach (List <Tile> wall in subWalls)
                {
                    int passageIdx = r.Next(0, wall.Count);
                    for (int i = 0; i < wall.Count; ++i)
                    {
                        if (i == passageIdx)
                        {
                            continue;
                        }
                        wall[i].Physics = wall[i].Physics.CloseOff(newWalls);
                    }
                }

                // Now subdivide the two new regions
                Rectangle subregion1 = new Rectangle(
                    currentRegion.X,
                    currentRegion.Y,
                    doHoriz ? currentRegion.Width : wx - currentRegion.X + 1,
                    doHoriz ? wy - currentRegion.Y + 1 : currentRegion.Height);
                Rectangle subregion2 = new Rectangle(
                    doHoriz ? currentRegion.X : wx + 1,
                    doHoriz ? wy + 1 : currentRegion.Y,
                    doHoriz ? currentRegion.Width : currentRegion.X + currentRegion.Width - wx - 1,
                    doHoriz ? currentRegion.Y + currentRegion.Height - wy - 1 : currentRegion.Height);

                // Push the new subregions ont othe stack
                subregions.Push(subregion1);
                subregions.Push(subregion2);

                if (this.GroupForDebug)
                {
                    d.Parent.CreateGroup(subregion1);
                    d.Parent.CreateGroup(subregion2);
                }

                this.RunCallbacks(_ctx);
            }
        }
Beispiel #8
0
        protected override void _runAlgorithm(IAlgorithmContext context)
        {
            DungeonTiles workingTiles = context.D.Tiles;

            // Implemented via http://weblog.jamisbuck.org/2015/1/15/better-recursive-division-algorithm

            if (this.WallStrategy != WallFormation.Boundaries)
            {
                throw new NotImplementedException();
            }

            // CLOBBER!
            workingTiles.SetAllToo(Tile.MoveType.Open_HORIZ, context.Mask);

            Stack <Subregion> subregions = new Stack <Subregion>();

            // 1. Collect all the cells in the maze into a single region.
            Subregion topRegion = new Subregion(workingTiles.GetTilesIn(context.Mask));

            subregions.Push(topRegion);

            while (subregions.Count > 0)
            {
                Subregion parentRegion = subregions.Pop();

                int roomSize = this.RoomSize - (this.RoomSizeVariability / 2) + context.R.Next(this.RoomSizeVariability);
                roomSize = roomSize < 1 ? 1 : roomSize;

                if (parentRegion.Tiles.Count <= roomSize)
                {
                    if (roomSize > 1)
                    {
                        if (this.GroupRooms)
                        {
                            workingTiles.Parent.CreateGroup(parentRegion.Tiles.ToHashSet(), TileCategory.Room);
                        }
                    }
                    continue;
                }

                // 2. Split the region into two, using the following process:
                List <Tile> S = new List <Tile>();
                Dictionary <Tile, Subregion_Split> tileSubregions = new Dictionary <Tile, Subregion_Split>();
                foreach (Tile t in parentRegion.Tiles)
                {
                    tileSubregions[t] = Subregion_Split.NONE;
                }

                //   2.1  Choose two cells from the region at random as “seeds”.
                //        Identify one as subregion A and one as subregion B.
                //        Then put them into a set S.
                Tile randomSeed_A = parentRegion.Tiles.PickRandomly(context.R);
                Tile randomSeed_B = randomSeed_A; // Shouldn't be equal when we're done
                while (randomSeed_A == randomSeed_B)
                {
                    randomSeed_B = parentRegion.Tiles.PickRandomly(context.R);
                }
                tileSubregions[randomSeed_A] = Subregion_Split.A;
                tileSubregions[randomSeed_B] = Subregion_Split.B;
                S.Add(randomSeed_A);
                S.Add(randomSeed_B);

                while (S.Count > 0)
                {
                    //   2.2  Choose a cell at random from S. Remove it from the set.
                    Tile currentTile = S.PullRandomly(context.R);

                    //   2.3  For each of that cell’s neighbors, if the neighbor
                    //        is not already associated with a subregion, add it to S,
                    //        and associate it with the same subregion as the cell itself.
                    IList <Tile> nextAdjacents = currentTile
                                                 .GetAdjacents()
                                                 .Where(t => parentRegion.Tiles.Contains(t))
                                                 .ToList();
                    foreach (Tile t in nextAdjacents)
                    {
                        if (tileSubregions[t] == Subregion_Split.NONE)
                        {
                            S.Add(t);
                            tileSubregions[t] = tileSubregions[currentTile];
                        }
                    }
                } //   2.4  Repeat 2.2 and 2.3 until the entire region has been split into two.

                // 3. Construct a wall between the two regions by identifying cells
                //    in one region that have neighbors in the other region. Leave a
                //    gap by omitting the wall from one such cell pair.
                List <Tuple <Tile, Tile> > wallBoundaries = new List <Tuple <Tile, Tile> >();

                wallBoundaries.AddRange(
                    tileSubregions.Keys
                    // Safe to filter with hard-coded A/B due to associative property
                    .Where(k => tileSubregions[k] == Subregion_Split.A)
                    .SelectMany <Tile, Tuple <Tile, Tile> >(
                        t => t.GetAdjacents()
                        .Where(adj => parentRegion.Tiles.Contains(adj))
                        .Where(sr => tileSubregions[sr] == Subregion_Split.B)
                        .Select(t2 => new Tuple <Tile, Tile>(t, t2))));

                // Leave as many gaps opened as requested
                int maxGaps = Math.Min(
                    this.GapCount,
                    (int)Math.Ceiling(wallBoundaries.Count * MaxGapProportion));
                for (int i = 0; i < maxGaps; ++i)
                {
                    wallBoundaries.PullRandomly(context.R);
                }

                foreach (Tuple <Tile, Tile> pair in wallBoundaries)
                {
                    Tile.MoveType touchingBoundary = workingTiles.GetCardinality(pair.Item1, pair.Item2);
                    pair.Item1.Physics = pair.Item1.Physics.CloseOff(touchingBoundary);
                }

                ISet <Tile> subregion_A = parentRegion.Tiles.Where(t => tileSubregions[t] == Subregion_Split.A).ToHashSet();
                ISet <Tile> subregion_B = parentRegion.Tiles.Where(t => tileSubregions[t] == Subregion_Split.B).ToHashSet();

                if (this.GroupForDebug)
                {
                    workingTiles.Parent.CreateGroup(subregion_A);
                    workingTiles.Parent.CreateGroup(subregion_B);
                }

                this.RunCallbacks(context);

                // Push new subregions to the stack
                subregions.Push(new Subregion(subregion_A));
                subregions.Push(new Subregion(subregion_B));
            } // 4. Repeat 2 and 3 for each subregion
        }
Beispiel #9
0
        protected override void _runAlgorithm(IAlgorithmContext context)
        {
            DungeonTiles workingTiles = context.D.Tiles;

            bool[,] algMask = context.Mask;

            if (this.ClearArea)
            {
                workingTiles.SetAllToo(Tile.MoveType.Wall, algMask);
            }

            bool[,] isExplored = (bool[, ])algMask.Clone();
            for (int y = 0; y < workingTiles.Height; ++y)
            {
                for (int x = 0; x < workingTiles.Width; ++x)
                {
                    isExplored[y, x] = !algMask[y, x];
                }
            }

            // If appropriate, mask out already-opened tiles
            if (!this.ClearArea && this.AvoidOpen)
            {
                for (int y = 0; y < workingTiles.Height; ++y)
                {
                    for (int x = 0; x < workingTiles.Width; ++x)
                    {
                        if (algMask[y, x] && workingTiles[y, x].Physics != Tile.MoveType.Wall)
                        {
                            isExplored[y, x] = true;
                        }
                    }
                }
            }

            // Create a pool of origins
            List <Point> unmaskedPoints = new List <Point>();

            for (int y = 0; y < isExplored.GetLength(0); ++y)
            {
                for (int x = 0; x < isExplored.GetLength(1); ++x)
                {
                    // If it's not masked out, add it to the pool of potential
                    // starting points
                    if (!isExplored[y, x])
                    {
                        unmaskedPoints.Add(new Point(x, y));
                    }
                }
            }

            // Only odd coordinates are valid starts due to limitations of algorithm below
            Predicate <Point> oddPoints          = p => p.X % 2 != 0 && p.Y % 2 != 0;
            Predicate <Point> paddedWithinBorder = p => p.X > BorderPadding && p.Y > BorderPadding && p.X < isExplored.GetLength(1) - BorderPadding && p.Y < isExplored.GetLength(0) - BorderPadding;
            List <Point>      originPool         = unmaskedPoints.FindAll(oddPoints).FindAll(paddedWithinBorder);

            if (originPool.Count == 0)
            {
                return;
            }

            int currentRooms   = 0;
            int currentAttempt = 0;

            while (++currentAttempt <= this.Attempts && currentRooms < this.TargetRoomCount)
            {
                int   originIdx  = context.R.Next(originPool.Count);
                Point nextOrigin = originPool[originIdx];

                int y = nextOrigin.Y;
                int x = nextOrigin.X;
                int w = context.R.Next(this.RoomWidthMin, this.RoomWidthMax) - 1 | 0x1;
                int h = context.R.Next(this.RoomHeightMin, this.RoomHeightMax) - 1 | 0x1;

                if (!workingTiles.TileIsValid(x, y) || !workingTiles.TileIsValid(x, y + h) || !workingTiles.TileIsValid(x + w, y) || !workingTiles.TileIsValid(x + w, y + h))
                {
                    continue;
                }

                bool overlapsOrAdjacent = false;
                for (int nuY = y - 1; nuY < y + h + 1; ++nuY)
                {
                    for (int nuX = x - 1; nuX < x + w + 1; ++nuX)
                    {
                        if (!workingTiles.TileIsValid(nuX, nuY) || isExplored[nuY, nuX])
                        {
                            overlapsOrAdjacent = true;
                            break;
                        }
                    }
                    if (overlapsOrAdjacent)
                    {
                        break;
                    }
                }
                if (overlapsOrAdjacent)
                {
                    continue;             // Failed attempt
                }
                ISet <Tile> newRoom = new HashSet <Tile>();
                for (int nuY = y; nuY < y + h; ++nuY)
                {
                    for (int nuX = x; nuX < x + w; ++nuX)
                    {
                        isExplored[nuY, nuX]           = true;
                        workingTiles[nuY, nuX].Physics = workingTiles[nuY, nuX].Physics.OpenUp(Tile.MoveType.Open_HORIZ);
                        originPool.Remove(workingTiles[nuY, nuX].Location);
                        newRoom.Add(workingTiles[nuY, nuX]);
                    }
                }
                // Close off boundaries if appropriate
                if (this.WallStrategy == WallFormation.Boundaries)
                {
                    for (int nuY = y; nuY < y + h; ++nuY)
                    {
                        for (int nuX = x; nuX < x + w; ++nuX)
                        {
                            if (nuY == y)
                            {
                                workingTiles[nuY, nuX].Physics = workingTiles[nuY, nuX].Physics.CloseOff(Tile.MoveType.Open_NORTH);
                            }
                            if (nuX == x)
                            {
                                workingTiles[nuY, nuX].Physics = workingTiles[nuY, nuX].Physics.CloseOff(Tile.MoveType.Open_WEST);
                            }
                            if (nuY == y + h - 1)
                            {
                                workingTiles[nuY, nuX].Physics = workingTiles[nuY, nuX].Physics.CloseOff(Tile.MoveType.Open_SOUTH);
                            }
                            if (nuX == x + w - 1)
                            {
                                workingTiles[nuY, nuX].Physics = workingTiles[nuY, nuX].Physics.CloseOff(Tile.MoveType.Open_EAST);
                            }
                        }
                    }
                }

                // Rooms should not be orphaned!
                workingTiles.Parent.CreateGroup(newRoom, TileCategory.Room);

                this.RunCallbacks(context);

                ++currentRooms;
            }
        }
Beispiel #10
0
        public void Render(DungeonTiles tiles, Graphics g)
        {
            if (tiles.IsHex)
            {
                throw new NotImplementedException("Hex tiles not supported yet.");
            }

            int tileSz_px   = (int)(g.VisibleClipBounds.Height / tiles.Height);
            int totalWidth  = (int)g.VisibleClipBounds.Width;
            int totalHeight = (int)g.VisibleClipBounds.Height;

            g.Clear(this.Background_Color);

            // Draw tiles based on their physics
            for (int y = 0; y < tiles.Height; ++y)
            {
                for (int x = 0; x < tiles.Height; ++x)
                {
                    Rectangle tileRect  = new Rectangle(x * tileSz_px, y * tileSz_px, tileSz_px, tileSz_px);
                    Brush     tileBrush = this.GetFillBrushFor(tiles[y, x].Physics);
                    g.FillRectangle(tileBrush, tileRect);
                }
            }

            // Draw grid lines based on tile physics
            if (!this.DrawBorders)
            {
                goto SkipBorders;
            }
            for (int y = 0; y < tiles.Height; ++y)
            {
                for (int x = 0; x < tiles.Height; ++x)
                {
                    int x1 = x * tileSz_px,
                        x2 = (x * tileSz_px) + tileSz_px,
                        y1 = y * tileSz_px,
                        y2 = (y * tileSz_px) + tileSz_px;

                    DrawBorderFor(tiles, x, y, Tile.MoveType.Open_HORIZ, g, x1, y1, x2, y2);
                }
            }
            for (int y = 0; y < tiles.Height; ++y)
            {
                for (int x = 0; x < tiles.Height; ++x)
                {
                    int px1 = x * tileSz_px,
                        px2 = (x * tileSz_px) + tileSz_px,
                        py1 = y * tileSz_px,
                        py2 = (y * tileSz_px) + tileSz_px;

                    DrawTopLeftPointFor(tiles, x, y, px1, py1, g);
                    // This is just to get the bottom-right border. Easier way? Sure. But this works too.
                    DrawTopLeftPointFor(tiles, x + 1, y, px2, py1, g);
                    DrawTopLeftPointFor(tiles, x, y + 1, px1, py2, g);
                    DrawTopLeftPointFor(tiles, x + 1, y + 1, px2, py2, g);
                }
            }
SkipBorders:

            if (!this.ShadeGroups)
            {
                goto SkipGroupShading;
            }
            Random r = new Random();

            for (int i = 0; i < tiles.Groups.Count; ++i)
            {
                Brush groupBrush = new SolidBrush(Color.FromArgb(32, r.Next(255), r.Next(255), r.Next(255)));
                foreach (var tile in tiles.Groups[i])
                {
                    Point     tileLoc  = tiles.TilesById[tile.Id];
                    Rectangle tileRect = new Rectangle(tileLoc.X * tileSz_px, tileLoc.Y * tileSz_px, tileSz_px, tileSz_px);
                    g.FillRectangle(groupBrush, tileRect);
                }
            }
SkipGroupShading:

            return;
        }
Beispiel #11
0
        /// <summary>
        /// Draws the border for tile (x, y) in the specified DungeonTiles,
        /// to the specified graphics and rectangle coordinates.
        /// </summary>
        /// <param name="tiles">The DungeonTiles for which this border is being drawn</param>
        /// <param name="x">The x-location of the tile whose border is being drawn</param>
        /// <param name="y">The x-location of the tile whose border is being drawn</param>
        /// <param name="moveDir">The move direction. Currently supports a single horizontal square direction, or all horizontal square directions, but no other combinations</param>
        /// <param name="g">The graphics on which to draw</param>
        /// <param name="x1">x1 of the tile's square in graphics.</param>
        /// <param name="y1">y1 of the tile's square in graphics.</param>
        /// <param name="x2">x2 of the tile's square in graphics.</param>
        /// <param name="y2">y2 of the tile's square in graphics.</param>
        private void DrawBorderFor(DungeonTiles tiles, int x, int y, Tile.MoveType moveDir, Graphics g, int x1, int y1, int x2, int y2)
        {
            if (tiles == null || g == null)
            {
                return;                       // no op
            }
            if (!tiles.TileIsValid(x, y))
            {
                return;
            }

            int adjacentTile_x = 0,
                adjacentTile_y = 0;

            // Determine the correct adjacent tile to check physics
            switch (moveDir)
            {
            case Tile.MoveType.Wall:
                return; // no op

            case Tile.MoveType.Open_NORTH:
                adjacentTile_x = x;
                adjacentTile_y = y - 1;
                break;

            case Tile.MoveType.Open_EAST:
                adjacentTile_x = x + 1;
                adjacentTile_y = y;
                break;

            case Tile.MoveType.Open_SOUTH:
                adjacentTile_x = x;
                adjacentTile_y = y + 1;
                break;

            case Tile.MoveType.Open_WEST:
                adjacentTile_x = x - 1;
                adjacentTile_y = y;
                break;

            case Tile.MoveType.Open_HORIZ:
                DrawBorderFor(tiles, x, y, Tile.MoveType.Open_NORTH, g, x1, y1, x2, y2);
                DrawBorderFor(tiles, x, y, Tile.MoveType.Open_EAST, g, x1, y1, x2, y2);
                DrawBorderFor(tiles, x, y, Tile.MoveType.Open_SOUTH, g, x1, y1, x2, y2);
                DrawBorderFor(tiles, x, y, Tile.MoveType.Open_WEST, g, x1, y1, x2, y2);
                return;

            default:
                throw new NotImplementedException("Unsupported Tile.MoveType specified");
            }

            // 1. Check if THIS cell is even open
            bool useWall = ((tiles[y, x].Physics & moveDir) == 0);

            // 2. Check if adjacent cell exists; if not, use a wall border
            useWall = (useWall || !tiles.TileIsValid(adjacentTile_x, adjacentTile_y));
            // 3. If adjacent cell exists, check if its corresponding wall is opened too; if not, use a wall border
            useWall = (useWall || (tiles[adjacentTile_y, adjacentTile_x].Physics & moveDir.GetOpposite()) == 0);

            if (this.WallBorder_Pen == null || this.OpenBorder_Pen == null)
            {
                throw new Exception("DungeonTileRenderer is in an invalid state: set WallBorder_Pen and OpenBorder_Pen");
            }

            Pen borderPen = useWall ? this.WallBorder_Pen : this.OpenBorder_Pen;

            // All this work, just to draw a single line.
            switch (moveDir)
            {
            case Tile.MoveType.Open_NORTH:
                g.DrawLine(borderPen, x1, y1, x2, y1);
                break;

            case Tile.MoveType.Open_EAST:
                g.DrawLine(borderPen, x2, y1, x2, y2);
                break;

            case Tile.MoveType.Open_SOUTH:
                g.DrawLine(borderPen, x1, y2, x2, y2);
                break;

            case Tile.MoveType.Open_WEST:
                g.DrawLine(borderPen, x1, y1, x1, y2);
                break;

            default:
                throw new ArgumentException("Unsupported moveDir to draw border");
            }
        }