before mutliplayer

This commit is contained in:
2026-04-02 18:02:15 +02:00
parent fa42cec01c
commit f004205069
2 changed files with 449 additions and 31 deletions

View File

@@ -66,6 +66,8 @@ public partial class MainWindow : Window
private DateTimeOffset _lastStatsAt = DateTimeOffset.MinValue; private DateTimeOffset _lastStatsAt = DateTimeOffset.MinValue;
private DateTimeOffset _lastStatusAt = DateTimeOffset.MinValue; private DateTimeOffset _lastStatusAt = DateTimeOffset.MinValue;
private (int X, int Y)? _nearestEnemy; private (int X, int Y)? _nearestEnemy;
private (int X, int Y)? _lastObservedEnemyRelative;
private (int X, int Y)? _enemyVelocityEstimate;
private Direction _lastMoveDirection = Direction.North; private Direction _lastMoveDirection = Direction.North;
private Direction _escapeDirection = Direction.North; private Direction _escapeDirection = Direction.North;
private int _recentDamageTicks; private int _recentDamageTicks;
@@ -402,11 +404,20 @@ public partial class MainWindow : Window
{ {
var scan = await _client.ScanAsync(_playerToken, ct); var scan = await _client.ScanAsync(_playerToken, ct);
MarkUsed(BotAction.Scan); MarkUsed(BotAction.Scan);
var previousEnemy = _nearestEnemy;
var sx = scan.DifferenceToNearestPlayer?.X; var sx = scan.DifferenceToNearestPlayer?.X;
var sy = scan.DifferenceToNearestPlayer?.Y; var sy = scan.DifferenceToNearestPlayer?.Y;
_nearestEnemy = sx.HasValue && sy.HasValue ? (sx.Value, sy.Value) : null; _nearestEnemy = sx.HasValue && sy.HasValue ? (sx.Value, sy.Value) : null;
if (_nearestEnemy.HasValue) if (_nearestEnemy.HasValue)
{ {
if (previousEnemy.HasValue)
{
_enemyVelocityEstimate = (
_nearestEnemy.Value.X - previousEnemy.Value.X,
_nearestEnemy.Value.Y - previousEnemy.Value.Y);
}
_lastObservedEnemyRelative = _nearestEnemy;
AddHeat(_x + _nearestEnemy.Value.X, _y + _nearestEnemy.Value.Y, 5.5); AddHeat(_x + _nearestEnemy.Value.X, _y + _nearestEnemy.Value.Y, 5.5);
} }
} }
@@ -461,6 +472,26 @@ public partial class MainWindow : Window
var underPressure = hpRatio < PressureHpThreshold(profile) || _recentDamageTicks > 0 || closeThreats >= PressureThreatThreshold(profile) || _survivalTicks > 0 || _pressureStreak >= 2; var underPressure = hpRatio < PressureHpThreshold(profile) || _recentDamageTicks > 0 || closeThreats >= PressureThreatThreshold(profile) || _survivalTicks > 0 || _pressureStreak >= 2;
_lastUnderPressure = underPressure; _lastUnderPressure = underPressure;
var predictedEnemy = PredictEnemyRelative();
var bestSight = _lastPeek
.Where(x => x.Value.Executed && x.Value.PlayersInSight > 0)
.OrderBy(x => x.Value.SightedPlayerDistance ?? int.MaxValue)
.FirstOrDefault();
if (Ready(BotAction.Hit) && immediateDir.HasValue && closeThreats >= 1)
{
var dir = immediateDir.Value;
return new BotDecision(
"hit-lock",
$"Enemy committed in close range at {dir}; preemptive melee pressure.",
async ct =>
{
var response = await _client.HitAsync(_playerToken, (int)dir, ct);
MarkUsed(BotAction.Hit);
return response.Executed;
});
}
// Teleport if we can and enemies are nearby - aggressive repositioning for kills // Teleport if we can and enemies are nearby - aggressive repositioning for kills
if (Ready(BotAction.Teleport) && closeThreats >= 1 && _nearestEnemy.HasValue) if (Ready(BotAction.Teleport) && closeThreats >= 1 && _nearestEnemy.HasValue)
@@ -487,10 +518,28 @@ public partial class MainWindow : Window
}); });
} }
if (Ready(BotAction.Dash) && TryGetAggressiveDashDirection(predictedEnemy, bestSight, out var dashDirection, out var dashReason))
{
return new BotDecision(
"dash-aggro",
dashReason,
async ct =>
{
var response = await _client.DashAsync(_playerToken, (int)dashDirection, ct);
MarkUsed(BotAction.Dash);
if (response.Executed)
{
ApplyMovement(dashDirection, Math.Max(response.BlocksDashed, 1));
}
return response.Executed;
});
}
var specialThreatThreshold = SpecialThreatThreshold(profile); var specialThreatThreshold = SpecialThreatThreshold(profile);
var specialHpFloor = SpecialHpFloor(profile); var specialHpFloor = SpecialHpFloor(profile);
if (Ready(BotAction.Special) && closeThreats >= specialThreatThreshold) if (Ready(BotAction.Special) && closeThreats >= specialThreatThreshold && (hpRatio >= specialHpFloor || closeThreats > specialThreatThreshold))
{ {
return new BotDecision( return new BotDecision(
"specialattack", "specialattack",
@@ -575,22 +624,14 @@ public partial class MainWindow : Window
}); });
} }
var bestSight = _lastPeek if (Ready(BotAction.Shoot) && TryGetPredictiveShotDirection(bestSight.Key, bestSight.Value, predictedEnemy, out var shotDirection, out var shotReason))
.Where(x => x.Value.Executed && x.Value.PlayersInSight > 0)
.OrderBy(x => x.Value.SightedPlayerDistance ?? int.MaxValue)
.FirstOrDefault();
// More aggressive shooting
if (bestSight.Value is not null && Ready(BotAction.Shoot))
{ {
var dir = bestSight.Key;
var dist = bestSight.Value.SightedPlayerDistance ?? 99;
return new BotDecision( return new BotDecision(
"shoot", "shoot",
$"Enemy at {dir} ({dist} dist); ranged kill.", shotReason,
async ct => async ct =>
{ {
var response = await _client.ShootAsync(_playerToken, (int)dir, ct); var response = await _client.ShootAsync(_playerToken, (int)shotDirection, ct);
MarkUsed(BotAction.Shoot); MarkUsed(BotAction.Shoot);
return response.Executed; return response.Executed;
}); });
@@ -641,7 +682,6 @@ public partial class MainWindow : Window
foreach (var dir in scored.Keys.ToArray()) foreach (var dir in scored.Keys.ToArray())
{ {
var score = 0.0; var score = 0.0;
// Always chase aggressively - ignore radar safety
score += scored[dir] * 0.5; score += scored[dir] * 0.5;
if (_lastPeek.TryGetValue(dir, out var peek) && peek.Executed) if (_lastPeek.TryGetValue(dir, out var peek) && peek.Executed)
@@ -656,8 +696,8 @@ public partial class MainWindow : Window
if (_nearestEnemy.HasValue) if (_nearestEnemy.HasValue)
{ {
var targetDir = DirectionFromVector(_nearestEnemy.Value.X, _nearestEnemy.Value.Y); var targetVector = PredictEnemyRelative() ?? _nearestEnemy.Value;
// Always move toward nearest enemy var targetDir = DirectionFromVector(targetVector.X, targetVector.Y);
if (dir == targetDir) if (dir == targetDir)
{ {
score += 12.0; score += 12.0;
@@ -927,6 +967,96 @@ public partial class MainWindow : Window
_ => hpRatio < 0.02, _ => hpRatio < 0.02,
}; };
private (int X, int Y)? PredictEnemyRelative()
{
if (!_nearestEnemy.HasValue)
{
return _lastObservedEnemyRelative;
}
if (_enemyVelocityEstimate is null)
{
return _nearestEnemy;
}
return (
_nearestEnemy.Value.X + _enemyVelocityEstimate.Value.X,
_nearestEnemy.Value.Y + _enemyVelocityEstimate.Value.Y);
}
private bool TryGetPredictiveShotDirection(
Direction? sightDirection,
PeekEntitiesAsyncResponse? sightResponse,
(int X, int Y)? predictedEnemy,
out Direction direction,
out string reason)
{
if (sightDirection is null || sightResponse is null)
{
if (predictedEnemy.HasValue)
{
direction = DirectionFromVector(predictedEnemy.Value.X, predictedEnemy.Value.Y);
reason = $"Predicted enemy lane -> {direction}; taking lead shot.";
return true;
}
direction = Direction.North;
reason = string.Empty;
return false;
}
var currentDir = sightDirection.Value;
var dist = sightResponse.SightedPlayerDistance ?? 99;
if (predictedEnemy.HasValue && dist >= 2)
{
var predictedDir = DirectionFromVector(predictedEnemy.Value.X, predictedEnemy.Value.Y);
if (predictedDir != currentDir)
{
direction = predictedDir;
reason = $"Enemy moving toward {predictedDir}; lead shot instead of static line {currentDir}.";
return true;
}
}
direction = currentDir;
reason = $"Enemy at {currentDir} ({dist} dist); ranged kill.";
return true;
}
private bool TryGetAggressiveDashDirection(
(int X, int Y)? predictedEnemy,
KeyValuePair<Direction, PeekEntitiesAsyncResponse>? bestSight,
out Direction direction,
out string reason)
{
if (_nearestEnemy.HasValue)
{
var target = predictedEnemy ?? _nearestEnemy.Value;
var dist = Math.Abs(target.X) + Math.Abs(target.Y);
if (dist >= 2 && dist <= 6)
{
direction = DirectionFromVector(target.X, target.Y);
reason = $"Aggressive gap-close toward enemy at {direction} (predicted distance {dist}).";
return true;
}
}
if (bestSight.HasValue && bestSight.Value.Value.Executed && bestSight.Value.Value.PlayersInSight > 0)
{
var dist = bestSight.Value.Value.SightedPlayerDistance ?? 99;
if (dist >= 2 && dist <= 5)
{
direction = bestSight.Value.Key;
reason = $"Enemy visible at {direction} ({dist} dist); dash to force close-range combat.";
return true;
}
}
direction = Direction.North;
reason = string.Empty;
return false;
}
private void MarkVisit(int x, int y, double amount) private void MarkVisit(int x, int y, double amount)
{ {
var key = (x, y); var key = (x, y);

View File

@@ -0,0 +1,288 @@
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%2Fmultiplayer-modus> //
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%2Fmultiplayer-
modus&title=&summary=&source=> //E-Mail <mailto:m.mustermann@countit.at?
subject=Lese-
Tipp%3A%20Dojo%20Game%20%7C%20Multiplayer%20Modus&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%20Multiplayer%20Modus%22%0D%0ASchau%20ihn%20dir%20mal%20an%3A%20https%3A%2F%2Fkarriere.countit.at%2Fdojo-game%2Fmultiplayer-modus%3Fkaid%3Dsharemail%0D%0A%0D%0ABeste%20Gr%C3%BC%C3%9Fe>
Der Multiplayer Modus
Dojo Game
//Multiplayer Modus
------------------------------------------------------------------------
Der Mutiplayer Modus basiert auf einem *Lobby System*. Erstelle eine
Lobby, lade Freunde ein oder spiele mit anderen Usern! Alle
Möglichkeiten stehen dir offen.
Desto mehr Spieler in einer Lobby sind, mit welchen du zuvor noch nicht
viel gespielt hast, desto mehr Punkte werden dir für eine gute
Platzierung gutgeschrieben. Der Mehrspieler Modus funktioniert mit den
selben API-Aufrufen <https://dd.countit.at/dojo-game/spieleinstieg#api>
wie der Singleplayer - es werden lediglich Bots durch Spieler ersetzt.
Du kannst daher deinen Einzelspieler Code auch für Mehrspieler Matches
verwenden.
*Viel Spaß!*
// Lobby Manager
------------------------------------------------------------------------
Lobby-Key
/
/
//
//Starten <https://dd.countit.at/lobby/start>
//Verlassen <https://dd.countit.at/lobby/leave>
//Schließen <https://dd.countit.at/lobby/close>
// tikaiz1 ADMIN
// Bot
// Bot
// Bot
// Bot
// Bot
// Dein derzeitiger Spielstand
Deine Tötungen: *21*
Deine Tode: *43*
Dein Leben: *(7/10)*
// Server Status
Server - Adresse: game-dd.countit.at
Port: 80 (optional)
Status: Online
//Wichtige Informationen
------------------------------------------------------------------------
//Der Start
Um eine Lobby zu starten, müssen mindestens die minimale Anzahl an
Spielern in einer Lobby sein - standardmäßig sind dies 2. Wir das Spiel
dann gestartet, wird die Lobby aus der Liste entfernt, da diese dann
nicht mehr benötigt wird. Spielern wird die eigene Lobby nach dem Start
nicht mehr angezeigt.
//Dein Algorithmus
Dein Algorithmus kann schon laufen bevor deine Lobby gestartet wurde.
Dein Code wird, sobald eine Verbindung zu einem Spiel hergestellt wurde,
automatisch Befehle ausführen.
*Vorsicht:* Du darfst deinen Singleplayer Code nicht 1:1 übernehmen.
Wenn du ein Lobby Game spielst, darfst du zuvor keine Singleplayer Game
Instanz erstellen.
//Dein Spieler
Dein Spieler ist im Multiplayer mit den selben Stärken und Fähigkeiten
ausgestattet wie im Singleplayer. In diesem Modus sind alle Spieler
gleich stark - es gibt keine unterschiedlichen Klassen, denn es geht
darum, wer den besten Code hat.
//Rangliste
Unsere Rangliste wird durch Kills und an der Vielzahl der verschiedenen
Gegner in einem Match berechnet. Spielst du gegen neue Gegner oder
andere Spieler, mit welchen du noch nicht all zu viel gespielt hast,
bekommst du mehr Punkte als wenn du beispielsweise immer mit denselben 3
Freunden spielst.
//Public/Private Lobby
Eine öffentliche Lobby ist für jeden Nutzer zugänglich und kann mit
jedem Spieler gespielt werden. Eine geschlossene und private Lobby ist
nur für jene Spieler zugänglich, welche von einem bereits in der Lobby
vorhandenen Spieler den Lobby-Key o.a. Lobby-ID erhalten haben. Beim
Eintreten in eine private Lobby wird nach diesem Key verlangt.
//Lobby Admin
Standardmäßig ist der Ersteller einer Lobby der Admin für die derzeitige
Sitzung. Verlässt dieser Nutzer die Lobby, wird durch ein Zufallsprinzip
ein neuer Admin ausgewählt. Das Spiel kann nur vom Lobby-Admin
geschlossen oder gestartet werden.
//Update Changelog
Es sind seit dem neuesten Update nur 2 Spieler notwendig, um eine Lobby
zu starten. Zusätzlich wurde die Interkation mit dem User durch
verbesserte Nachrichten und Übersetzungen optimiert.
Übung macht den Meister!
Du möchtest deine Skills noch ein wenig trainieren? Dann führe ein paar
Katas aus!
Katas //
<https://dd.countit.at/katas>
/
/
Dojo - virtueller Übungsraum
Löse die Dojo-Aufgaben und werde Programmier-Profi!
Zu den Dojos // <https://dd.countit.at/dojos>
/
/
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 |
Datenschutz­erklä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