From 39e943b25eecee67064876b7c2d0ba5bd1c0113d Mon Sep 17 00:00:00 2001 From: Tim Kainz Date: Thu, 2 Apr 2026 00:12:02 +0200 Subject: [PATCH] Improved --- DDApp/DDApplication/MainWindow.axaml.cs | 253 +++++++++++++++++- DDApp/DDApplication/README.md | 5 + DDApp/DDApplication/levelübersicht.txt | 338 ++++++++++++++++++++++++ 3 files changed, 588 insertions(+), 8 deletions(-) create mode 100644 DDApp/DDApplication/levelübersicht.txt diff --git a/DDApp/DDApplication/MainWindow.axaml.cs b/DDApp/DDApplication/MainWindow.axaml.cs index 5dae0ee..2a46ef5 100644 --- a/DDApp/DDApplication/MainWindow.axaml.cs +++ b/DDApp/DDApplication/MainWindow.axaml.cs @@ -36,6 +36,8 @@ public partial class MainWindow : Window private readonly DigitalDojoApiClient _client; private readonly ObservableCollection _logEntries = []; 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 _lastRadar = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _lastPeek = []; private readonly Dictionary _lastUse = []; @@ -65,9 +67,13 @@ public partial class MainWindow : Window private DateTimeOffset _lastStatusAt = DateTimeOffset.MinValue; private (int X, int Y)? _nearestEnemy; private Direction _lastMoveDirection = Direction.North; + private Direction _escapeDirection = Direction.North; private int _recentDamageTicks; private int _survivalTicks; + private int _escapeLockTicks; private bool _lastUnderPressure; + private int _pressureStreak; + private LevelProfile _currentLevelProfile = LevelProfile.Unknown; public MainWindow() { @@ -234,6 +240,11 @@ public partial class MainWindow : Window _survivalTicks--; } + if (_escapeLockTicks > 0) + { + _escapeLockTicks--; + } + DecayBlockedDirections(); await PollTelemetryAsync(ct); DecayHeat(); @@ -297,6 +308,7 @@ public partial class MainWindow : Window _progress = stats.Level?.Progress ?? _progress; _remainingTime = stats.Level?.Remainingtime ?? _remainingTime; _levelName = stats.Level?.Name ?? _levelName; + UpdateLevelProfile(_levelName); if (previousHealth > 0.1 && _currentHealth + 0.05 < previousHealth) { @@ -312,11 +324,15 @@ public partial class MainWindow : Window _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; _y = 0; _enemyHeat.Clear(); _nearestEnemy = null; + MarkDeathZone(_x, _y); _sessionLogger?.LogEvent("death", $"death_count={_deaths};death_window_90s={_recentDeaths.Count}"); } @@ -330,7 +346,7 @@ public partial class MainWindow : Window _levelName, _x, _y, - $"survival_ticks={_survivalTicks};damage_ticks={_recentDamageTicks}"); + $"survival_ticks={_survivalTicks};damage_ticks={_recentDamageTicks};escape_lock={_escapeLockTicks};pressure_streak={_pressureStreak}"); } catch (Exception ex) { @@ -420,6 +436,7 @@ public partial class MainWindow : Window private BotDecision? BuildDecision() { var hpRatio = _maxHealth > 0.1 ? _currentHealth / _maxHealth : 1.0; + var profile = _currentLevelProfile; var immediateDir = _lastPeek .Where(x => x.Value.Executed && x.Value.PlayersInSight > 0 && x.Value.SightedPlayerDistance == 1) @@ -432,10 +449,22 @@ public partial class MainWindow : Window 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; - 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( "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; 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); - if (Ready(BotAction.Dash) && (hpRatio < 0.40 || _survivalTicks > 0)) + if (Ready(BotAction.Dash) && (hpRatio < DashHpThreshold(profile) || _pressureStreak >= 3 || _survivalTicks > 0)) { return new BotDecision( "dash-retreat", @@ -478,6 +507,8 @@ public partial class MainWindow : Window if (response.Executed) { ApplyMovement(retreatDirection, Math.Max(response.BlocksDashed, 1)); + _escapeDirection = retreatDirection; + _escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile)); } return response.Executed; @@ -496,6 +527,8 @@ public partial class MainWindow : Window if (moved) { ApplyMovement(retreatDirection, 1); + _escapeDirection = retreatDirection; + _escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile)); } return response.Executed; @@ -558,6 +591,11 @@ public partial class MainWindow : Window if (moved) { ApplyMovement(moveDirection, 1); + if (underPressure) + { + _escapeDirection = moveDirection; + _escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile)); + } } return response.Executed; @@ -569,6 +607,14 @@ public partial class MainWindow : Window 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.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) { 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) { score -= 5.0 + blockedTicks * 0.4; @@ -690,6 +762,8 @@ public partial class MainWindow : Window { if (_enemyHeat.Count == 0) { + DecaySpatialHeat(_visitHeat, 0.94, 0.15); + DecaySpatialHeat(_deathHeat, 0.97, 0.10); return; } @@ -702,6 +776,9 @@ public partial class MainWindow : Window _enemyHeat.Remove(key); } } + + DecaySpatialHeat(_visitHeat, 0.94, 0.15); + DecaySpatialHeat(_deathHeat, 0.97, 0.10); } private void ApplyMovement(Direction direction, int steps) @@ -710,6 +787,7 @@ public partial class MainWindow : Window _x += dx * steps; _y += dy * steps; AddHeat(_x, _y, -2.5); + MarkVisit(_x, _y, 1.0); } 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) { if (!_lastUse.TryGetValue(action, out var time)) @@ -794,10 +1017,15 @@ public partial class MainWindow : Window _nearestEnemy = null; _recentDamageTicks = 0; _survivalTicks = 0; + _escapeLockTicks = 0; _lastUnderPressure = false; _lastMoveDirection = Direction.North; + _escapeDirection = Direction.North; + _pressureStreak = 0; _enemyHeat.Clear(); + _visitHeat.Clear(); + _deathHeat.Clear(); _lastRadar.Clear(); _lastPeek.Clear(); _lastUse.Clear(); @@ -819,7 +1047,7 @@ public partial class MainWindow : Window $"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)}%) | " + $"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 radar = _lastRadar.Count == 0 @@ -955,6 +1183,15 @@ public partial class MainWindow : Window string Reason, Func> Execute); + private enum LevelProfile + { + Unknown, + Default, + BiggerMap, + MoreBots, + NewBots, + } + private enum Direction { North = 0, diff --git a/DDApp/DDApplication/README.md b/DDApp/DDApplication/README.md index f967392..e9369e2 100644 --- a/DDApp/DDApplication/README.md +++ b/DDApp/DDApplication/README.md @@ -32,6 +32,11 @@ dotnet run --project /home/tikaiz/RiderProjects/digitalDojo/DDApp/DDApplication/ ## Notes - 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. - Session logs are written to `~/.local/share/DDApplication/logs` on Linux: - `*.events.csv` contains timestamped telemetry, decisions, and events diff --git a/DDApp/DDApplication/levelübersicht.txt b/DDApp/DDApplication/levelübersicht.txt new file mode 100644 index 0000000..fdbe526 --- /dev/null +++ b/DDApp/DDApplication/levelübersicht.txt @@ -0,0 +1,338 @@ +Digital Dojo Menü // + + * //Coden lernen + o //Programmier-Roadmap + o //Programmierschule + o //Übungsräume + o //Katas + o //Tutorials + * //Dojo Game + o //Besiege unseren Bot + o //Spieleinstieg + o //Levels & Bots + o //Leaderboard + o //Multiplayer Modus + o //Tipps + * //Programmier-Challenge + * //Bewerben + * //Feedback + * // <#> + Ausloggen + +/ + +/ +/ + +/ + +//Teilen //Facebook //Instagram + //LinkedIn //E-Mail + + + + Ü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. + + + //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 // + +/ + +/ + + + Programmier-Challenge + +Stell dich der Programmier-Challenge und miss dich mit deinen +Klassenkolleg*innen. + +Jetzt teilnehmen! // +/ + +/ + + + Ultimative Coding-Roadmap + +Unsere Roadmap weist dir den Weg zum Coding-Profi! + +Mehr erfahren // +/ + +/ + + + Bewerbung bei COUNT IT + +Starte deine Karriere als Softwareentwickler*in bei COUNT IT. + +Zum Formular! // + + + Ü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 News + +Programmierschule Partner + +Programmier-Challenge +Dojo Game + + +Datenschutz Karriere @ COUNT IT + +Impressum COUNT IT +Softwarehouse + + + Newsletter abonnieren + +Der COUNT IT Newsletter liefert viermal jährlich interessante +Neuigkeiten über das Unternehmen. Gleich anfordern! + +E-Mail-Adresse +Senden + +// // // + +// + +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 | +Datenschutz­erklärung + +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