Assignment system overhaul to fix many bugs

This commit is contained in:
Daniel Lovell
2024-09-29 23:18:40 -07:00
parent d5ae6f97f1
commit e15b70fafc
20 changed files with 467 additions and 165 deletions

View File

@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using UnityEngine;
using System.Linq;
using System.Diagnostics.Contracts;
// The assignment class is an interface for assigning a threat to each interceptor.
public interface IAssignment {
@@ -8,39 +11,30 @@ public interface IAssignment {
// The first element corresponds to the interceptor index, and the second element
// corresponds to the threat index.
public struct AssignmentItem {
public int InterceptorIndex;
public int ThreatIndex;
public Interceptor Interceptor;
public Threat Threat;
public AssignmentItem(int missileIndex, int threatIndex) {
InterceptorIndex = missileIndex;
ThreatIndex = threatIndex;
public AssignmentItem(Interceptor interceptor, Threat threat) {
Interceptor = interceptor;
Threat = threat;
}
}
// A list containing the interceptor-target assignments.
// Assign a target to each interceptor that has not been assigned a target yet.
public abstract IEnumerable<AssignmentItem> Assign(List<Agent> missiles, List<Agent> targets);
[Pure]
public abstract IEnumerable<AssignmentItem> Assign(in IReadOnlyList<Interceptor> interceptors, in IReadOnlyList<ThreatData> threatTable);
// Get the list of assignable interceptor indices.
protected static List<int> GetAssignableInterceptorIndices(List<Agent> missiles) {
List<int> assignableInterceptorIndices = new List<int>();
for (int missileIndex = 0; missileIndex < missiles.Count; missileIndex++) {
if (missiles[missileIndex].IsAssignable()) {
assignableInterceptorIndices.Add(missileIndex);
}
}
return assignableInterceptorIndices;
[Pure]
protected static List<Interceptor> GetAssignableInterceptors(in IReadOnlyList<Interceptor> interceptors) {
return interceptors.Where(interceptor => interceptor.IsAssignable()).ToList();
}
// Get the list of active target indices.
protected static List<int> GetActiveThreatIndices(List<Agent> threats) {
List<int> activeThreatIndices = new List<int>();
for (int threatIndex = 0; threatIndex < threats.Count; threatIndex++) {
if (!threats[threatIndex].IsHit()) {
activeThreatIndices.Add(threatIndex);
}
}
return activeThreatIndices;
// Get the list of active threats.
[Pure]
protected static List<ThreatData> GetActiveThreats(in IReadOnlyList<ThreatData> threats) {
return threats.Where(t => t.Status != ThreatStatus.DESTROYED).ToList();
}
}

View File

@@ -1,39 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
// The round-robin assignment class assigns missiles to the targets in a
// round-robin order.
using System.Diagnostics.Contracts;
// The round-robin assignment class assigns interceptors to the targets in a
// round-robin order using the new paradigm.
public class RoundRobinAssignment : IAssignment {
// Previous target index that was assigned.
private int prevTargetIndex = -1;
// Previous target index that was assigned.
private int prevTargetIndex = -1;
// Assign a target to each interceptor that has not been assigned a target yet.
public IEnumerable<IAssignment.AssignmentItem> Assign(List<Agent> missiles, List<Agent> targets) {
List<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
List<int> assignableInterceptorIndices = IAssignment.GetAssignableInterceptorIndices(missiles);
if (assignableInterceptorIndices.Count == 0) {
return assignments;
// Assign a target to each interceptor that has not been assigned a target yet.
[Pure]
public IEnumerable<IAssignment.AssignmentItem> Assign(in IReadOnlyList<Interceptor> interceptors, in IReadOnlyList<ThreatData> targets) {
List<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
// Get the list of interceptors that are available for assignment.
List<Interceptor> assignableInterceptors = IAssignment.GetAssignableInterceptors(interceptors);
if (assignableInterceptors.Count == 0) {
return assignments;
}
// Get the list of active threats that need to be addressed.
List<ThreatData> activeThreats = IAssignment.GetActiveThreats(targets);
if (activeThreats.Count == 0) {
return assignments;
}
// Perform round-robin assignment.
foreach (Interceptor interceptor in assignableInterceptors) {
// Determine the next target index in a round-robin fashion.
int nextTargetIndex = (prevTargetIndex + 1) % activeThreats.Count;
ThreatData selectedThreat = activeThreats[nextTargetIndex];
// Assign the interceptor to the selected threat.
assignments.Add(new IAssignment.AssignmentItem(interceptor, selectedThreat.Threat));
// Update the previous target index.
prevTargetIndex = nextTargetIndex;
}
return assignments;
}
List<int> activeThreatIndices = IAssignment.GetActiveThreatIndices(targets);
if (activeThreatIndices.Count == 0) {
return assignments;
}
foreach (int missileIndex in assignableInterceptorIndices) {
int nextActiveTargetIndex = activeThreatIndices.FindIndex(index => index > prevTargetIndex);
if (nextActiveTargetIndex == -1) {
nextActiveTargetIndex = 0;
}
int nextTargetIndex = activeThreatIndices[nextActiveTargetIndex];
assignments.Add(new IAssignment.AssignmentItem(missileIndex, nextTargetIndex));
prevTargetIndex = nextTargetIndex;
}
return assignments;
}
}

View File

@@ -3,70 +3,64 @@ using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
// The threat assignment class assigns missiles to the targets based
using System.Diagnostics.Contracts;
// The threat assignment class assigns interceptors to the targets based
// on the threat level of the targets.
public class ThreatAssignment : IAssignment {
// Assign a target to each interceptor that has not been assigned a target yet.
public IEnumerable<IAssignment.AssignmentItem> Assign(List<Agent> missiles, List<Agent> targets) {
[Pure]
public IEnumerable<IAssignment.AssignmentItem> Assign(in IReadOnlyList<Interceptor> interceptors, in IReadOnlyList<ThreatData> targets) {
List<IAssignment.AssignmentItem> assignments = new List<IAssignment.AssignmentItem>();
List<int> assignableInterceptorIndices = IAssignment.GetAssignableInterceptorIndices(missiles);
if (assignableInterceptorIndices.Count == 0) {
List<Interceptor> assignableInterceptors = IAssignment.GetAssignableInterceptors(interceptors);
if (assignableInterceptors.Count == 0) {
Debug.LogWarning("No assignable interceptors found");
return assignments;
}
List<int> activeThreatIndices = IAssignment.GetActiveThreatIndices(targets);
if (activeThreatIndices.Count == 0) {
List<ThreatData> activeThreats = IAssignment.GetActiveThreats(targets);
if (activeThreats.Count == 0) {
Debug.LogWarning("No active threats found");
return assignments;
}
Vector3 positionToDefend = Vector3.zero;
List<ThreatInfo> threatInfos =
CalculateThreatLevels(targets, activeThreatIndices, positionToDefend);
CalculateThreatLevels(activeThreats, positionToDefend);
foreach (int missileIndex in assignableInterceptorIndices) {
if (missiles[missileIndex].HasAssignedTarget())
continue;
if (threatInfos.Count == 0)
break;
// Find the optimal target for this interceptor based on distance and threat
ThreatInfo optimalTarget = null;
float optimalScore = float.MinValue;
foreach (ThreatInfo threat in threatInfos) {
float distance = Vector3.Distance(missiles[missileIndex].transform.position,
targets[threat.TargetIndex].transform.position);
float score = threat.ThreatLevel / distance; // Balance threat level with proximity
if (score > optimalScore) {
optimalScore = score;
optimalTarget = threat;
// Sort ThreatInfo first by ThreatData.Status (UNASSIGNED first, then ASSIGNED)
// Within each group, order by ThreatLevel descending
threatInfos = threatInfos.OrderByDescending(t => t.ThreatData.Status == ThreatStatus.UNASSIGNED)
.ThenByDescending(t => t.ThreatLevel)
.ToList();
var assignableInterceptorsEnumerator = assignableInterceptors.GetEnumerator();
if (assignableInterceptorsEnumerator.MoveNext()) // Move to the first element
{
foreach (ThreatInfo threatInfo in threatInfos) {
assignments.Add(new IAssignment.AssignmentItem(assignableInterceptorsEnumerator.Current, threatInfo.ThreatData.Threat));
if (!assignableInterceptorsEnumerator.MoveNext()) {
break;
}
}
if (optimalTarget != null) {
assignments.Add(new IAssignment.AssignmentItem(missileIndex, optimalTarget.TargetIndex));
threatInfos.Remove(optimalTarget);
}
}
return assignments;
}
private List<ThreatInfo> CalculateThreatLevels(List<Agent> targets, List<int> activeThreatIndices,
Vector3 missilesMeanPosition) {
private List<ThreatInfo> CalculateThreatLevels(List<ThreatData> threatTable,
Vector3 defensePosition) {
List<ThreatInfo> threatInfos = new List<ThreatInfo>();
foreach (int targetIndex in activeThreatIndices) {
Agent target = targets[targetIndex];
float distanceToMean = Vector3.Distance(target.transform.position, missilesMeanPosition);
float velocityMagnitude = target.GetVelocity().magnitude;
foreach (ThreatData threatData in threatTable) {
Threat threat = threatData.Threat;
float distanceToMean = Vector3.Distance(threat.transform.position, defensePosition);
float velocityMagnitude = threat.GetVelocity().magnitude;
// Calculate threat level based on proximity and velocity
float threatLevel = (1 / distanceToMean) * velocityMagnitude;
threatInfos.Add(new ThreatInfo(targetIndex, threatLevel));
threatInfos.Add(new ThreatInfo(threatData, threatLevel));
}
// Sort threats in descending order
@@ -74,11 +68,11 @@ public class ThreatAssignment : IAssignment {
}
private class ThreatInfo {
public int TargetIndex { get; }
public ThreatData ThreatData { get; }
public float ThreatLevel { get; }
public ThreatInfo(int targetIndex, float threatLevel) {
TargetIndex = targetIndex;
public ThreatInfo(ThreatData threatData, float threatLevel) {
ThreatData = threatData;
ThreatLevel = threatLevel;
}
}