Moved Dungeon Map Generator to it's own solution.

This commit is contained in:
Max 2025-02-10 15:43:13 +01:00
parent 5c46281334
commit fd2a28afe5
50 changed files with 86389 additions and 84 deletions

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="mscorlib" publicKeyToken="b77a5c561934e089" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="netstandard" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System" publicKeyToken="b77a5c561934e089" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: b2976da120e9418ab6437c973c915dd7
timeCreated: 1738336012

View file

@ -1,607 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using Unity.VisualScripting;
using Unity.VisualScripting.Dependencies.NCalc;
using Random = System.Random;
namespace DungeonGenerator
{
public class DungeonGenerator
{
private const int SIDE_LENGTH_OF_MONSTER = 4;
private const int SIDE_LENGTH_OF_NORMAL = 2;
private const int SIDE_LENGTH_OF_ENTRANCE = 2;
private const int WIDTH_OF_BOSS = 10;
private const int HEIGHT_OF_BOSS = 6;
private int _xLength = 40;
private int _yLength = 28;
public DungeonMap GenerateDungeon(int length, float monsterRoomRatio)
{
_xLength = 40;
_yLength = 28;
Random random = new Random();
DungeonMap dungeonMap = new DungeonMap(_xLength, _yLength);
dungeonMap.AddRooms(GenerateEntranceRooms(_xLength, _yLength, random.Next(1,4)));
dungeonMap.AddRoom(GenerateOnlyBossRoom(_xLength, _yLength, WIDTH_OF_BOSS, HEIGHT_OF_BOSS));
EvenDisperser disperser = new EvenDisperser(_xLength, _yLength, dungeonMap.GetUnoccupiedPoints()); //TODO calculate L and W from length
int numberOfMonsterRooms = 7; // TODO: Calculate from ratio
for (var i = 0; i < numberOfMonsterRooms; i ++)
{
dungeonMap.AddRoom(disperser.GenerateAndPlaceRoom(
SIDE_LENGTH_OF_MONSTER,
SIDE_LENGTH_OF_MONSTER,
RoomType.Monster));
}
AddNormalRoomsAroundMonsterRooms(dungeonMap);
AddNormalRoomsAroundBossRoom(dungeonMap);
AddConnectionRooms(dungeonMap);
return dungeonMap;
}
private void AddConnectionRooms(DungeonMap dungeon)
{
// For each room adjacent to a monster and boos room connect it with the closest thing (entrance room, or monster adjacent room)
// unless there is something already going there.
List<Room> rootRooms = new List<Room>(dungeon.GetMonsterRooms());
rootRooms.Add(dungeon.GetBossRoom());
// Create list of connection rooms
Dictionary<RoomSide, List<Room>> nodeRoomAndSideItIsOn = dungeon.GetNodesWithSides();
List<Room> nodeRoomsWithEntrances = new List<Room>(dungeon.GetNodeRooms());
nodeRoomsWithEntrances.AddRange(dungeon.GetEntranceRooms());
// For each connection room that is not an entrance room,
// find the next closest uninterrupted connection room that is not on the same root room.
foreach (var roomSideWithRoom in nodeRoomAndSideItIsOn)
{
foreach (Room room in roomSideWithRoom.Value)
{
Room closestSeenRoom = FindClosestSeenConnection(room,
roomSideWithRoom.Key,
nodeRoomsWithEntrances,
rootRooms);
if (closestSeenRoom != null)
{
//Place rooms along the line until they are connected
dungeon.AddRooms(PlaceRoomsOrganicallyTowardRoom(room, closestSeenRoom, RoomType.Normal,
dungeon.GetOccupiedPoints(), dungeon.GetUnoccupiedPoints()));
}
}
}
}
private void AddNormalRoomsAroundBossRoom(DungeonMap dungeon)
{
AddNormalRoomsAroundRoom(dungeon.GetBossRoom(), dungeon);
}
private void AddNormalRoomsAroundMonsterRooms(DungeonMap dungeon)
{
foreach (Room monsterRoom in dungeon.GetMonsterRooms())
{
AddNormalRoomsAroundRoom(monsterRoom, dungeon);
}
}
private void AddNormalRoomsAroundRoom(Room room, DungeonMap dungeon)
{
Random random = new Random();
List<Point> topUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Top, dungeon.GetUnoccupiedPoints()));
List<Point> bottomUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Bottom, dungeon.GetUnoccupiedPoints()));
List<Point> leftUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Left, dungeon.GetUnoccupiedPoints()));
List<Point> rightUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Right, dungeon.GetUnoccupiedPoints()));
int minNecessaryAvailablePoints = (SIDE_LENGTH_OF_MONSTER + SIDE_LENGTH_OF_NORMAL) / 2;
List<RoomSide> availableSides = new List<RoomSide>();
if (topUnoccupiedPoints.Count >= minNecessaryAvailablePoints){availableSides.Add(RoomSide.Top);}
if (bottomUnoccupiedPoints.Count >= minNecessaryAvailablePoints){availableSides.Add(RoomSide.Bottom);}
if (leftUnoccupiedPoints.Count >= minNecessaryAvailablePoints){availableSides.Add(RoomSide.Left);}
if (rightUnoccupiedPoints.Count >= minNecessaryAvailablePoints){availableSides.Add(RoomSide.Right);}
// Ensure between 3 and 4 rooms are added
int numRoomsToAdd = Math.Min(availableSides.Count, random.Next(3, 5));
// Randomly shuffle and take the required number of sides
foreach (RoomSide side in availableSides.OrderBy(_ => random.Next()).Take(numRoomsToAdd))
{
List<Point> unoccupiedPointsOnSide = side switch
{
RoomSide.Top => topUnoccupiedPoints,
RoomSide.Bottom => bottomUnoccupiedPoints,
RoomSide.Left => leftUnoccupiedPoints,
RoomSide.Right => rightUnoccupiedPoints,
_ => throw new ArgumentOutOfRangeException(nameof(side), $"Unexpected RoomSide value: {side}")
};
// Create room and add it if valid
Room newRoom = CreateAdjacentRoom(RoomType.Normal, unoccupiedPointsOnSide, side, dungeon.GetOccupiedPoints());
if (newRoom != null)
{
room.AddAdjacentRoom(newRoom, side);
dungeon.AddRoom(newRoom);
}
}
}
private List<Point> GetUnoccupiedPointsOnSide(Room room, RoomSide side, HashSet<Point> unoccupiedPoints)
{
List<Point> unoccupiedPointsOnSide = new List<Point>();
var roomPoints = room.GetPointsInRoom();
int minX = roomPoints.Min(p => p.X);
int maxX = roomPoints.Max(p => p.X);
int minY = roomPoints.Min(p => p.Y);
int maxY = roomPoints.Max(p => p.Y);
switch (side)
{
case RoomSide.Top:
for (int x = minX - SIDE_LENGTH_OF_NORMAL; x <= maxX + SIDE_LENGTH_OF_NORMAL; x++)
{
Point point = new Point(x, minY - SIDE_LENGTH_OF_NORMAL);
if (unoccupiedPoints.Contains(point))
{
unoccupiedPointsOnSide.Add(point);
}
}
return unoccupiedPointsOnSide;
case RoomSide.Bottom:
for (int x = minX - SIDE_LENGTH_OF_NORMAL; x <= maxX + SIDE_LENGTH_OF_NORMAL; x++)
{
Point point = new Point(x, maxY + SIDE_LENGTH_OF_NORMAL);
if (unoccupiedPoints.Contains(point))
{
unoccupiedPointsOnSide.Add(point);
}
}
return unoccupiedPointsOnSide;
case RoomSide.Left:
for (int y = minY - SIDE_LENGTH_OF_NORMAL; y <= maxY + SIDE_LENGTH_OF_NORMAL; y++)
{
Point point = new Point(minX - SIDE_LENGTH_OF_NORMAL, y);
if (unoccupiedPoints.Contains(point))
{
unoccupiedPointsOnSide.Add(point);
}
}
return unoccupiedPointsOnSide;
case RoomSide.Right:
for (int y = minY - SIDE_LENGTH_OF_NORMAL; y <= maxY + SIDE_LENGTH_OF_NORMAL; y++)
{
Point point = new Point(maxX + SIDE_LENGTH_OF_NORMAL, y);
if (unoccupiedPoints.Contains(point))
{
unoccupiedPointsOnSide.Add(point);
}
}
return unoccupiedPointsOnSide;
default:
throw new ArgumentException("Side not recognized");
}
}
private Room CreateAdjacentRoom(RoomType type, List<Point> unoccupiedPointsOnSide, RoomSide side, HashSet<Point> occupiedPoints)
{
int sizeOfNewRoom = GetRoomSizeByType(type);
Random random = new Random();
// Sort points by their coordinate based on the side of placement
var orderedPoints = side == RoomSide.Left || side == RoomSide.Right
? unoccupiedPointsOnSide.OrderBy(p => p.Y).ToList() // Sort by Y for vertical placement
: unoccupiedPointsOnSide.OrderBy(p => p.X).ToList();
// List to store possible valid room placements (top-left points of the room)
List<Point> validPlacements = new List<Point>();
// Iterate over the ordered points to find potential placements
for (int i = 0; i < orderedPoints.Count - sizeOfNewRoom + 1; i++)
{
// Get a sequence of SIZE_OF_ROOM points starting from index i
var potentialPoints = orderedPoints.Skip(i).Take(sizeOfNewRoom).ToList();
// Check if all points are consecutive and fit within the range
if (IsRoomWideEnough(potentialPoints, side))
{
Point firstPoint = potentialPoints.First();
// Adjust the first point for different sides
Point adjustedPoint = side switch
{
RoomSide.Top => new Point(firstPoint.X, firstPoint.Y), // Move up for top side
RoomSide.Bottom => new Point(firstPoint.X, firstPoint.Y - 1), // Move down for bottom side
RoomSide.Left => new Point(firstPoint.X, firstPoint.Y), // Move left for left side
RoomSide.Right => new Point(firstPoint.X - 1, firstPoint.Y), // Move right for right side
_ => firstPoint // Default case (shouldn't happen)
};
if (!potentialPoints.Any(occupiedPoints.Contains))
{
validPlacements.Add(adjustedPoint); // First point is the top-left point of the room
}
}
}
// If there are valid placements, select a random one and create the room
if (validPlacements.Any())
{
Point randomPlacement = validPlacements[random.Next(validPlacements.Count)];
Room newRoom = new Room(type, sizeOfNewRoom, sizeOfNewRoom, randomPlacement);
return newRoom;
}
// If no valid placements are found, return null or handle it as necessary
return null;
}
// Helper method to check if a sequence of points is wide enough (consecutive points)
private bool IsRoomWideEnough(List<Point> points, RoomSide side)
{
for (int i = 1; i < points.Count; i++)
{
if (side == RoomSide.Left || side == RoomSide.Right)
{
// Check if points are consecutive along the Y-axis for left/right side
if (points[i].Y != points[i - 1].Y + 1)
{
return false;
}
}
else
{
// Check if points are consecutive along the X-axis for top/bottom side
if (points[i].X != points[i - 1].X + 1)
{
return false;
}
}
}
return true;
}
private int GetRoomSizeByType(RoomType type)
{
switch (type)
{
case RoomType.Entrance:
return SIDE_LENGTH_OF_ENTRANCE;
case RoomType.Monster:
return
SIDE_LENGTH_OF_MONSTER;
case RoomType.Normal:
return SIDE_LENGTH_OF_NORMAL;
default:
return SIDE_LENGTH_OF_NORMAL;
}
}
private Room GenerateOnlyBossRoom(int xLengthOfDungeon, int yLengthOfDungeon, int width, int heigth)
{
// Place monster room in the middle third
int middleAreaX = xLengthOfDungeon / 3;
int middleAreaY = yLengthOfDungeon / 3;
Random random = new Random();
int bossX = middleAreaX + random.Next(0, middleAreaX - width);
int bossY = middleAreaY + random.Next(0, middleAreaY - heigth);
return new Room(RoomType.Boss, width, heigth, new Point(bossX, bossY));
}
private enum Side
{
Top,
Right,
Bottom,
Left
}
private List<Room> GenerateEntranceRooms(int xLengthOfDungeon, int yLengthOfDungeon, int numberOfEntranceLines)
{
if (numberOfEntranceLines > 4)
{
throw new Exception("Number of entrance lines cannot be greater than 4");
}
const int numEntranceRooms = 12;
List<Room> entranceRooms = new List<Room>();
Random random = new Random();
Dictionary<Side, int> sidesToEnterOn = new()
{
{ Side.Top, 0},
{ Side.Right, 0},
{ Side.Bottom, 0},
{ Side.Left, 0}
};
// Randomly assign starting lines to sides of dungeon
for (var i = 0; i < numberOfEntranceLines; i++)
{
while (true)
{
Side side = (Side)random.Next(0, 4);
if (sidesToEnterOn[side] < 2)
{
sidesToEnterOn[side] += 1;
break;
}
}
}
int roomsPerLine = numEntranceRooms / numberOfEntranceLines;
int bottom = yLengthOfDungeon - SIDE_LENGTH_OF_NORMAL;
int oneRoomFromBottom = bottom - SIDE_LENGTH_OF_NORMAL;
int right = xLengthOfDungeon - SIDE_LENGTH_OF_NORMAL;
// Generate entrance lines on each side
switch (sidesToEnterOn[Side.Top])
{
case 2:
// Add half of points for this entrance line to the top left side
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(i,0)));
}
// Add the rest of the points for this entrance line to the top right side.
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(right - i,0)));
}
break;
case 1:
int startOfLine = GetStartOfCenteredLine(xLengthOfDungeon, roomsPerLine, SIDE_LENGTH_OF_NORMAL);
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL,new Point(startOfLine + i, 0)));
}
break;
}
switch (sidesToEnterOn[Side.Right])
{
case 2:
// Add points for this entrance line to the top right side
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(right, i + SIDE_LENGTH_OF_NORMAL)));
}
// Add the rest of the points for this entrance line to the bottom right side.
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(right, oneRoomFromBottom - i)));
}
break;
case 1:
int startOfLine = GetStartOfCenteredLine(yLengthOfDungeon, roomsPerLine, SIDE_LENGTH_OF_NORMAL);
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(right, startOfLine + i)));
}
break;
}
switch (sidesToEnterOn[Side.Bottom])
{
case 2:
// Add half of points for this entrance line to the bottom left side
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(i, bottom)));
}
// Add the rest of the points for this entrance line to the bottom right side.
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point((xLengthOfDungeon - 1) - i, bottom)));
}
break;
case 1:
int startOfLine = GetStartOfCenteredLine(xLengthOfDungeon, roomsPerLine, SIDE_LENGTH_OF_NORMAL);
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(startOfLine + i, bottom)));
}
break;
}
switch (sidesToEnterOn[Side.Left])
{
case 2:
// Add half of points for this entrance line to the top left side
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(0, i + SIDE_LENGTH_OF_NORMAL)));
}
// Add the rest of the points for this entrance line to the bottom left side.
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(0, oneRoomFromBottom - i)));
}
break;
case 1:
int startOfLine = GetStartOfCenteredLine(yLengthOfDungeon, roomsPerLine, SIDE_LENGTH_OF_NORMAL);
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
{
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(0, startOfLine + i)));
}
break;
}
return entranceRooms;
}
private int GetStartOfCenteredLine(int length, int numberOfRooms, int sizeOfRoom)
{
int midpoint = length / 2;
return midpoint - (numberOfRooms * sizeOfRoom / 2);
}
private Room FindClosestSeenConnection(Room nodeRoom, RoomSide attachedSide, List<Room> possibleConnections,
List<Room> obstructions)
{
const float minClosestLength = 0;
Vector2 lookDirection = attachedSide switch
{
RoomSide.Top => new Vector2(0, -1), // Expanding upward
RoomSide.Bottom => new Vector2(0, 1), // Expanding downward
RoomSide.Left => new Vector2(-1, 0), // Expanding left
RoomSide.Right => new Vector2(1, 0), // Expanding right
_ => Vector2.Zero // Default (should not happen)
};
Point nodeCenter = nodeRoom.GetCenterOfRoom();
float visionConeAngle = MathF.PI / 4; // 45 degree cone in each side
Room closestSeenRoom = null;
float closestDistance = float.MaxValue;
foreach (Room otherRoom in possibleConnections)
{
if (otherRoom == nodeRoom) continue;
Point otherCenter = otherRoom.GetCenterOfRoom();
Vector2 toOther = new Vector2(otherCenter.X - nodeCenter.X, otherCenter.Y - nodeCenter.Y);
float distance = toOther.Length();
if (distance <= minClosestLength || distance >= closestDistance) continue;
// Normalize direction vectors
toOther = Vector2.Normalize(toOther);
Vector2 lookNormalized = Vector2.Normalize(lookDirection);
float angle = MathF.Acos(Vector2.Dot(lookNormalized, toOther));
if (angle <= visionConeAngle)
{
if (!IsObstructed(nodeCenter, otherCenter, obstructions))
{
closestSeenRoom = otherRoom;
closestDistance = distance;
}
}
}
return closestSeenRoom; // Could be null if no valid room is found
}
private bool IsObstructed(Point start, Point end, List<Room> obstructions)
{
List<Point> linePoints = GetPointsOnLine(start, end);
foreach (Point point in linePoints)
{
if (obstructions.Any(room => room.ContainsPoint(point)))
{
return true;
}
}
return false;
}
// Bresenhams Line Algorithm to get points between two points
private List<Point> GetPointsOnLine(Point p0, Point p1)
{
List<Point> points = new List<Point>();
int dx = Math.Abs(p1.X - p0.X), sx = p0.X < p1.X ? 1 : -1;
int dy = -Math.Abs(p1.Y - p0.Y), sy = p0.Y < p1.Y ? 1 : -1;
int err = dx + dy, e2;
int x = p0.X, y = p0.Y;
while (true)
{
points.Add(new Point(x, y));
if (x == p1.X && y == p1.Y) break;
e2 = 2 * err;
if (e2 >= dy) { err += dy; x += sx; }
if (e2 <= dx) { err += dx; y += sy; }
}
return points;
}
private List<Room> PlaceRoomsOrganicallyTowardRoom(Room startingRoom, Room targetRoom, RoomType type,
HashSet<Point> occupiedPoints, HashSet<Point> unoccupiedPoints)
{
List<Room> placedRooms = new List<Room>();
Room currentRoom = startingRoom;
// TODO I don't think I actually need this
List<Point> path = GetPointsOnLine(startingRoom.GetCenterOfRoom(), targetRoom.GetCenterOfRoom());
bool roomPlaced = true;
while (currentRoom.GetCenterOfRoom().ManhattanDistance(targetRoom.GetCenterOfRoom()) >=
GetRoomSizeByType(type) && roomPlaced)
{
Room newRoom;
// Get sides of room to try and place new room
(RoomSide primary, RoomSide secondary) =
GetSidesTowardsEndPoint(currentRoom.GetCenterOfRoom(), targetRoom.GetCenterOfRoom());
List<Point> availablePointsOnSide = GetUnoccupiedPointsOnSide(currentRoom, primary, unoccupiedPoints);
newRoom = CreateAdjacentRoom(type, availablePointsOnSide, primary, occupiedPoints);
// If the primary side doesn't have room for a new room the try secondary
if (newRoom == null)
{
availablePointsOnSide = GetUnoccupiedPointsOnSide(currentRoom, secondary, unoccupiedPoints);
newRoom = CreateAdjacentRoom(type, availablePointsOnSide, secondary, occupiedPoints);
}
if (newRoom != null)
{
placedRooms.Add(newRoom);
occupiedPoints.AddRange(newRoom.GetPointsInRoom());
currentRoom = newRoom;
}
else
{
roomPlaced = false;
}
}
return placedRooms;
}
public (RoomSide primarySide, RoomSide secondarySide) GetSidesTowardsEndPoint(Point start, Point end)
{
// Calculate the differences in X and Y
int deltaX = end.X - start.X;
int deltaY = end.Y - start.Y;
// Determine the primary side
RoomSide primarySide;
RoomSide secondarySide;
if (Math.Abs(deltaX) > Math.Abs(deltaY))
{
// If horizontal movement is greater, primary side is horizontal
primarySide = deltaX > 0 ? RoomSide.Right : RoomSide.Left;
secondarySide = deltaY > 0 ? RoomSide.Bottom : RoomSide.Top;
}
else
{
// If vertical movement is greater, primary side is vertical
primarySide = deltaY > 0 ? RoomSide.Bottom : RoomSide.Top;
secondarySide = deltaX > 0 ? RoomSide.Right : RoomSide.Left;
}
return (primarySide, secondarySide);
}
} // Class
} // Namespace

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e9db8a8571ad45d9a8d7c8d22ebe0d05
timeCreated: 1738336090

