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" } },