Improved
This commit is contained in:
@@ -36,6 +36,8 @@ public partial class MainWindow : Window
|
|||||||
private readonly DigitalDojoApiClient _client;
|
private readonly DigitalDojoApiClient _client;
|
||||||
private readonly ObservableCollection<string> _logEntries = [];
|
private readonly ObservableCollection<string> _logEntries = [];
|
||||||
private readonly Dictionary<(int X, int Y), double> _enemyHeat = [];
|
private readonly Dictionary<(int X, int Y), double> _enemyHeat = [];
|
||||||
|
private readonly Dictionary<(int X, int Y), double> _visitHeat = [];
|
||||||
|
private readonly Dictionary<(int X, int Y), double> _deathHeat = [];
|
||||||
private readonly Dictionary<string, int> _lastRadar = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, int> _lastRadar = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly Dictionary<Direction, PeekEntitiesAsyncResponse> _lastPeek = [];
|
private readonly Dictionary<Direction, PeekEntitiesAsyncResponse> _lastPeek = [];
|
||||||
private readonly Dictionary<BotAction, DateTimeOffset> _lastUse = [];
|
private readonly Dictionary<BotAction, DateTimeOffset> _lastUse = [];
|
||||||
@@ -65,9 +67,13 @@ public partial class MainWindow : Window
|
|||||||
private DateTimeOffset _lastStatusAt = DateTimeOffset.MinValue;
|
private DateTimeOffset _lastStatusAt = DateTimeOffset.MinValue;
|
||||||
private (int X, int Y)? _nearestEnemy;
|
private (int X, int Y)? _nearestEnemy;
|
||||||
private Direction _lastMoveDirection = Direction.North;
|
private Direction _lastMoveDirection = Direction.North;
|
||||||
|
private Direction _escapeDirection = Direction.North;
|
||||||
private int _recentDamageTicks;
|
private int _recentDamageTicks;
|
||||||
private int _survivalTicks;
|
private int _survivalTicks;
|
||||||
|
private int _escapeLockTicks;
|
||||||
private bool _lastUnderPressure;
|
private bool _lastUnderPressure;
|
||||||
|
private int _pressureStreak;
|
||||||
|
private LevelProfile _currentLevelProfile = LevelProfile.Unknown;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
@@ -234,6 +240,11 @@ public partial class MainWindow : Window
|
|||||||
_survivalTicks--;
|
_survivalTicks--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_escapeLockTicks > 0)
|
||||||
|
{
|
||||||
|
_escapeLockTicks--;
|
||||||
|
}
|
||||||
|
|
||||||
DecayBlockedDirections();
|
DecayBlockedDirections();
|
||||||
await PollTelemetryAsync(ct);
|
await PollTelemetryAsync(ct);
|
||||||
DecayHeat();
|
DecayHeat();
|
||||||
@@ -297,6 +308,7 @@ public partial class MainWindow : Window
|
|||||||
_progress = stats.Level?.Progress ?? _progress;
|
_progress = stats.Level?.Progress ?? _progress;
|
||||||
_remainingTime = stats.Level?.Remainingtime ?? _remainingTime;
|
_remainingTime = stats.Level?.Remainingtime ?? _remainingTime;
|
||||||
_levelName = stats.Level?.Name ?? _levelName;
|
_levelName = stats.Level?.Name ?? _levelName;
|
||||||
|
UpdateLevelProfile(_levelName);
|
||||||
|
|
||||||
if (previousHealth > 0.1 && _currentHealth + 0.05 < previousHealth)
|
if (previousHealth > 0.1 && _currentHealth + 0.05 < previousHealth)
|
||||||
{
|
{
|
||||||
@@ -312,11 +324,15 @@ public partial class MainWindow : Window
|
|||||||
_recentDeaths.Dequeue();
|
_recentDeaths.Dequeue();
|
||||||
}
|
}
|
||||||
|
|
||||||
_survivalTicks = Math.Max(_survivalTicks, 180 + _recentDeaths.Count * 30);
|
_survivalTicks = Math.Max(_survivalTicks, 24 + _recentDeaths.Count * 4);
|
||||||
|
_escapeLockTicks = Math.Max(_escapeLockTicks, 8);
|
||||||
|
_escapeDirection = Opposite(_lastMoveDirection);
|
||||||
|
_pressureStreak = 0;
|
||||||
_x = 0;
|
_x = 0;
|
||||||
_y = 0;
|
_y = 0;
|
||||||
_enemyHeat.Clear();
|
_enemyHeat.Clear();
|
||||||
_nearestEnemy = null;
|
_nearestEnemy = null;
|
||||||
|
MarkDeathZone(_x, _y);
|
||||||
_sessionLogger?.LogEvent("death", $"death_count={_deaths};death_window_90s={_recentDeaths.Count}");
|
_sessionLogger?.LogEvent("death", $"death_count={_deaths};death_window_90s={_recentDeaths.Count}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +346,7 @@ public partial class MainWindow : Window
|
|||||||
_levelName,
|
_levelName,
|
||||||
_x,
|
_x,
|
||||||
_y,
|
_y,
|
||||||
$"survival_ticks={_survivalTicks};damage_ticks={_recentDamageTicks}");
|
$"survival_ticks={_survivalTicks};damage_ticks={_recentDamageTicks};escape_lock={_escapeLockTicks};pressure_streak={_pressureStreak}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -420,6 +436,7 @@ public partial class MainWindow : Window
|
|||||||
private BotDecision? BuildDecision()
|
private BotDecision? BuildDecision()
|
||||||
{
|
{
|
||||||
var hpRatio = _maxHealth > 0.1 ? _currentHealth / _maxHealth : 1.0;
|
var hpRatio = _maxHealth > 0.1 ? _currentHealth / _maxHealth : 1.0;
|
||||||
|
var profile = _currentLevelProfile;
|
||||||
|
|
||||||
var immediateDir = _lastPeek
|
var immediateDir = _lastPeek
|
||||||
.Where(x => x.Value.Executed && x.Value.PlayersInSight > 0 && x.Value.SightedPlayerDistance == 1)
|
.Where(x => x.Value.Executed && x.Value.PlayersInSight > 0 && x.Value.SightedPlayerDistance == 1)
|
||||||
@@ -432,10 +449,22 @@ public partial class MainWindow : Window
|
|||||||
closeThreats++;
|
closeThreats++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var underPressure = hpRatio < 0.58 || _recentDamageTicks > 0 || closeThreats >= 2 || _survivalTicks > 0;
|
if (hpRatio < 0.75 || _recentDamageTicks > 0 || closeThreats >= 2)
|
||||||
|
{
|
||||||
|
_pressureStreak = Math.Min(_pressureStreak + 1, 12);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_pressureStreak = Math.Max(_pressureStreak - 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var underPressure = hpRatio < PressureHpThreshold(profile) || _recentDamageTicks > 0 || closeThreats >= PressureThreatThreshold(profile) || _survivalTicks > 0 || _pressureStreak >= 2;
|
||||||
_lastUnderPressure = underPressure;
|
_lastUnderPressure = underPressure;
|
||||||
|
|
||||||
if (Ready(BotAction.Special) && closeThreats >= 2)
|
var specialThreatThreshold = SpecialThreatThreshold(profile);
|
||||||
|
var specialHpFloor = SpecialHpFloor(profile);
|
||||||
|
|
||||||
|
if (Ready(BotAction.Special) && closeThreats >= specialThreatThreshold && (hpRatio >= specialHpFloor || closeThreats >= specialThreatThreshold + 1))
|
||||||
{
|
{
|
||||||
return new BotDecision(
|
return new BotDecision(
|
||||||
"specialattack",
|
"specialattack",
|
||||||
@@ -448,7 +477,7 @@ public partial class MainWindow : Window
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (immediateDir.HasValue && !underPressure && Ready(BotAction.Hit))
|
if (immediateDir.HasValue && Ready(BotAction.Hit) && ShouldUseImmediateHit(profile, hpRatio, closeThreats, underPressure))
|
||||||
{
|
{
|
||||||
var dir = immediateDir.Value;
|
var dir = immediateDir.Value;
|
||||||
return new BotDecision(
|
return new BotDecision(
|
||||||
@@ -462,11 +491,11 @@ public partial class MainWindow : Window
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (immediateDir.HasValue && Ready(BotAction.Move))
|
if (immediateDir.HasValue && Ready(BotAction.Move) && ShouldRetreatOnImmediate(profile, hpRatio, closeThreats))
|
||||||
{
|
{
|
||||||
var retreatDirection = Opposite(immediateDir.Value);
|
var retreatDirection = Opposite(immediateDir.Value);
|
||||||
|
|
||||||
if (Ready(BotAction.Dash) && (hpRatio < 0.40 || _survivalTicks > 0))
|
if (Ready(BotAction.Dash) && (hpRatio < DashHpThreshold(profile) || _pressureStreak >= 3 || _survivalTicks > 0))
|
||||||
{
|
{
|
||||||
return new BotDecision(
|
return new BotDecision(
|
||||||
"dash-retreat",
|
"dash-retreat",
|
||||||
@@ -478,6 +507,8 @@ public partial class MainWindow : Window
|
|||||||
if (response.Executed)
|
if (response.Executed)
|
||||||
{
|
{
|
||||||
ApplyMovement(retreatDirection, Math.Max(response.BlocksDashed, 1));
|
ApplyMovement(retreatDirection, Math.Max(response.BlocksDashed, 1));
|
||||||
|
_escapeDirection = retreatDirection;
|
||||||
|
_escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Executed;
|
return response.Executed;
|
||||||
@@ -496,6 +527,8 @@ public partial class MainWindow : Window
|
|||||||
if (moved)
|
if (moved)
|
||||||
{
|
{
|
||||||
ApplyMovement(retreatDirection, 1);
|
ApplyMovement(retreatDirection, 1);
|
||||||
|
_escapeDirection = retreatDirection;
|
||||||
|
_escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Executed;
|
return response.Executed;
|
||||||
@@ -558,6 +591,11 @@ public partial class MainWindow : Window
|
|||||||
if (moved)
|
if (moved)
|
||||||
{
|
{
|
||||||
ApplyMovement(moveDirection, 1);
|
ApplyMovement(moveDirection, 1);
|
||||||
|
if (underPressure)
|
||||||
|
{
|
||||||
|
_escapeDirection = moveDirection;
|
||||||
|
_escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Executed;
|
return response.Executed;
|
||||||
@@ -569,6 +607,14 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
private Direction GetBestMoveDirection(bool chase)
|
private Direction GetBestMoveDirection(bool chase)
|
||||||
{
|
{
|
||||||
|
if (!chase && _escapeLockTicks > 0)
|
||||||
|
{
|
||||||
|
if (!_blockedDirectionTicks.TryGetValue(_escapeDirection, out var blocked) || blocked <= 0)
|
||||||
|
{
|
||||||
|
return _escapeDirection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var scored = new Dictionary<Direction, double>
|
var scored = new Dictionary<Direction, double>
|
||||||
{
|
{
|
||||||
[Direction.North] = RadarScore(Direction.North),
|
[Direction.North] = RadarScore(Direction.North),
|
||||||
@@ -615,6 +661,20 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var visitPenalty = VisitHeatFor(dir);
|
||||||
|
var deathPenalty = DeathHeatFor(dir);
|
||||||
|
|
||||||
|
if (_currentLevelProfile == LevelProfile.BiggerMap)
|
||||||
|
{
|
||||||
|
score -= visitPenalty * 1.4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
score -= visitPenalty * 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
score -= deathPenalty * 1.6;
|
||||||
|
|
||||||
if (_survivalTicks > 0 && _nearestEnemy.HasValue)
|
if (_survivalTicks > 0 && _nearestEnemy.HasValue)
|
||||||
{
|
{
|
||||||
var safest = Opposite(DirectionFromVector(_nearestEnemy.Value.X, _nearestEnemy.Value.Y));
|
var safest = Opposite(DirectionFromVector(_nearestEnemy.Value.X, _nearestEnemy.Value.Y));
|
||||||
@@ -624,6 +684,18 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_escapeLockTicks > 0)
|
||||||
|
{
|
||||||
|
if (dir == _escapeDirection)
|
||||||
|
{
|
||||||
|
score += 4.5;
|
||||||
|
}
|
||||||
|
else if (dir == Opposite(_escapeDirection))
|
||||||
|
{
|
||||||
|
score -= 4.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_blockedDirectionTicks.TryGetValue(dir, out var blockedTicks) && blockedTicks > 0)
|
if (_blockedDirectionTicks.TryGetValue(dir, out var blockedTicks) && blockedTicks > 0)
|
||||||
{
|
{
|
||||||
score -= 5.0 + blockedTicks * 0.4;
|
score -= 5.0 + blockedTicks * 0.4;
|
||||||
@@ -690,6 +762,8 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
if (_enemyHeat.Count == 0)
|
if (_enemyHeat.Count == 0)
|
||||||
{
|
{
|
||||||
|
DecaySpatialHeat(_visitHeat, 0.94, 0.15);
|
||||||
|
DecaySpatialHeat(_deathHeat, 0.97, 0.10);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,6 +776,9 @@ public partial class MainWindow : Window
|
|||||||
_enemyHeat.Remove(key);
|
_enemyHeat.Remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DecaySpatialHeat(_visitHeat, 0.94, 0.15);
|
||||||
|
DecaySpatialHeat(_deathHeat, 0.97, 0.10);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyMovement(Direction direction, int steps)
|
private void ApplyMovement(Direction direction, int steps)
|
||||||
@@ -710,6 +787,7 @@ public partial class MainWindow : Window
|
|||||||
_x += dx * steps;
|
_x += dx * steps;
|
||||||
_y += dy * steps;
|
_y += dy * steps;
|
||||||
AddHeat(_x, _y, -2.5);
|
AddHeat(_x, _y, -2.5);
|
||||||
|
MarkVisit(_x, _y, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddHeat(int worldX, int worldY, double value)
|
private void AddHeat(int worldX, int worldY, double value)
|
||||||
@@ -764,6 +842,151 @@ public partial class MainWindow : Window
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateLevelProfile(string? levelName)
|
||||||
|
{
|
||||||
|
var newProfile = ResolveLevelProfile(levelName);
|
||||||
|
if (newProfile == _currentLevelProfile)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentLevelProfile = newProfile;
|
||||||
|
_sessionLogger?.LogEvent("level-profile", $"level={levelName ?? "unknown"};profile={newProfile}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LevelProfile ResolveLevelProfile(string? levelName)
|
||||||
|
{
|
||||||
|
var value = levelName?.Trim().ToLowerInvariant() ?? string.Empty;
|
||||||
|
if (value.Contains("bigger")) return LevelProfile.BiggerMap;
|
||||||
|
if (value.Contains("more bots")) return LevelProfile.MoreBots;
|
||||||
|
if (value.Contains("new bots")) return LevelProfile.NewBots;
|
||||||
|
if (value.Contains("default")) return LevelProfile.Default;
|
||||||
|
return LevelProfile.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double PressureHpThreshold(LevelProfile profile) => profile switch
|
||||||
|
{
|
||||||
|
LevelProfile.BiggerMap => 0.66,
|
||||||
|
LevelProfile.MoreBots => 0.62,
|
||||||
|
LevelProfile.NewBots => 0.68,
|
||||||
|
LevelProfile.Default => 0.58,
|
||||||
|
_ => 0.60,
|
||||||
|
};
|
||||||
|
|
||||||
|
private int PressureThreatThreshold(LevelProfile profile) => profile switch
|
||||||
|
{
|
||||||
|
LevelProfile.BiggerMap => 2,
|
||||||
|
LevelProfile.MoreBots => 2,
|
||||||
|
LevelProfile.NewBots => 2,
|
||||||
|
LevelProfile.Default => 2,
|
||||||
|
_ => 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
private int SpecialThreatThreshold(LevelProfile profile) => profile switch
|
||||||
|
{
|
||||||
|
LevelProfile.MoreBots => 4,
|
||||||
|
LevelProfile.NewBots => 3,
|
||||||
|
LevelProfile.BiggerMap => 2,
|
||||||
|
LevelProfile.Default => 2,
|
||||||
|
_ => 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
private double SpecialHpFloor(LevelProfile profile) => profile switch
|
||||||
|
{
|
||||||
|
LevelProfile.MoreBots => 0.78,
|
||||||
|
LevelProfile.NewBots => 0.55,
|
||||||
|
LevelProfile.BiggerMap => 0.42,
|
||||||
|
LevelProfile.Default => 0.35,
|
||||||
|
_ => 0.40,
|
||||||
|
};
|
||||||
|
|
||||||
|
private double DashHpThreshold(LevelProfile profile) => profile switch
|
||||||
|
{
|
||||||
|
LevelProfile.BiggerMap => 0.50,
|
||||||
|
LevelProfile.NewBots => 0.58,
|
||||||
|
LevelProfile.MoreBots => 0.40,
|
||||||
|
LevelProfile.Default => 0.30,
|
||||||
|
_ => 0.45,
|
||||||
|
};
|
||||||
|
|
||||||
|
private int EscapeLockTicks(LevelProfile profile) => profile switch
|
||||||
|
{
|
||||||
|
LevelProfile.BiggerMap => 8,
|
||||||
|
LevelProfile.NewBots => 9,
|
||||||
|
LevelProfile.MoreBots => 6,
|
||||||
|
LevelProfile.Default => 5,
|
||||||
|
_ => 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
private bool ShouldUseImmediateHit(LevelProfile profile, double hpRatio, int closeThreats, bool underPressure) => profile switch
|
||||||
|
{
|
||||||
|
LevelProfile.Default => hpRatio >= 0.22 && closeThreats <= 2,
|
||||||
|
LevelProfile.BiggerMap => hpRatio >= 0.25 && closeThreats <= 2,
|
||||||
|
LevelProfile.MoreBots => hpRatio >= 0.35 && closeThreats <= 3,
|
||||||
|
LevelProfile.NewBots => !underPressure && hpRatio >= 0.50,
|
||||||
|
_ => hpRatio >= 0.30 && closeThreats <= 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
private bool ShouldRetreatOnImmediate(LevelProfile profile, double hpRatio, int closeThreats) => profile switch
|
||||||
|
{
|
||||||
|
LevelProfile.Default => hpRatio < 0.22 || closeThreats >= 3,
|
||||||
|
LevelProfile.BiggerMap => hpRatio < 0.30 || closeThreats >= 3,
|
||||||
|
LevelProfile.MoreBots => hpRatio < 0.32 || closeThreats >= 3,
|
||||||
|
LevelProfile.NewBots => hpRatio < 0.68 || closeThreats >= 2,
|
||||||
|
_ => hpRatio < 0.30 || closeThreats >= 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
private void MarkVisit(int x, int y, double amount)
|
||||||
|
{
|
||||||
|
var key = (x, y);
|
||||||
|
_visitHeat.TryGetValue(key, out var current);
|
||||||
|
_visitHeat[key] = Math.Min(current + amount, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MarkDeathZone(int x, int y)
|
||||||
|
{
|
||||||
|
for (var dx = -1; dx <= 1; dx++)
|
||||||
|
{
|
||||||
|
for (var dy = -1; dy <= 1; dy++)
|
||||||
|
{
|
||||||
|
var key = (x + dx, y + dy);
|
||||||
|
_deathHeat.TryGetValue(key, out var current);
|
||||||
|
_deathHeat[key] = Math.Min(current + (dx == 0 && dy == 0 ? 4.0 : 1.5), 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DecaySpatialHeat(Dictionary<(int X, int Y), double> heatMap, double decay, double minimum)
|
||||||
|
{
|
||||||
|
if (heatMap.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var key in heatMap.Keys.ToArray())
|
||||||
|
{
|
||||||
|
heatMap[key] *= decay;
|
||||||
|
if (heatMap[key] < minimum)
|
||||||
|
{
|
||||||
|
heatMap.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double VisitHeatFor(Direction direction)
|
||||||
|
{
|
||||||
|
var (dx, dy) = Delta(direction);
|
||||||
|
var key = (_x + dx, _y + dy);
|
||||||
|
return _visitHeat.TryGetValue(key, out var heat) ? heat : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double DeathHeatFor(Direction direction)
|
||||||
|
{
|
||||||
|
var (dx, dy) = Delta(direction);
|
||||||
|
var key = (_x + dx, _y + dy);
|
||||||
|
return _deathHeat.TryGetValue(key, out var heat) ? heat : 0;
|
||||||
|
}
|
||||||
|
|
||||||
private bool Ready(BotAction action)
|
private bool Ready(BotAction action)
|
||||||
{
|
{
|
||||||
if (!_lastUse.TryGetValue(action, out var time))
|
if (!_lastUse.TryGetValue(action, out var time))
|
||||||
@@ -794,10 +1017,15 @@ public partial class MainWindow : Window
|
|||||||
_nearestEnemy = null;
|
_nearestEnemy = null;
|
||||||
_recentDamageTicks = 0;
|
_recentDamageTicks = 0;
|
||||||
_survivalTicks = 0;
|
_survivalTicks = 0;
|
||||||
|
_escapeLockTicks = 0;
|
||||||
_lastUnderPressure = false;
|
_lastUnderPressure = false;
|
||||||
_lastMoveDirection = Direction.North;
|
_lastMoveDirection = Direction.North;
|
||||||
|
_escapeDirection = Direction.North;
|
||||||
|
_pressureStreak = 0;
|
||||||
|
|
||||||
_enemyHeat.Clear();
|
_enemyHeat.Clear();
|
||||||
|
_visitHeat.Clear();
|
||||||
|
_deathHeat.Clear();
|
||||||
_lastRadar.Clear();
|
_lastRadar.Clear();
|
||||||
_lastPeek.Clear();
|
_lastPeek.Clear();
|
||||||
_lastUse.Clear();
|
_lastUse.Clear();
|
||||||
@@ -819,7 +1047,7 @@ public partial class MainWindow : Window
|
|||||||
$"Level: {_levelName} | Kills: {_kills} | Deaths: {_deaths} | K/D: {kdr} | " +
|
$"Level: {_levelName} | Kills: {_kills} | Deaths: {_deaths} | K/D: {kdr} | " +
|
||||||
$"HP: {_currentHealth.ToString("0.0", CultureInfo.InvariantCulture)}/{_maxHealth.ToString("0.0", CultureInfo.InvariantCulture)} ({hpPercent.ToString("0", CultureInfo.InvariantCulture)}%) | " +
|
$"HP: {_currentHealth.ToString("0.0", CultureInfo.InvariantCulture)}/{_maxHealth.ToString("0.0", CultureInfo.InvariantCulture)} ({hpPercent.ToString("0", CultureInfo.InvariantCulture)}%) | " +
|
||||||
$"Progress: {_progress.ToString("0.0", CultureInfo.InvariantCulture)}% | Remaining: {_remainingTime.ToString("0.00", CultureInfo.InvariantCulture)} min | " +
|
$"Progress: {_progress.ToString("0.0", CultureInfo.InvariantCulture)}% | Remaining: {_remainingTime.ToString("0.00", CultureInfo.InvariantCulture)} min | " +
|
||||||
$"Pos (estimated): ({_x}, {_y}) | SurvivalMode: {(_survivalTicks > 0 ? "ON" : "OFF")}";
|
$"Pos (estimated): ({_x}, {_y}) | Profile: {_currentLevelProfile} | SurvivalMode: {(_survivalTicks > 0 ? "ON" : "OFF")}";
|
||||||
|
|
||||||
var nearest = _nearestEnemy.HasValue ? $"({_nearestEnemy.Value.X}, {_nearestEnemy.Value.Y})" : "none";
|
var nearest = _nearestEnemy.HasValue ? $"({_nearestEnemy.Value.X}, {_nearestEnemy.Value.Y})" : "none";
|
||||||
var radar = _lastRadar.Count == 0
|
var radar = _lastRadar.Count == 0
|
||||||
@@ -955,6 +1183,15 @@ public partial class MainWindow : Window
|
|||||||
string Reason,
|
string Reason,
|
||||||
Func<CancellationToken, Task<bool>> Execute);
|
Func<CancellationToken, Task<bool>> Execute);
|
||||||
|
|
||||||
|
private enum LevelProfile
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Default,
|
||||||
|
BiggerMap,
|
||||||
|
MoreBots,
|
||||||
|
NewBots,
|
||||||
|
}
|
||||||
|
|
||||||
private enum Direction
|
private enum Direction
|
||||||
{
|
{
|
||||||
North = 0,
|
North = 0,
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ dotnet run --project /home/tikaiz/RiderProjects/digitalDojo/DDApp/DDApplication/
|
|||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Position and map are inferred from your own movement + sensor calls (not an authoritative server map).
|
- Position and map are inferred from your own movement + sensor calls (not an authoritative server map).
|
||||||
|
- The strategy is level-aware:
|
||||||
|
- `Default Level`: balanced combat with short escape commitments
|
||||||
|
- `Bigger Map Level`: more scouting and stronger anti-loop exploration
|
||||||
|
- `More Bots Level`: safer special-attack usage and tighter targeting
|
||||||
|
- `New Bots Level`: more ranged pressure and less chasing of teleporting targets
|
||||||
- The strategy is heuristic and tunable; it is designed for high kill throughput while respecting API cooldowns.
|
- The strategy is heuristic and tunable; it is designed for high kill throughput while respecting API cooldowns.
|
||||||
- Session logs are written to `~/.local/share/DDApplication/logs` on Linux:
|
- Session logs are written to `~/.local/share/DDApplication/logs` on Linux:
|
||||||
- `*.events.csv` contains timestamped telemetry, decisions, and events
|
- `*.events.csv` contains timestamped telemetry, decisions, and events
|
||||||
|
|||||||
338
DDApp/DDApplication/levelübersicht.txt
Normal file
338
DDApp/DDApplication/levelübersicht.txt
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
Digital Dojo <https://dd.countit.at/> Menü //
|
||||||
|
|
||||||
|
* //Coden lernen<https://dd.countit.at/programmieren-lernen>
|
||||||
|
o //Programmier-Roadmap <https://dd.countit.at/programmieren-lernen>
|
||||||
|
o //Programmierschule <https://dd.countit.at/programmierschule>
|
||||||
|
o //Übungsräume <https://dd.countit.at/dojos>
|
||||||
|
o //Katas <https://dd.countit.at/katas>
|
||||||
|
o //Tutorials <https://dd.countit.at/tutorials>
|
||||||
|
* //Dojo Game<https://dd.countit.at/dojo-game/multiplayer-modus>
|
||||||
|
o //Besiege unseren Bot <https://dd.countit.at/dojo-game>
|
||||||
|
o //Spieleinstieg <https://dd.countit.at/dojo-game/spieleinstieg>
|
||||||
|
o //Levels & Bots <https://dd.countit.at/dojo-game/levels>
|
||||||
|
o //Leaderboard <https://dd.countit.at/dojo-game/leaderboard>
|
||||||
|
o //Multiplayer Modus <https://dd.countit.at/dojo-game/
|
||||||
|
multiplayer-modus>
|
||||||
|
o //Tipps <https://dd.countit.at/dojo-game/tipps>
|
||||||
|
* //Programmier-Challenge <https://dd.countit.at/programmierwettbewerb>
|
||||||
|
* //Bewerben <https://dd.countit.at/bewerben>
|
||||||
|
* //Feedback <https://dd.countit.at/feedback>
|
||||||
|
* // <#>
|
||||||
|
Ausloggen
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
/
|
||||||
|
/
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
//Teilen //Facebook <https://www.facebook.com/sharer/sharer.php?
|
||||||
|
u=https%3A%2F%2Fdd.countit.at%2Fdojo-game%2Flevels> //Instagram
|
||||||
|
<https://www.instagram.com/count_it_group/> //LinkedIn <https://
|
||||||
|
www.linkedin.com/shareArticle?
|
||||||
|
mini=true&url=https%3A%2F%2Fdd.countit.at%2Fdojo-
|
||||||
|
game%2Flevels&title=&summary=&source=> //E-Mail
|
||||||
|
<mailto:m.mustermann@countit.at?subject=Lese-
|
||||||
|
Tipp%3A%20Dojo%20Game%20%7C%20%C3%9Cbersicht%20der%20Levels%20%26%20Bots&body=Hey%2C%0D%0A%0D%0Aich%20habe%20auf%20https%3A%2F%2Fkarriere.countit.at%20einen%20Beitrag%20gefunden%2C%20der%20dich%20interessieren%20k%C3%B6nnte%3A%0D%0A%22Dojo%20Game%20%7C%20%C3%9Cbersicht%20der%20Levels%20%26%20Bots%22%0D%0ASchau%20ihn%20dir%20mal%20an%3A%20https%3A%2F%2Fkarriere.countit.at%2Fdojo-game%2Flevels%3Fkaid%3Dsharemail%0D%0A%0D%0ABeste%20Gr%C3%BC%C3%9Fe>
|
||||||
|
|
||||||
|
|
||||||
|
Übersicht der Levels & Bots
|
||||||
|
|
||||||
|
|
||||||
|
Dojo Game
|
||||||
|
|
||||||
|
|
||||||
|
//Dojo Game | Levelübersicht
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Im Folgenden findest du wichtige Infos und Eckdaten zu den einzelnen
|
||||||
|
Levels, welche im Spiel implementiert sind. Bestimmte Informationen
|
||||||
|
können sich unter den einzelnen Levels unterscheiden, daher ist es
|
||||||
|
wichtig, in deinem Algorithmus auf diese Attribute einzugehen.
|
||||||
|
Einen Einblick in die verschiedenen Bot-Typen findest du hier. <https://
|
||||||
|
dd.countit.at/dojo-game/levels#bots>
|
||||||
|
|
||||||
|
|
||||||
|
//1. Default Level
|
||||||
|
|
||||||
|
Das Default Level ist das erste Level und gleichzeitig auch das
|
||||||
|
Einsteiger-Level im Dojo Game.
|
||||||
|
|
||||||
|
Während der Laufzeit des Spiels wird jeder Gegner und auch dein Ritter,
|
||||||
|
sobald dieser getötet wird, sofort an einer anderen Stelle des
|
||||||
|
Spielfelds neu geboren. Nach Ablauf der Zeit wird das Spiel automatisch
|
||||||
|
beendet und die Kills und Deaths dieses Spiels dem Leaderboard
|
||||||
|
hinzugefügt. Danach kann direkt ein neues Spiel erstellt werden.
|
||||||
|
Generell ist es möglich, mehrere Gegner auf einmal zu schlagen. Stehen 2
|
||||||
|
Spieler auf demselben Block oder ineinander, so wird beiden gleich viel
|
||||||
|
Schaden zugefügt.
|
||||||
|
|
||||||
|
*Spielfeldgröße:* 15x15
|
||||||
|
|
||||||
|
*Bots:* (4) Default Bot x2, Tank Bot x2
|
||||||
|
|
||||||
|
*Dauer des Spiels:* erfolgreicher Abschluss des Levels oder 15 Minuten
|
||||||
|
|
||||||
|
*Erfolgreicher Abschluss:* 20 Kills
|
||||||
|
|
||||||
|
*Cooldowns:* Move = 250, Hit = 250, Radar = 250, SpecialAttack = 5000,
|
||||||
|
Peek = 1000, Scan = 2000, Dash = 3000, Teleport = 20000, Shoot = 1000
|
||||||
|
|
||||||
|
|
||||||
|
//2. Bigger Map Level
|
||||||
|
|
||||||
|
Im 2. Level des Games vergrößert sich die Spielfläche und die Default
|
||||||
|
Bots werden mehr!
|
||||||
|
|
||||||
|
Dieses Level wird nach 15 Minuten beendet. Wenn du nach Ablauf der Zeit
|
||||||
|
100% Progress hast, wirst du ins nächste Level weitergeleitet.
|
||||||
|
|
||||||
|
Während der Laufzeit des Spiels wird jeder Gegner und auch dein Ritter,
|
||||||
|
sobald dieser getötet wird, sofort an einer anderen Stelle des
|
||||||
|
Spielfelds neu geboren. Nach Ablauf der Zeit wird das Spiel automatisch
|
||||||
|
beendet und die Kills und Deaths dieses Spiels dem Leaderboard
|
||||||
|
hinzugefügt. Danach kann direkt ein neues Spiel erstellt werden.
|
||||||
|
Generell ist es möglich, mehrere Gegner auf einmal zu schlagen. Stehen 2
|
||||||
|
Spieler auf demselben Block oder ineinander, so wird beiden gleich viel
|
||||||
|
Schaden zugefügt.
|
||||||
|
|
||||||
|
*Spielfeldgröße:* 25x25
|
||||||
|
|
||||||
|
*Bots:* (5) Default Bot x5
|
||||||
|
|
||||||
|
*Dauer des Spiels:* 15 Minuten
|
||||||
|
|
||||||
|
*Erfolgreicher Abschluss:* 5 Kills
|
||||||
|
|
||||||
|
Cooldowns: Move = 250, Hit = 250, Radar = 250, SpecialAttack = 5000,
|
||||||
|
Peek = 1000, Scan = 2000, Dash = 3000, Teleport = 20000, Shoot = 1000
|
||||||
|
|
||||||
|
|
||||||
|
//3. More Bots Level
|
||||||
|
|
||||||
|
In diesem Level kämpfst du gegen 6 Tank Bots - pass auf, der Radius der
|
||||||
|
Specialattack ist in diesem Level von 3 Blöcken auf 1 Block geschrumpft!
|
||||||
|
|
||||||
|
Während der Laufzeit des Spiels wird jeder Gegner und auch dein Ritter,
|
||||||
|
sobald dieser getötet wird, sofort an einer anderen Stelle des
|
||||||
|
Spielfelds neu geboren. Nach Ablauf der Zeit wird das Spiel automatisch
|
||||||
|
beendet und die Kills und Deaths dieses Spiels dem Leaderboard
|
||||||
|
hinzugefügt. Danach kann direkt ein neues Spiel erstellt werden.
|
||||||
|
Generell ist es möglich, mehrere Gegner auf einmal zu schlagen. Stehen 2
|
||||||
|
Spieler auf demselben Block oder ineinander, so wird beiden gleich viel
|
||||||
|
Schaden zugefügt.
|
||||||
|
|
||||||
|
*Spielfeldgröße:* 25x25
|
||||||
|
|
||||||
|
*Bots:* (6) Tank Bot x6
|
||||||
|
|
||||||
|
*Dauer des Spiels: *15 Minuten oder erfolgreicher Abschluss des Levels
|
||||||
|
|
||||||
|
*Erfolgreicher Abschluss:* 10 Kills
|
||||||
|
|
||||||
|
Cooldowns: Move = 250, Hit = 250, Radar = 250, SpecialAttack = 5000,
|
||||||
|
Peek = 1000, Scan = 2000, Dash = 3000, Teleport = 20000, Shoot = 1000
|
||||||
|
|
||||||
|
|
||||||
|
//4. New Bots Level
|
||||||
|
|
||||||
|
Hier kämpfst du gegen 5 Psy Bots, welche mit einer neuen Fähigkeit
|
||||||
|
ausgestattet wurden.
|
||||||
|
|
||||||
|
Während der Laufzeit des Spiels wird jeder Gegner und auch dein Ritter,
|
||||||
|
sobald dieser getötet wird, sofort an einer anderen Stelle des
|
||||||
|
Spielfelds neu geboren. Nach Ablauf der Zeit wird das Spiel automatisch
|
||||||
|
beendet und die Kills und Deaths dieses Spiels dem Leaderboard
|
||||||
|
hinzugefügt. Danach kann direkt ein neues Spiel erstellt werden.
|
||||||
|
Generell ist es möglich, mehrere Gegner auf einmal zu schlagen. Stehen 2
|
||||||
|
Spieler auf demselben Block oder ineinander, so wird beiden gleich viel
|
||||||
|
Schaden zugefügt.
|
||||||
|
|
||||||
|
*Spielfeldgröße:* 15x15
|
||||||
|
|
||||||
|
*Bots:* (5) Psy Bot x5
|
||||||
|
|
||||||
|
*Dauer des Spiels: *15 Minuten
|
||||||
|
|
||||||
|
*Erfolgreicher Abschluss:* 10 Kills
|
||||||
|
|
||||||
|
Cooldowns: Move = 250, Hit = 250, Radar = 250, SpecialAttack = 5000,
|
||||||
|
Peek = 1000, Scan = 2000, Dash = 3000, Teleport = 20000, Shoot = 1000
|
||||||
|
|
||||||
|
|
||||||
|
//Übersicht der Bots
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
In den Dojo Game Levels sind verschiedene Bot-Typen vorhanden. Jeder Bot
|
||||||
|
hat eine individuelle Fähigkeit und eigene Attribute, welche ihn
|
||||||
|
einzigartig machen. Hier findest du eine Liste der Bots und ihren Eckdaten.
|
||||||
|
|
||||||
|
|
||||||
|
//The Default Bot
|
||||||
|
|
||||||
|
Der Default Bot hat die selben Rahmenbedingungen wie ein echter Spieler.
|
||||||
|
Dies macht ihn, die Werte betreffend, gleich stark wie einen Spieler.
|
||||||
|
Sie unterscheiden sich nur in deren Implementierung.
|
||||||
|
|
||||||
|
*Lebenspunkte beim Start:* 10.0
|
||||||
|
|
||||||
|
*Maximale Lebenspunkte:* 10.0
|
||||||
|
|
||||||
|
*Einfacher Schaden:* 3
|
||||||
|
|
||||||
|
*Special-Attack:* 3 Blöcke Rundumschlag mit einfachem Schaden
|
||||||
|
|
||||||
|
|
||||||
|
//The Tank Bot
|
||||||
|
|
||||||
|
Der Tank Bot ist ein auf den ersten Blick schwächer erscheinender Bot,
|
||||||
|
welcher aber mit ansteigender Spieldauer immer stärker wird.
|
||||||
|
|
||||||
|
*Lebenspunkte beim Start:* 5.0
|
||||||
|
|
||||||
|
*Maximale Lebenspunkte:* 10.0
|
||||||
|
|
||||||
|
*Einfacher Schaden:* 3
|
||||||
|
|
||||||
|
*Special-Attack:* +1 Lebenspunkt zusätzlich und 3 Blöcke Rundumschlag
|
||||||
|
mit einfachem Schaden
|
||||||
|
|
||||||
|
|
||||||
|
//The Psy Bot
|
||||||
|
|
||||||
|
Der Psy Bot verhält sich gleich wie der Default Bot, jedoch
|
||||||
|
unterscheidet er sich deutlich von diesem durch seinen Special-Attack.
|
||||||
|
Dieser hat nämlich die Fähigkeit, sich beim Erhalten von Schaden oder
|
||||||
|
alle 5 Sekunden seinen Standort zu ändern und sich an eine zufällige
|
||||||
|
Position zu teleportieren.
|
||||||
|
|
||||||
|
*Lebenspunkte beim Start:* 10.0
|
||||||
|
|
||||||
|
*Maximale Lebenspunkte: *10.0
|
||||||
|
|
||||||
|
*Einfacher Schaden:* 3
|
||||||
|
|
||||||
|
*Special-Attack:* Standort ändern an einen zufälligen Ort am Spielfeld
|
||||||
|
|
||||||
|
|
||||||
|
Los geht's mit dem Dojo-Game!
|
||||||
|
|
||||||
|
Erster Platz am Leaderboard? Klingt genial!
|
||||||
|
|
||||||
|
Dojo-Game //
|
||||||
|
<https://dd.countit.at/dojo-game>
|
||||||
|
/
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
|
||||||
|
Programmier-Challenge
|
||||||
|
|
||||||
|
Stell dich der Programmier-Challenge und miss dich mit deinen
|
||||||
|
Klassenkolleg*innen.
|
||||||
|
|
||||||
|
Jetzt teilnehmen! // <https://dd.countit.at/programmierwettbewerb>
|
||||||
|
/
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
|
||||||
|
Ultimative Coding-Roadmap
|
||||||
|
|
||||||
|
Unsere Roadmap weist dir den Weg zum Coding-Profi!
|
||||||
|
|
||||||
|
Mehr erfahren // <https://dd.countit.at/programmieren-lernen>
|
||||||
|
/
|
||||||
|
|
||||||
|
/
|
||||||
|
|
||||||
|
|
||||||
|
Bewerbung bei COUNT IT
|
||||||
|
|
||||||
|
Starte deine Karriere als Softwareentwickler*in bei COUNT IT.
|
||||||
|
|
||||||
|
Zum Formular! // <https://dd.countit.at/bewerben>
|
||||||
|
|
||||||
|
|
||||||
|
Über Digital Dojo
|
||||||
|
|
||||||
|
Das Digital Dojo ist der virtuelle Übungsraum von COUNT IT.
|
||||||
|
|
||||||
|
Angehende Programmierer*innen, Code-Neulinge, Wiedereinsteiger*innen und
|
||||||
|
Fortgeschrittene finden hier das nötige Rüstzeug für ihre Karriere.
|
||||||
|
|
||||||
|
Du möchtest deine Lehre bei COUNT IT starten? Dann bist du hier richtig
|
||||||
|
- besiege deine Gegner im Dojo Game und sichere dir deine Lehrstelle!
|
||||||
|
|
||||||
|
Inspire your career.
|
||||||
|
|
||||||
|
|
||||||
|
Navigation
|
||||||
|
|
||||||
|
Programmieren lernen <https://dd.countit.at/programmieren-lernen> News
|
||||||
|
<https://dd.countit.at/news>
|
||||||
|
Programmierschule <https://dd.countit.at/programmierschule> Partner
|
||||||
|
<https://dd.countit.at/partner>
|
||||||
|
Programmier-Challenge <https://dd.countit.at/programmierwettbewerb>
|
||||||
|
Dojo Game <https://dd.countit.at/dojo-game>
|
||||||
|
|
||||||
|
|
||||||
|
Datenschutz <https://www.countit.at/datenschutz> Karriere @ COUNT IT
|
||||||
|
<https://karriere.countit.at/>
|
||||||
|
Impressum <https://www.countit.at/impressum> COUNT IT
|
||||||
|
Softwarehouse <https://it.countit.at/>
|
||||||
|
|
||||||
|
|
||||||
|
Newsletter abonnieren
|
||||||
|
|
||||||
|
Der COUNT IT Newsletter liefert viermal jährlich interessante
|
||||||
|
Neuigkeiten über das Unternehmen. Gleich anfordern!
|
||||||
|
|
||||||
|
E-Mail-Adresse
|
||||||
|
Senden
|
||||||
|
|
||||||
|
// <https://de-de.facebook.com/COUNTITGroup/> // <https://
|
||||||
|
www.instagram.com/count_it_group/> // <https://de.linkedin.com/company/
|
||||||
|
count-it-group>
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
COUNT IT Group verwendet Cookies, um die Website bestmöglich an die
|
||||||
|
Bedürfnisse der User anpassen zu können.
|
||||||
|
|
||||||
|
* Google
|
||||||
|
* Microsoft
|
||||||
|
* Ahrefs
|
||||||
|
* Statistik
|
||||||
|
|
||||||
|
Akzeptieren
|
||||||
|
Individuelle EinstellungenIndividuelle Einstellungen ausblenden |
|
||||||
|
Datenschutzerklärung <https://www.countit.at/datenschutz>
|
||||||
|
|
||||||
|
Google
|
||||||
|
|
||||||
|
Google Analytics und Optimize aggregiert Daten über unsere Besucher und
|
||||||
|
ihr Verhalten auf unserer Website. Wir nutzen diese Informationen zur
|
||||||
|
Verbesserung unserer Seite.
|
||||||
|
|
||||||
|
Microsoft
|
||||||
|
|
||||||
|
Microsoft aggregiert Daten über unsere Besucher und ihr Verhalten auf
|
||||||
|
unserer Website. Wir nutzen diese Informationen zur Verbesserung unserer
|
||||||
|
Seite.
|
||||||
|
|
||||||
|
Ahrefs
|
||||||
|
|
||||||
|
Ahrefs aggregiert Daten über unsere Besucher und ihr Verhalten auf
|
||||||
|
unserer Website. Wir nutzen diese Informationen zur Verbesserung unserer
|
||||||
|
Seite.
|
||||||
|
|
||||||
|
Statistik
|
||||||
|
|
||||||
|
Es werden grundlegend notwendige, anonymisierte Userdaten aufgezeichnet,
|
||||||
|
welche nur von uns für Auswertungen verwendet werden!
|
||||||
|
|
||||||
|
Cookie Entscheidung speichern
|
||||||
Reference in New Issue
Block a user