diff --git a/TD.sln.DotSettings b/TD.sln.DotSettings index 861c3e947..912e13430 100644 --- a/TD.sln.DotSettings +++ b/TD.sln.DotSettings @@ -379,4 +379,5 @@ namespace $NAMESPACE$ True True True + True True \ No newline at end of file diff --git a/src/Bearded.TD/Game/Generation/Semantic/Logical/LogicalTilemapFitness.cs b/src/Bearded.TD/Game/Generation/Semantic/Logical/LogicalTilemapFitness.cs index 5c503a063..32a1d0548 100644 --- a/src/Bearded.TD/Game/Generation/Semantic/Logical/LogicalTilemapFitness.cs +++ b/src/Bearded.TD/Game/Generation/Semantic/Logical/LogicalTilemapFitness.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Bearded.TD.Game.Generation.Semantic.Features; using Bearded.TD.Game.Generation.Semantic.Fitness; @@ -10,15 +11,14 @@ namespace Bearded.TD.Game.Generation.Semantic.Logical; -using Tilemap = LogicalTilemap; using FF = SimpleFitnessFunction; static class LogicalTilemapFitness { - public static FF AcuteAnglesCount { get; } = new AcuteAngles(); public static FF ConnectedComponentsCount { get; } = new ConnectedComponents(); public static FF DisconnectedCrevices { get; } = new ConnectedCrevices(); public static FF ConnectedTrianglesCount { get; } = new ConnectedTriangles(); + public static FF CriticalConnectionCount { get; } = new CriticalConnections(); public static FF BiomeComponentCount { get; } = new DisconnectedBiomes(); public static FF NodeBehaviorFitness { get; } = new NodeBehavior(); @@ -27,10 +27,10 @@ private sealed class NodeBehavior : SimpleFitnessFunction { public override string Name => "Node Behaviour"; - protected override double CalculateFitness(Tilemap tilemap) + protected override double CalculateFitness(LogicalTilemap tilemap) { return ( - from tile in Tiles.Tilemap.EnumerateTilemapWith(tilemap.Radius) + from tile in Tilemap.EnumerateTilemapWith(tilemap.Radius) let node = tilemap[tile] where node.Blueprint != null select node.Blueprint!.Behaviors.Sum(b => b.GetFitnessPenalty(tilemap, tile)) @@ -63,12 +63,12 @@ public ConnectionDegreeHistogram(IEnumerable idealHistogram) targetHistogram = builder.MoveToImmutable(); } - protected override double CalculateFitness(Tilemap tilemap) + protected override double CalculateFitness(LogicalTilemap tilemap) { var actualHistogram = new int[7]; var tileCount = 0; - foreach (var tile in Tiles.Tilemap.EnumerateTilemapWith(tilemap.Radius) + foreach (var tile in Tilemap.EnumerateTilemapWith(tilemap.Radius) .Select(t => tilemap[t]).Where(n => n.Blueprint != null)) { var connections = tile.ConnectedTo.Enumerate().Count(); @@ -88,11 +88,11 @@ private sealed class ConnectedTriangles : FF { public override string Name => "Connected Triangles"; - protected override double CalculateFitness(Tilemap tilemap) + protected override double CalculateFitness(LogicalTilemap tilemap) { var count = 0; - foreach (var (tile, node) in Tiles.Tilemap.EnumerateTilemapWith(tilemap.Radius) + foreach (var (tile, node) in Tilemap.EnumerateTilemapWith(tilemap.Radius) .Select(t => (t, tilemap[t]))) { var connected = node.ConnectedTo; @@ -106,18 +106,84 @@ protected override double CalculateFitness(Tilemap tilemap) } } - private sealed class AcuteAngles : FF + private sealed class CriticalConnections : FF { - public override string Name => "Acute Angles"; + private static readonly ImmutableHashSet rightFacingDirections = + ImmutableHashSet.Create(Direction.Right, Direction.UpRight, Direction.DownRight); + + public override string Name => "Critical Connections"; - protected override double CalculateFitness(Tilemap tilemap) + protected override double CalculateFitness(LogicalTilemap tilemap) { - return Tiles.Tilemap.EnumerateTilemapWith(tilemap.Radius) - .Select(t => tilemap[t]) - .Select(node => node.ConnectedTo) - .Sum(connected => Extensions.Directions.Count(direction - => connected.Includes(direction) - && connected.Includes(direction.Next()))); + var edges = Tilemap.EnumerateTilemapWith(tilemap.Radius) + .SelectMany( + t => tilemap[t].ConnectedTo.Enumerate() + .Where(rightFacingDirections.Contains) + .Select(dir => t.Edge(dir))); + + var penalty = 0; + + foreach (var (from, to) in edges.Select(e => e.AdjacentTiles)) + { + if (isConnectedIndirectly(from, to, out var tilesConnectedToFrom)) + { + continue; + } + + penalty += 3; + + if (!anyTagsPresent(tilesConnectedToFrom) || + (!isConnectedIndirectly(to, from, out var tilesConnectedToTo)! && + anyTagsPresent(tilesConnectedToTo))) + { + penalty += 15; + } + } + + return penalty; + + bool isConnectedIndirectly( + Tile start, Tile target, [NotNullWhen(false)] out ImmutableHashSet? connectedTiles) + { + var q = new Queue(); + var seen = new HashSet(); + + q.Enqueue(start); + seen.Add(start); + + while (q.TryDequeue(out var currTile)) + { + var neighborTiles = tilemap[currTile].ConnectedTo.Enumerate().Select(currTile.Neighbor); + foreach (var neighborTile in neighborTiles) + { + if (neighborTile == target) + { + // Don't explore tiles across from our direct connection. + if (currTile == start) + { + continue; + } + + connectedTiles = default; + return true; + } + + if (seen.Contains(neighborTile)) + { + continue; + } + + q.Enqueue(neighborTile); + seen.Add(neighborTile); + } + } + + connectedTiles = ImmutableHashSet.CreateRange(seen); + return false; + } + + bool anyTagsPresent(IEnumerable tiles) => + tiles.SelectMany(t => tilemap[t].Blueprint?.AllTags ?? Enumerable.Empty()).Any(); } } @@ -125,11 +191,11 @@ private sealed class ConnectedComponents : FF { public override string Name => "Connected Components"; - protected override double CalculateFitness(Tilemap tilemap) + protected override double CalculateFitness(LogicalTilemap tilemap) { var componentCount = 0; var seen = new HashSet(); - foreach (var tile in Tiles.Tilemap.EnumerateTilemapWith(tilemap.Radius)) + foreach (var tile in Tilemap.EnumerateTilemapWith(tilemap.Radius)) { if (seen.Contains(tile) || tilemap[tile].Blueprint == null) continue; @@ -184,7 +250,7 @@ private IEnumerable enumerateCorners(LogicalTilemap tilemap) { var virtualRadius = tilemap.Radius + 1; - foreach (var tile in Tiles.Tilemap.EnumerateTilemapWith(virtualRadius)) + foreach (var tile in Tilemap.EnumerateTilemapWith(virtualRadius)) { if (tile.Neighbor(Direction.UpRight).Radius > virtualRadius) continue; @@ -211,7 +277,7 @@ protected override double CalculateFitness(LogicalTilemap tilemap) var componentCounts = componentCountsByBiome(tilemap); var totalExcessComponents = componentCounts.Sum(kvp => kvp.Value - 1); - return totalExcessComponents * 200; + return totalExcessComponents * 50; } private static ImmutableDictionary componentCountsByBiome(LogicalTilemap tilemap) @@ -219,7 +285,7 @@ private static ImmutableDictionary componentCountsByBiome(LogicalTi var result = ImmutableDictionary.CreateBuilder(); var seen = new HashSet(); - foreach (var tile in Tiles.Tilemap.EnumerateTilemapWith(tilemap.Radius)) + foreach (var tile in Tilemap.EnumerateTilemapWith(tilemap.Radius)) { if (seen.Contains(tile) || tilemap[tile].Biome == null) { diff --git a/src/Bearded.TD/Game/Generation/Semantic/Logical/LogicalTilemapGenerator.cs b/src/Bearded.TD/Game/Generation/Semantic/Logical/LogicalTilemapGenerator.cs index f70796d23..07ac92218 100644 --- a/src/Bearded.TD/Game/Generation/Semantic/Logical/LogicalTilemapGenerator.cs +++ b/src/Bearded.TD/Game/Generation/Semantic/Logical/LogicalTilemapGenerator.cs @@ -35,6 +35,7 @@ private sealed record Settings( LogicalTilemapFitness.ConnectedComponentsCount, LogicalTilemapFitness.DisconnectedCrevices, LogicalTilemapFitness.ConnectedTrianglesCount, + LogicalTilemapFitness.CriticalConnectionCount, LogicalTilemapFitness.NodeBehaviorFitness, LogicalTilemapFitness.ConnectionDegreeHistogramDifference( new[] {0, /*1*/ 0.15, /*2*/ 0.2, /*3*/ 0.45, /*4*/ 0.2, 0, 0}), diff --git a/src/Bearded.TD/assets/mods/default/defs/levelnodes/core.json5 b/src/Bearded.TD/assets/mods/default/defs/levelnodes/core.json5 index 455855959..6bc03483c 100644 --- a/src/Bearded.TD/assets/mods/default/defs/levelnodes/core.json5 +++ b/src/Bearded.TD/assets/mods/default/defs/levelnodes/core.json5 @@ -3,7 +3,8 @@ behaviors: [ { id: "core" }, { id: "forceToCenter" }, - { id: "avoidTagProximity", parameters: { tagToAvoid: "spawner", steps: 2 } }, + { id: "avoidTagProximity", parameters: { tagToAvoid: "spawner", steps: 2, penaltyFactor: 300 } }, + { id: "avoidTagProximity", parameters: { tagToAvoid: "spawner", steps: 3, penaltyFactor: 100 } }, { id: "setTiles", parameters: { type: "floor" } }, diff --git a/src/Bearded.TD/assets/mods/default/defs/levelnodes/spawner.json5 b/src/Bearded.TD/assets/mods/default/defs/levelnodes/spawner.json5 index c04c05d9a..a62e636cf 100644 --- a/src/Bearded.TD/assets/mods/default/defs/levelnodes/spawner.json5 +++ b/src/Bearded.TD/assets/mods/default/defs/levelnodes/spawner.json5 @@ -3,8 +3,9 @@ radius: 2, explorable: false, behaviors: [ - { id: "forceDeadEnd", parameters: { penaltyFactor: 200 } }, - { id: "avoidTagAdjacency", parameters: { tagToAvoid: "spawner", penaltyFactor: 50 } }, + { id: "forceDeadEnd", parameters: { penaltyFactor: 400 } }, + { id: "avoidTagAdjacency", parameters: { tagToAvoid: "spawner", penaltyFactor: 40 } }, + { id: "avoidTagProximity", parameters: { tagToAvoid: "spawner", steps: 2, penaltyFactor: 20 } }, { id: "spawner" }, { id: "setTiles", parameters: { type: "floor" } },