public static int SidesOpened(this Tile.MoveType t) { if (0 != (t & Tile.MoveType.Open_HEX)) { throw new NotImplementedException("don't support hex yet"); } if (t == Tile.MoveType.Open_HORIZ) { return(4); } int sidesOpened = 0; if (0 != (t & Tile.MoveType.Open_NORTH)) { ++sidesOpened; } if (0 != (t & Tile.MoveType.Open_EAST)) { ++sidesOpened; } if (0 != (t & Tile.MoveType.Open_SOUTH)) { ++sidesOpened; } if (0 != (t & Tile.MoveType.Open_WEST)) { ++sidesOpened; } return(sidesOpened); }
/// <summary> /// Gets the opposite MoveType to the one specified. Can do most /// hex directionality, but will get confused with hex horiz vs /// non-hex horiz. Will have to create a new func later to handle /// that case (maybe with specified "preferHex" bool?) /// </summary> /// <param name="t">The MoveType for which to find the opposite</param> /// <returns>The opposite MoveType</returns> public static Tile.MoveType GetOpposite(this Tile.MoveType t) { switch (t) { case Tile.MoveType.Wall: return(Tile.MoveType.Open_ALL); case Tile.MoveType.Open_NORTH: return(Tile.MoveType.Open_SOUTH); case Tile.MoveType.Open_EAST: return(Tile.MoveType.Open_WEST); case Tile.MoveType.Open_SOUTH: return(Tile.MoveType.Open_NORTH); case Tile.MoveType.Open_WEST: return(Tile.MoveType.Open_EAST); case Tile.MoveType.Open_HORIZ: return(Tile.MoveType.Open_VERT); case Tile.MoveType.Open_UP: return(Tile.MoveType.Open_DOWN); case Tile.MoveType.Open_DOWN: return(Tile.MoveType.Open_UP); case Tile.MoveType.Open_VERT: return(Tile.MoveType.Open_HORIZ); case Tile.MoveType.Open_ALL: return(Tile.MoveType.Wall); case Tile.MoveType.Open_HEX_NW: return(Tile.MoveType.Open_HEX_SE); case Tile.MoveType.Open_HEX_NE: return(Tile.MoveType.Open_HEX_SW); case Tile.MoveType.Open_HEX_SE: return(Tile.MoveType.Open_HEX_NW); case Tile.MoveType.Open_HEX_SW: return(Tile.MoveType.Open_HEX_NE); case Tile.MoveType.Open_HEX_HORIZ: throw new NotSupportedException("Can't do that, sorry"); default: throw new ArgumentException("Unknown Tile.MoveType"); } }
public void SetAllToo(Tile.MoveType physics, bool[,] mask = null) { for (int y = 0; y < this.Height; ++y) { for (int x = 0; x < this.Width; ++x) { if (null == mask || mask[y, x]) { this[y, x].Physics = physics; } } } }
public Brush GetFillBrushFor(Tile.MoveType physics) { if (physics == Tile.MoveType.Wall) { return(this.WallTile_Brush); } if ((physics & Tile.MoveType.Open_ALL) != 0) { // Open the tile up, and assume the border drawing will handle // open-ness return(this.OpenTile_Brush); } return(new SolidBrush(this.Background_Color)); }
/// <summary> /// Checks if a wall exists anywhere that prevents movement from the specified tile /// to the adjacent tile in the specified direction (including walls in that tile /// which would prevent movement). If the movement direction is not possible due to /// being at the edge of a map, returns true. If the specified tile is invalid, /// returns true. /// </summary> public bool WallExists(int x, int y, Tile.MoveType direction) { if (!TileIsValid(x, y)) { return(true); } if (0 == (this[y, x].Physics & direction)) { return(true); } switch (direction) { case Tile.MoveType.Wall: throw new ArgumentException(); case Tile.MoveType.Open_NORTH: return(!TileIsValid(x, y - 1) || (this[y - 1, x].Physics & direction.GetOpposite()) == 0); case Tile.MoveType.Open_EAST: return(!TileIsValid(x + 1, y) || (this[y, x + 1].Physics & direction.GetOpposite()) == 0); case Tile.MoveType.Open_SOUTH: return(!TileIsValid(x, y + 1) || (this[y + 1, x].Physics & direction.GetOpposite()) == 0); case Tile.MoveType.Open_WEST: return(!TileIsValid(x - 1, y) || (this[y, x - 1].Physics & direction.GetOpposite()) == 0); case Tile.MoveType.Open_HORIZ: return(WallExists(x, y, Tile.MoveType.Open_NORTH) && WallExists(x, y, Tile.MoveType.Open_EAST) && WallExists(x, y, Tile.MoveType.Open_SOUTH) && WallExists(x, y, Tile.MoveType.Open_WEST)); case Tile.MoveType.Open_UP: case Tile.MoveType.Open_DOWN: case Tile.MoveType.Open_VERT: case Tile.MoveType.Open_ALL: case Tile.MoveType.Open_HEX_NW: case Tile.MoveType.Open_HEX_NE: case Tile.MoveType.Open_HEX_SE: case Tile.MoveType.Open_HEX_SW: case Tile.MoveType.Open_HEX: case Tile.MoveType.Open_HEX_HORIZ: default: throw new NotImplementedException(); } }
public void ResetTiles(int width, int height, Tile.MoveType startingPhyics = Tile.MoveType.Wall) { Tile[,] tiles = new Tile[height, width]; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { tiles[y, x] = new Tile() { Parent = this, Physics = startingPhyics }; } } this.Tiles = tiles; this.IsHex = false; // TODO }
public DungeonTiles(int width, int height, Tile.MoveType startingPhyics = Tile.MoveType.Wall) { ResetTiles(width, height, startingPhyics); }
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); } }
public static Tile.MoveType OpenUp(this Tile.MoveType t, int direction) { return(t.OpenUp((Tile.MoveType)direction)); }
public static Tile.MoveType OpenUp(this Tile.MoveType t, Tile.MoveType direction) { return(t | direction); }
public static Tile.MoveType CloseOff(this Tile.MoveType t, int direction) { return(t.CloseOff((Tile.MoveType)direction)); }
public static Tile.MoveType CloseOff(this Tile.MoveType t, Tile.MoveType direction) { return(t & ~direction); }
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 }
/// <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"); } }