View file

@ -1,199 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Unity.VisualScripting;
using UnityEngine;
namespace DungeonGenerator
{
public class DungeonMap
{
private int _width;
private int _height;
private List<Room> _monsterRooms = new List<Room>();
private List<Room> _entranceRooms = new List<Room>();
private List<Room> _normalRooms = new List<Room>();
private Room _bossRoom;
private HashSet<Point> _unoccupiedPoints = new HashSet<Point>();
private HashSet<Point> _occupiedPoints = new HashSet<Point>();
public DungeonMap(int width, int height)
{
_width = width;
_height = height;
_unoccupiedPoints.AddRange(Enumerable.Range(0, _width)
.SelectMany(x => Enumerable.Range(0, _height)
.Select(y => new Point(x,y))));
}
public void AddRoom(Room room)
{
switch (room.TypeOfRoom)
{
case RoomType.Monster:
_monsterRooms.Add(room);
break;
case RoomType.Entrance:
_entranceRooms.Add(room);
break;
case RoomType.Normal:
_normalRooms.Add(room);
break;
case RoomType.Boss:
_bossRoom = room;
break;
default:
return;
}
AddPointsToOccupied(room.GetPointsInRoom());
}
public void AddRooms(List<Room> rooms)
{
if (rooms.Count == 0) return;
switch (rooms[0].TypeOfRoom)
{
case RoomType.Monster:
_monsterRooms.AddRange(rooms);
break;
case RoomType.Entrance:
_entranceRooms.AddRange(rooms);
break;
case RoomType.Normal:
_normalRooms.AddRange(rooms);
break;
default:
return;
}
AddPointsToOccupied(rooms.SelectMany(room => room.GetPointsInRoom()).ToList());
}
public List<Room> GetMonsterRooms()
{
return _monsterRooms;
}
public Room GetBossRoom()
{
return _bossRoom;
}
public List<Room> GetEntranceRooms()
{
return _entranceRooms;
}
public List<Room> GetNormalRooms()
{
return _normalRooms;
}
public HashSet<Point> GetUnoccupiedPoints()
{
return new HashSet<Point>(_unoccupiedPoints);
}
public HashSet<Point> GetOccupiedPoints()
{
return new HashSet<Point>(_occupiedPoints);
}
public void PrintMap()
{
List<Point> monsterRoomPoints = _monsterRooms.SelectMany(room => room.GetPointsInRoom()).ToList();
List<Point> entranceRoomPoints = _entranceRooms.SelectMany(room => room.GetPointsInRoom()).ToList();
List<Point> normalRoomPoints = _normalRooms.SelectMany(room => room.GetPointsInRoom()).ToList();
char[,] mapMatrix = new Char[_width, _height];
for (int x = 0; x < _width; x++)
{
for (int y = 0; y < _height; y++)
{
Point point = new Point(x, y);
if (entranceRoomPoints.Contains(point))
{
mapMatrix[x, y] = '*';
}
else if (monsterRoomPoints.Contains(point))
{
mapMatrix[x, y] = 'X';
}
else if (_bossRoom.GetPointsInRoom().Contains(point))
{
mapMatrix[x, y] = 'B';
}
else if (normalRoomPoints.Contains(point))
{
mapMatrix[x, y] = 'N';
}
else
{
mapMatrix[x, y] = '-';
}
}
}
StringBuilder sb = new StringBuilder();
for (int j = 0; j < mapMatrix.GetLength(1); j++)
{
for (int i = 0; i < mapMatrix.GetLength(0); i++)
{
sb.Append(mapMatrix[i, j] + " ");
}
sb.Append(Environment.NewLine);
}
Debug.Log($"{DateTime.Now}{Environment.NewLine}" +
$"X Length: {_width}{Environment.NewLine}" +
$"Y Length: {_height}{Environment.NewLine}" +
$"Monster Rooms: {_monsterRooms.Count}{Environment.NewLine}" +
sb.ToString());
}
private void AddPointsToOccupied(List<Point> points)
{
_occupiedPoints.AddRange(points);
_unoccupiedPoints.ExceptWith(points);
}
private void RemovePointsFromOccupied(List<Point> points)
{
_occupiedPoints.ExceptWith(points);
_unoccupiedPoints.AddRange(points);
}
// Node rooms are the rooms placed around each monster room and the boss room.
public List<Room> GetNodeRooms()
{
List<Room> nodeRooms = new List<Room>();
nodeRooms.AddRange(_monsterRooms.SelectMany(room => room.GetAdjacentRooms()));
nodeRooms.AddRange(_bossRoom.GetAdjacentRooms());
return nodeRooms;
}
public Dictionary<RoomSide, List<Room>> GetNodesWithSides()
{
Dictionary<RoomSide, List<Room>> nodesWithSides = new Dictionary<RoomSide, List<Room>>();
foreach (Room room in _monsterRooms)
{
foreach (var kvp in room.GetAdjacentRoomsDict())
{
if (!nodesWithSides.ContainsKey(kvp.Key))
{
nodesWithSides[kvp.Key] = new List<Room>(kvp.Value);
}
else
{
nodesWithSides[kvp.Key].AddRange(kvp.Value);
}
}
}
return nodesWithSides;
}
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: b8c880c12ad649f8a395e6367a9c8838
timeCreated: 1738774051

View file

@ -1,78 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.UIElements;
using Random = System.Random;
namespace DungeonGenerator
{
public class EvenDisperser
{
private List<Room> _samples = new List<Room>(){new Room(
RoomType.Normal,
1,
1,
new Point(1000, 1000))};
private HashSet<Point> _availablePoints;
public EvenDisperser(int xLength, int yLength, HashSet<Point> availablePoints)
{
_availablePoints = availablePoints;
}
private HashSet<Point> GenerateAvailablePoints(int xLength, int yLength, List<Point> excludedPoints)
{
HashSet<Point> availablePoints = new HashSet<Point>();
for (var x = 0; x < xLength; x++)
{
for (var y = 0; y < yLength; y++)
{
if (!excludedPoints.Contains(new Point(x, y)))
{
availablePoints.Add(new Point(x, y));
}
}
}
return availablePoints;
}
public Room GenerateAndPlaceRoom(int xLength, int yLength, RoomType roomType)
{
int numCandidates = 100; // Increasing improves results but greatly effects performance.
Random rnd = new Random();
Room bestCandidate = new Room(roomType, xLength, yLength, new Point(Int32.MaxValue, Int32.MaxValue));
int bestDistance = 0;
for (var i = 0; i < numCandidates; i++)
{
var candidate = new Room(
roomType, xLength, yLength, _availablePoints.ToList()[rnd.Next(0, _availablePoints.Count)]);
var distance = candidate.GetDistanceToRoom(FindClosestRoom(_samples, candidate));
if (distance > bestDistance
&& candidate.GetPointsInRoom().All(room => _availablePoints.Contains(room)))
{
_availablePoints.ExceptWith(candidate.GetPointsInRoom());
bestCandidate = candidate;
bestDistance = distance;
}
}
_samples.Add(bestCandidate);
return bestCandidate;
}
private Room FindClosestRoom(List<Room> options, Room reference)
{
Room closest = options[0];
foreach (var option in options)
{
if (reference.GetDistanceToRoom(option) < reference.GetDistanceToRoom(closest))
{
closest = option;
}
}
return closest;
}
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 3ce67840c99f4761af7805355099b495
timeCreated: 1738577856

View file

@ -1,25 +0,0 @@
using System;
namespace DungeonGenerator
{
public struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) {
X = x;
Y = y;
}
public override bool Equals(object obj) => obj is Point p && X == p.X && Y == p.Y;
public override int GetHashCode() => HashCode.Combine(X, Y);
public override string ToString() => $"({X}, {Y})";
public static Point operator +(Point a, Point b) => new Point(a.X + b.X, a.Y + b.Y);
public static Point operator -(Point a, Point b) => new Point(a.X - b.X, a.Y - b.Y);
public int ManhattanDistance(Point other) => Math.Abs(X - other.X) + Math.Abs(Y - other.Y);
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5c2b1ba54c4043b19dd204ab61f465c3
timeCreated: 1738579679

View file

@ -1,107 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.VisualScripting;
namespace DungeonGenerator
{
public enum RoomSide
{
Top,
Bottom,
Left,
Right
}
public enum RoomType
{
Normal,
Entrance,
Monster,
Boss
}
public class Room
{
public Room(RoomType roomType, int width, int height, Point positionOfTopLeft)
{
TypeOfRoom = roomType;
Width = width;
Height = height;
PositionOfTopLeft = positionOfTopLeft;
}
private readonly Dictionary<RoomSide, List<Room>> _adjacentRooms = new();
public RoomType TypeOfRoom { get; set; }
public int Height { get; set; }
public int Width { get; set; }
public Point PositionOfTopLeft { get; set; }
public List<Point> GetPointsInRoom()
{
List<Point> points = new List<Point>();
for (int i = 0; i < Width; i++)
{
for (int j = 0; j < Height; j++)
{
points.Add(new Point(PositionOfTopLeft.X + i, PositionOfTopLeft.Y + j));
}
}
return points;
}
public bool ContainsPoint(Point point)
{
return GetPointsInRoom().Any(p => p.Equals(point));
}
public int GetDistanceToRoom(Room other)
{
Point centerOfRoom = GetCenterOfRoom();
Point centerOfOther = other.GetCenterOfRoom();
return centerOfRoom.ManhattanDistance(centerOfOther);
}
public Point GetCenterOfRoom()
{
return new Point(PositionOfTopLeft.X + Width / 2, PositionOfTopLeft.Y + Height / 2);
}
public void AddAdjacentRoom(Room room, RoomSide side)
{
if (!_adjacentRooms.ContainsKey(side))
{
_adjacentRooms[side] = new List<Room>();
}
// Prevent duplicates
if (!_adjacentRooms[side].Contains(room))
{
_adjacentRooms[side].Add(room);
room.AddAdjacentRoom(this, GetOppositeSide(side)); // Ensure bidirectional linkage
}
}
public IEnumerable<Room> GetAdjacentRooms()
{
return _adjacentRooms.Values.SelectMany(roomList => roomList);
}
public Dictionary<RoomSide, List<Room>> GetAdjacentRoomsDict()
{
return new Dictionary<RoomSide, List<Room>>(_adjacentRooms);
}
// Helper method to get the opposite side
private RoomSide GetOppositeSide(RoomSide side) => side switch
{
RoomSide.Top => RoomSide.Bottom,
RoomSide.Bottom => RoomSide.Top,
RoomSide.Left => RoomSide.Right,
RoomSide.Right => RoomSide.Left,
_ => throw new ArgumentException("Invalid RoomSide", nameof(side))
};
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 2dd79ebcc11d4938a8d116e711842b89
timeCreated: 1738337240

View file

@ -1,13 +0,0 @@
using DungeonGenerator;
using UnityEngine;
public class TestRun : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
DungeonGenerator.DungeonGenerator gen = new DungeonGenerator.DungeonGenerator();
DungeonMap dungeon = gen.GenerateDungeon(0 , 0.5f);
dungeon.PrintMap();
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 05a951debbad424aada4449064592d39

Binary file not shown.

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2007 James Newton-King
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,71 @@
# ![Logo](https://raw.githubusercontent.com/JamesNK/Newtonsoft.Json/master/Doc/icons/logo.jpg) Json.NET
[![NuGet version (Newtonsoft.Json)](https://img.shields.io/nuget/v/Newtonsoft.Json.svg?style=flat-square)](https://www.nuget.org/packages/Newtonsoft.Json/)
[![Build status](https://dev.azure.com/jamesnk/Public/_apis/build/status/JamesNK.Newtonsoft.Json?branchName=master)](https://dev.azure.com/jamesnk/Public/_build/latest?definitionId=8)
Json.NET is a popular high-performance JSON framework for .NET
## Serialize JSON
```csharp
Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Sizes = new string[] { "Small" };
string json = JsonConvert.SerializeObject(product);
// {
// "Name": "Apple",
// "Expiry": "2008-12-28T00:00:00",
// "Sizes": [
// "Small"
// ]
// }
```
## Deserialize JSON
```csharp
string json = @"{
'Name': 'Bad Boys',
'ReleaseDate': '1995-4-7T00:00:00',
'Genres': [
'Action',
'Comedy'
]
}";
Movie m = JsonConvert.DeserializeObject<Movie>(json);
string name = m.Name;
// Bad Boys
```
## LINQ to JSON
```csharp
JArray array = new JArray();
array.Add("Manual text");
array.Add(new DateTime(2000, 5, 23));
JObject o = new JObject();
o["MyArray"] = array;
string json = o.ToString();
// {
// "MyArray": [
// "Manual text",
// "2000-05-23T00:00:00"
// ]
// }
```
## Links
- [Homepage](https://www.newtonsoft.com/json)
- [Documentation](https://www.newtonsoft.com/json/help)
- [NuGet Package](https://www.nuget.org/packages/Newtonsoft.Json)
- [Release Notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Contributing Guidelines](https://github.com/JamesNK/Newtonsoft.Json/blob/master/CONTRIBUTING.md)
- [License](https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/json.net)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net471" />
</packages>