Compare commits

11 Commits

Author SHA1 Message Date
8d02589ed1 added python bots 2026-04-07 00:18:12 +02:00
49156bcda4 improved 2026-04-02 20:51:21 +02:00
1e3c2b176d sami test 2026-04-02 20:00:21 +02:00
f004205069 before mutliplayer 2026-04-02 18:02:15 +02:00
fa42cec01c More aggressive, got to level 3 at least 2026-04-02 17:38:23 +02:00
39e943b25e Improved 2026-04-02 00:12:02 +02:00
f8454d8950 Improved logging 2026-04-01 23:13:10 +02:00
284a45ece1 added First bot with logging 2026-04-01 23:02:17 +02:00
e0ee552012 added ApiClient 2026-04-01 22:13:50 +02:00
e5a4b6a1c9 Added empty Avalonia App 2026-04-01 21:43:34 +02:00
github-classroom[bot]
16ff0a9497 Setting up GitHub Classroom Feedback 2026-03-15 21:45:29 +00:00
17 changed files with 5553 additions and 0 deletions

3
DDApp/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.idea
bin
obj

16
DDApp/DDApp.sln Normal file
View File

@@ -0,0 +1,16 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DDApplication", "DDApplication\DDApplication.csproj", "{6519A0C8-BC8F-490D-B429-C2460DCB272C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6519A0C8-BC8F-490D-B429-C2460DCB272C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6519A0C8-BC8F-490D-B429-C2460DCB272C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6519A0C8-BC8F-490D-B429-C2460DCB272C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6519A0C8-BC8F-490D-B429-C2460DCB272C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,10 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="DDApplication.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

View File

@@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace DDApplication;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}

View File

@@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
namespace DDApplication;
internal sealed class BotSessionLogger : IDisposable
{
private readonly object _gate = new();
private readonly DateTimeOffset _startedAt = DateTimeOffset.UtcNow;
private readonly Dictionary<string, int> _actionAttempts = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, int> _actionSuccess = new(StringComparer.OrdinalIgnoreCase);
private StreamWriter? _eventsWriter;
private bool _disposed;
public string SessionId { get; } = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
public string DirectoryPath { get; }
public string EventsPath { get; }
public string SummaryCsvPath { get; }
public string LatestSymlinkPath { get; }
public string LatestSummarySymlinkPath { get; }
public BotSessionLogger()
{
var baseDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
DirectoryPath = Path.Combine(baseDir, "DDApplication", "logs");
Directory.CreateDirectory(DirectoryPath);
var stamp = DateTimeOffset.UtcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture);
EventsPath = Path.Combine(DirectoryPath, $"bot_{stamp}_{SessionId}.events.csv");
SummaryCsvPath = Path.Combine(DirectoryPath, $"bot_{stamp}_{SessionId}.summary.csv");
LatestSymlinkPath = Path.Combine(DirectoryPath, "latest.log.csv");
LatestSummarySymlinkPath = Path.Combine(DirectoryPath, "latest-summary.log.csv");
_eventsWriter = new StreamWriter(EventsPath, append: false, Encoding.UTF8) { AutoFlush = true };
_eventsWriter.WriteLine("timestamp_utc,event,label,executed,hp,max_hp,kills,deaths,pos_x,pos_y,pressure,details");
UpdateLatestSymlink(LatestSymlinkPath, EventsPath);
}
public void LogDecision(
string label,
bool executed,
double hp,
double maxHp,
int kills,
int deaths,
int x,
int y,
bool pressure,
string details)
{
lock (_gate)
{
if (_disposed || _eventsWriter is null)
{
return;
}
Increment(_actionAttempts, label);
if (executed)
{
Increment(_actionSuccess, label);
}
_eventsWriter.WriteLine(string.Join(",",
Csv(DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture)),
Csv("decision"),
Csv(label),
Csv(executed ? "1" : "0"),
Csv(hp.ToString("0.00", CultureInfo.InvariantCulture)),
Csv(maxHp.ToString("0.00", CultureInfo.InvariantCulture)),
Csv(kills.ToString(CultureInfo.InvariantCulture)),
Csv(deaths.ToString(CultureInfo.InvariantCulture)),
Csv(x.ToString(CultureInfo.InvariantCulture)),
Csv(y.ToString(CultureInfo.InvariantCulture)),
Csv(pressure ? "1" : "0"),
Csv(details)));
}
}
public void LogTelemetry(
double hp,
double maxHp,
int kills,
int deaths,
double progress,
double remaining,
string level,
int x,
int y,
string details)
{
lock (_gate)
{
if (_disposed || _eventsWriter is null)
{
return;
}
_eventsWriter.WriteLine(string.Join(",",
Csv(DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture)),
Csv("telemetry"),
Csv(level),
Csv(""),
Csv(hp.ToString("0.00", CultureInfo.InvariantCulture)),
Csv(maxHp.ToString("0.00", CultureInfo.InvariantCulture)),
Csv(kills.ToString(CultureInfo.InvariantCulture)),
Csv(deaths.ToString(CultureInfo.InvariantCulture)),
Csv(x.ToString(CultureInfo.InvariantCulture)),
Csv(y.ToString(CultureInfo.InvariantCulture)),
Csv(""),
Csv($"progress={progress:0.00};remaining={remaining:0.00};{details}")));
}
}
public void LogEvent(string label, string details)
{
lock (_gate)
{
if (_disposed || _eventsWriter is null)
{
return;
}
_eventsWriter.WriteLine(string.Join(",",
Csv(DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture)),
Csv("event"),
Csv(label),
Csv(""),
Csv(""),
Csv(""),
Csv(""),
Csv(""),
Csv(""),
Csv(""),
Csv(""),
Csv(details)));
}
}
public void WriteSummaryAndDispose(double finalHp, double finalMaxHp, int finalKills, int finalDeaths)
{
lock (_gate)
{
if (_disposed)
{
return;
}
var elapsed = DateTimeOffset.UtcNow - _startedAt;
var minutes = Math.Max(elapsed.TotalMinutes, 0.01);
var kd = finalDeaths == 0 ? finalKills : (double)finalKills / finalDeaths;
var lines = new List<string>
{
$"session={SessionId}",
$"started_utc={_startedAt:O}",
$"ended_utc={DateTimeOffset.UtcNow:O}",
$"duration_minutes={minutes:0.00}",
$"kills={finalKills}",
$"deaths={finalDeaths}",
$"kd={kd:0.000}",
$"kills_per_min={(finalKills / minutes):0.000}",
$"deaths_per_min={(finalDeaths / minutes):0.000}",
$"final_hp={finalHp:0.00}/{finalMaxHp:0.00}",
"actions="
};
foreach (var attempt in _actionAttempts.OrderBy(x => x.Key))
{
_actionSuccess.TryGetValue(attempt.Key, out var success);
var rate = attempt.Value == 0 ? 0 : (double)success / attempt.Value;
lines.Add($" {attempt.Key}: attempts={attempt.Value}, success={success}, rate={rate:0.000}");
}
using (var csvWriter = new StreamWriter(SummaryCsvPath, append: false, Encoding.UTF8))
{
csvWriter.WriteLine("type,key,value");
csvWriter.WriteLine($"{Csv("metric")},{Csv("session")},{Csv(SessionId)}");
csvWriter.WriteLine($"{Csv("metric")},{Csv("started_utc")},{Csv(_startedAt.ToString("O", CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("metric")},{Csv("ended_utc")},{Csv(DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("metric")},{Csv("duration_minutes")},{Csv(minutes.ToString("0.00", CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("metric")},{Csv("kills")},{Csv(finalKills.ToString(CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("metric")},{Csv("deaths")},{Csv(finalDeaths.ToString(CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("metric")},{Csv("kd")},{Csv(kd.ToString("0.000", CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("metric")},{Csv("kills_per_min")},{Csv((finalKills / minutes).ToString("0.000", CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("metric")},{Csv("deaths_per_min")},{Csv((finalDeaths / minutes).ToString("0.000", CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("metric")},{Csv("final_hp")},{Csv($"{finalHp:0.00}/{finalMaxHp:0.00}")}");
foreach (var attempt in _actionAttempts.OrderBy(x => x.Key))
{
_actionSuccess.TryGetValue(attempt.Key, out var success);
var rate = attempt.Value == 0 ? 0 : (double)success / attempt.Value;
csvWriter.WriteLine($"{Csv("action")},{Csv(attempt.Key + ".attempts")},{Csv(attempt.Value.ToString(CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("action")},{Csv(attempt.Key + ".success")},{Csv(success.ToString(CultureInfo.InvariantCulture))}");
csvWriter.WriteLine($"{Csv("action")},{Csv(attempt.Key + ".rate")},{Csv(rate.ToString("0.000", CultureInfo.InvariantCulture))}");
}
}
UpdateLatestSymlink(LatestSummarySymlinkPath, SummaryCsvPath);
_eventsWriter?.Dispose();
_eventsWriter = null;
_disposed = true;
}
}
public void Dispose()
{
WriteSummaryAndDispose(0, 0, 0, 0);
}
private static void Increment(Dictionary<string, int> dict, string key)
{
dict.TryGetValue(key, out var current);
dict[key] = current + 1;
}
private static string Csv(string value)
{
var safe = value.Replace("\"", "\"\"");
return $"\"{safe}\"";
}
private static void UpdateLatestSymlink(string symlinkPath, string targetPath)
{
try
{
if (File.Exists(symlinkPath) || Directory.Exists(symlinkPath))
{
File.Delete(symlinkPath);
}
File.CreateSymbolicLink(symlinkPath, targetPath);
}
catch
{
// Best-effort only; logging should continue even if symlink creation fails.
}
}
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<UserSecretsId>c93b84d8-b4e9-4725-84e3-00a10f74073f</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.12" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.12" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.12" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.12">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="NSwag.ApiDescription.Client" Version="14.6.3" />
</ItemGroup>
<ItemGroup>
<OpenApiReference Include="DigitalDojoApi.json" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="1320" d:DesignHeight="860"
Width="1320" Height="860"
x:Class="DDApplication.MainWindow"
Title="Digital Dojo Bot Controller">
<Grid RowDefinitions="Auto,Auto,*,Auto" ColumnDefinitions="2*,2*,3*" Margin="12">
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Orientation="Horizontal" Spacing="8">
<Button x:Name="StartButton" Content="Start Bot" Width="120"/>
<Button x:Name="StopButton" Content="Stop Bot" Width="120" IsEnabled="False"/>
<CheckBox x:Name="MultiplayerCheckBox" Content="Multiplayer" VerticalAlignment="Center"/>
<TextBlock Text="Aggro:" VerticalAlignment="Center"/>
<ComboBox x:Name="AggressivenessProfileBox" Width="150" SelectedIndex="2">
<ComboBoxItem Content="Conservative"/>
<ComboBoxItem Content="Balanced"/>
<ComboBoxItem Content="Aggressive"/>
<ComboBoxItem Content="Extreme"/>
</ComboBox>
<TextBlock Text="Mode Profile:" VerticalAlignment="Center"/>
<ComboBox x:Name="ModeOptimizationProfileBox" Width="200" SelectedIndex="0">
<ComboBoxItem Content="Auto"/>
<ComboBoxItem Content="Singleplayer Optimized"/>
<ComboBoxItem Content="Multiplayer Optimized"/>
</ComboBox>
<TextBlock Text="API Key:" VerticalAlignment="Center"/>
<TextBox x:Name="ApiKeyBox" Width="360" Watermark="GUID player token"/>
<TextBlock x:Name="StatusText" VerticalAlignment="Center" Text="Idle"/>
</StackPanel>
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Margin="0,8,0,8" Padding="8" BorderThickness="1" CornerRadius="6">
<SelectableTextBlock x:Name="StatsText" TextWrapping="Wrap" Text="Waiting for game state..." Focusable="True"/>
</Border>
<Border Grid.Row="2" Grid.Column="0" Margin="0,0,8,0" Padding="8" BorderThickness="1" CornerRadius="6">
<Grid RowDefinitions="Auto,*" RowSpacing="6">
<TextBlock Text="Bot Knowledge" FontWeight="Bold"/>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<UniformGrid x:Name="KnowledgeGrid" Rows="21" Columns="21"/>
</ScrollViewer>
</Grid>
</Border>
<Border Grid.Row="2" Grid.Column="1" Margin="0,0,8,0" Padding="8" BorderThickness="1" CornerRadius="6">
<Grid RowDefinitions="Auto,Auto,*" RowSpacing="8">
<TextBlock Text="Decision + Cooldowns" FontWeight="Bold"/>
<TextBlock x:Name="DecisionText" Grid.Row="1" TextWrapping="Wrap" Text="No decision yet."/>
<TextBlock x:Name="CooldownText" Grid.Row="2" TextWrapping="Wrap"/>
</Grid>
</Border>
<Border Grid.Row="2" Grid.Column="2" Padding="8" BorderThickness="1" CornerRadius="6">
<Grid RowDefinitions="Auto,*" RowSpacing="6">
<TextBlock Text="Action Log" FontWeight="Bold"/>
<ListBox x:Name="LogList" Grid.Row="1"/>
</Grid>
</Border>
<TextBlock Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" Margin="0,8,0,0" Text="Legend: P=bot, N=scan-nearest, *=high threat, +=medium threat, .=low threat" />
</Grid>
</Window>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
using Avalonia;
using System;
using Microsoft.Extensions.Configuration;
namespace DDApplication;
internal class Program
{
public static IConfiguration Configuration { get; private set; } = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true)
.AddUserSecrets<Program>(optional: true)
.AddEnvironmentVariables()
.Build();
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static int Main(string[] args)
{
return BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
private static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

View File

@@ -0,0 +1,48 @@
# Digital Dojo Bot Controller
This Avalonia app runs an autonomous bot for the Digital Dojo singleplayer API and visualizes both current game state and inferred enemy knowledge.
## What it does
- Starts/uses your current game session (`/api/game/{key}/create`, `/status`)
- Runs a cooldown-aware combat loop (`move`, `hit`, `shoot`, `specialattack`, `radar`, `scan`, `peek`, `dash`)
- Tries to maximize score by prioritizing fast damage actions when enemies are nearby
- Shows live stats (kills/deaths, HP, level progress, remaining time)
- Shows a local knowledge map:
- `P` = estimated player position
- `N` = nearest enemy from latest scan
- `* + .` = inferred enemy threat intensity
- Shows cooldown timers and action reasoning/logs
- Writes per-session analytics logs for post-run analysis
## Setup
Set your API key (GUID) in one of these ways:
1. Environment variable: `APIKEY`
2. User secrets (`Program` already loads user secrets)
3. Paste it directly into the UI text box before clicking **Start Bot**
## Run
```bash
dotnet run --project /home/tikaiz/RiderProjects/digitalDojo/DDApp/DDApplication/DDApplication.csproj
```
## 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
- `*.summary.csv` contains machine-readable summary metrics and action rates
- `latest-summary.log.csv` is a symlink that auto-points to the newest `*.summary.csv`
- `latest.log` is a symlink that auto-points to the newest `*.events.csv` session log

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="DDApplication.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View 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 |
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

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

View File

@@ -0,0 +1,633 @@
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%2Fspieleinstieg> //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%2Fspieleinstieg&title=&summary=&source=> //E-Mail
<mailto:m.mustermann@countit.at?subject=Lese-
Tipp%3A%20Dojo%20Game%20%7C%20Wichtige%20Infos%20zum%20Spieleinstieg&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%20Wichtige%20Infos%20zum%20Spieleinstieg%22%0D%0ASchau%20ihn%20dir%20mal%20an%3A%20https%3A%2F%2Fkarriere.countit.at%2Fdojo-game%2Fspieleinstieg%3Fkaid%3Dsharemail%0D%0A%0D%0ABeste%20Gr%C3%BC%C3%9Fe>
Spieleinstieg
Dojo Game
//Dojo Game | Dein Einstieg ins Spiel
------------------------------------------------------------------------
In diesem Abschnitt findest du alle notwendigen Infos, um das Dojo Game
zu starten und erste Aktionen durchzuführen.
*Hinweis: *Hol dir deinen persönlichen Zugang <https://dd.countit.at/
register> bevor du startest bzw. logge dich ein <https://dd.countit.at/
login>. So erscheinst du später auch am Leaderboard!
Inhaltsverzeichnis
1. Das Spiel <#spiel>
2. Vorbereitung <#vorbereitung>
3. API-Schlüssel <#api>
4. Spiel starten & beenden <#starten>
5. Ritter steuern <#ritter>
//Das Spiel
Im Dojo Game geht es, einfach erklärt, um einen *Server*, auf welchem
ein Spiel für dich bereitgestellt wird. In diesem Spiel gibt es im
Singleplayer-Modus *einen Gegner, den sogenannten Dojo-Bot,* und deinen
Charakter. *Dein Ritter* hat dieselben Eigenschaften wie ein vom
Computer gesteuerter Ritter. Über bestimmte Befehle (diese werden über
URLs aufgerufen) kannst du deinen Spieler steuern und so programmieren,
dass dieser mit deinen Gegnern kämpft, vor ihnen wegläuft oder andere
strategische Aktionen ausführt. Ziel ist es, so viele Gegner wie möglich
zu eliminieren.
*Ein Spiel dauert maximal 15 Minuten* - danach wird deine Sitzung
geschlossen. Es gibt innerhalb dieser 15 Minuten unendlich viele Gegner.
Wird ein Bot getötet, wird dieser auf einer zufälligen anderen Stelle
der Map wiedergeboren. Bei Start eines neuen Spiels beginnst du immer
automatisch neu in Level 1.
//Vorbereitung
Um am Dojo Game teilzunehmen, benötigst du einen an das Internet
angeschlossenen Computer, einen *Digital Dojo Zugang* <https://
dd.countit.at/register>. Sobald du eingeloggt <https://dd.countit.at/
login> bist, findest du im Folgenden deinen *API-Schlüssel*, den du
schon bald brauchen wirst.
Die Logik deines Spielers kannst du in jeder beliebigen
Programmiersprache implementieren, da Web-Requests an eine API von fast
jeder verfügbaren Programmier- und Skriptsprache unterstützt werden.
Falls du Starthilfe benötigst und auf bereits erstellte Algorithmen
aufbauen möchtest, kannst du dich hier <https://dd.countit.at/dojo-game/
tipps> umsehen.
Die REST-API Befehle können auch mithilfe vom Fiddler <https://
www.telerik.com/fiddler> oder dem Postman <https://www.postman.com/
> gesendet werden. Diese Programme werden meistens zum Testen oder für
Prototypen verwendet und benötigen keinen Programm-Code zum Funktionieren.
//API-Schlüssel
Mithilfe deines Keys kannst du dich mit unseren Servern verbinden um
dein Spiel zu starten. Zudem werden damit deine gesendeten Befehle
identifiziert, damit Aktionen, welche du ausführst, auch wirklich nur
für dich gelten. Somit wird verhindert, dass du die Spiele anderer User
stören oder manipulieren kannst.
*Halte deinen Schlüssel geheim* - er könnte es anderen Nutzern
ermöglichen, Befehle auszuführen welche nicht in deinem Sinne sind!
//Los geht's
------------------------------------------------------------------------
In diesem Abschnitt erfährst du, wie du ein Spiel startest und beendest,
den Status des laufenden Spiels abfragen kannst, und wie sich dein
Ritter steuern lässt.
Wie starte ich ein Spiel? [POST]
Um ein Spiel zu starten, benötigst du deinen API-Schlüssel <#api>. Wurde
dieser Befehl erfolgreich ausgeführt, wird das Spiel automatisch
gestartet und du kannst direkt Aktionen zum Bewegen des Ritters ausführen.
Falls bereits ein Spiel mit deinem Charakter läuft, musst du dieses
zuerst beenden, bevor du ein neues erstellen kannst. Spiele werden
mithilfe eines POST-Requests erzeugt.
https://game-dd.countit.at/api/game/{key}/create
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/game/{key}/create
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#spielstart>
<http://countit-azure-test.westeurope.cloudapp.azure.com:1337/dojo-
game/postman#spieleinstieg>
Wie beende ich ein Spiel? [POST]
Ein Spiel wird beendet, wenn du mit deinem API-Key folgenden Befehl als
POST-Request ausführst:
https://game-dd.countit.at/api/game/{key}/close
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/game/{key}/close
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#spielende>
Status des laufenden Spiels abfragen [GET]
Um abfragen zu können, ob mit deinem API-Schlüssel bereits ein Spiel
läuft, kannst du den folgenden Befehl verwenden. Du erhältst als
Rückgabewert dasselbe wie beim Erstellen und Schließen eines Spiels
sowie eine Fehlermeldung, falls kein Spiel vorhanden ist.
https://game-dd.countit.at/api/game/{key}/status
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/game/{key}/status
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#status>
Beim Erstellen und Beenden eines Spiels werden jeweils dieselben Daten
zurückgeliefert:
→ Game-ID: Diese macht dein Spiel identifizierbar.
→ Running: Damit werden dein Spielstatus und der Name des geladenen
Levels sichtbar.
Der zurückgegebene String ist im JSON Format.
{"gameid":"d2144a67-733a-4e06-8a6b-9ce877b99753","running":true,"level":"The Default Level"}
text/x-csharp
xxxxxxxxxx
{"gameid":"d2144a67-733a-4e06-8a6b-9ce877b99753","running":true,"level":"The Default Level"}
➯ Was ist JSON? <https://dd.countit.at/dojo-game/tipps#technologie>
//Steuere deinen Ritter
------------------------------------------------------------------------
Ein Ritter hat die Möglichkeit auf einem zweidimensionalen Spielfeld
verschiedenste Aktionen auszuführen. Aktionen sowie ihre Wirkungen und
REST Aufrufe sind hier gelistet. Versuche deine Aktionen nicht zu
schnell hintereinander auszuführen, da du nach jeder Aktion eine gewisse
Zeit abwarten musst, bis du diese wieder einsetzen kannst. Dein Ritter
führt nur Befehle aus, welche du ihm gibst. Sendest du deinem Spieler
keine Aktionen, welche dieser ausführen soll, steht dieser durchgehend
auf derselben Position und ist verletzbar.
Move [POST]
Du kannst deinen Ritter alle 250 Millisekunden um ein Feld in eine der
vier Himmelsrichtungen bewegen.
Das Feld "Direction" sollte eine Zahl zwischen 0 und 3 enthalten:
→ 0 für Norden
→ 1 für Osten
→ 2 für Süden
→ 3 für Westen
https://game-dd.countit.at/api/player/{key}/move/{direction}
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/move/{direction}
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#move>
Dash [POST]
Dash lässt dich 5 Blöcke in eine von dir bestimmte Richtung bewegen.
Es hat außerdem einen besonderen Cooldown: Du kannst Dash schon 3
Sekunden nach der Aktivierung verwenden, aber dein Spieler wird zu
erschöpft sein und sich nur 3 Blöcke bewegen. Doch aufgepasst, wenn du
gegen eine Wand dasht nimmst du Schaden in Höhe der verbleibenden Dash-
Distanz. Nachdem du diese Aktion verwendet hast, musst du 5000
Millisekunden warten.
https://game-dd.countit.at/api/player/{key}/dash/{direction}
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/dash/{direction}
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#dash>
Hit [POST]
Diese Aktion wird benutzt, um mit deinem Ritter zu schlagen. Es kann
jeweils nur in eine Himmelsrichtung (0 für Norden, 1 für Osten, 2 für
Süden und 3 für Westen) geschlagen werden und dies nur einen Block weit.
Danach musst du 250 Millisekunden warten, bis du wieder angreifen
kannst. Ein normaler Schlag, welcher mit einem Abstand von einem Block
erzielt wurde, fügt dem Gegner weniger Schaden zu als wenn du auf
demselben Block wie ein Gegner stehst. Hierbei ist es egal, wie viele
Gegner auf einem Fleck stehen. Jeder bekommt gleich viel Schaden.
https://game-dd.countit.at/api/player/{key}/hit/{direction}
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/hit/{direction}
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#hit>
Shoot [POST]
Shoot benötigt, wie Peek, eine Direction (0 für Norden, 1 für Osten, 2
für Süden und 3 für Westen), also eine Richtung. Shoot verwendet diese
Richtung, um einen Schuss in diese Richtung abzugeben. Als Antwort kommt
zurück, ob diese Aktion ausgeführt wurde und ob ein Gegner getroffen
wurde. Die Kugel stoppt beim ersten Spieler. Nachdem du diese Aktion
verwendet hast, musst du 1000 Millisekunden warten.
https://game-dd.countit.at/api/player/{key}/shoot/{direction}
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/shoot/{direction}
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#shoot>
Radar [GET]
Mit dem Befehl "Radar" kannst du die Umgebung um deinen Spieler im
Umkreis von 5 Blöcken auf Feinde überprüfen. Das Feld wird aufgeteilt
und durchsucht. Danach erhältst du Informationen über die Anzahl der
Spieler in den 4 Himmelsrichtungen und den Block, auf dem du stehst.
Nachdem du diese Aktion verwendet hast, musst du 250 Millisekunden warten.
Radar <https://dd.countit.at/media/dojo-game/dojo-game_radar-
himmelsrichtungen.png>
https://game-dd.countit.at/api/player/{key}/radar
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/radar
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#radar>
Scan [GET]
Mit Scan kannst du den am nächsten stehenden Spieler im Umkreis von 3
Blöcken finden. Durch den Rückgabewert weißt du dann auch, was seine
relative Position zu dir ist. Nachdem du diese Aktion verwendet hast,
musst du 2000 Millisekunden warten.
https://game-dd.countit.at/api/player/{key}/scan
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/scan
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request? <https://dd.countit.at/dojo-game/postman#scan>
Peek [GET]
Peek benötigt, wie Move und Hit, eine Direction (0 für Norden, 1 für
Osten, 2 für Süden und 3 für Westen). Peek verwendet diese Richtung, um
zu überprüfen, ob sich in der angegebenen Richtung ein Spieler in deinem
Sichtfeld befindet. Peek gibt dir dann die Anzahl der Gegner in der
Sichtlinie und die Distanz zum nächsten stehenden Spieler in dieser
Sichtlinie zurück. Dies kannst du alle 1000 Millisekunden verwenden.
https://game-dd.countit.at/api/player/{key}/peek/{direction}
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/peek/{direction}
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#peek>
Stats [GET]
Mit Stats hast du die Möglichkeit dir deine Kills und Deaths des aktiven
Spiels anzusehen. Der Rückgabewert weicht vom Rückgabewert der anderen
Funktionen ab - enthalten sind Informationen zum derzeitigen Level,
allgemeine Stats sowie Informationen zu den Lebenspunkten eines Spielers.
*progress* - gibt an, wie weit du ein Level bereits abgeschlossen hast (%)
*remainingtime* - gibt an, wieviele Minuten noch übrig sind bevor das
Spiel gestoppt wird
*deathsleft* - gibt an, wie oft du noch sterben darfst, bevor das Spiel
gestoppt wird
https://game-dd.countit.at/api/player/{key}/stats
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/stats
Der Rückgabewert dieser Funktion wurde mit dem letzten Update verändert.
Hier ein Beispiel für den neuen Rückgabewert:
{"action":"stats","executed":true,"stats":
{"kills":1,"deaths":1},"level":
{"progress":6.6,"remainingtime":14.44,"deathsleft":999,"levelid":2,"name":"Bigger Map Level"},"health":{"currenthealth":10.0,"maxhealth":10.0}}
text/x-csharp
xxxxxxxxxx
{"action":"stats","executed":true,"stats":{"kills":1,"deaths":1},"level":{"progress":6.6,"remainingtime":14.44,"deathsleft":999,"levelid":2,"name":"Bigger Map Level"},"health":{"currenthealth":10.0,"maxhealth":10.0}}
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#stats>
Specialattack [POST]
Specialattack ist eine Aktion, welche du viel seltener als einen
normalen Schlag ("Hit") einsetzen kannst. Diese hat allerdings eine
bestimmte Fähigkeit und verursacht dadurch mehr Schaden an deinem
Gegner. Specialattack nimmt keine Direction als Parameter entgegen,
sondern führt den Angriff in allen Richtungen mit einem Wirkungskreis
von 3 Blöcken gleichzeitig aus. Du kannst die Specialattack alle 5000
Millisekunden ausführen.
https://game-dd.countit.at/api/player/{key}/specialattack
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/specialattack
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#specialattack>
Teleport [GET]
Mit Teleport kannst du dich auf ein beliebiges Feld teleportieren. Der
Trick dabei: Du musst das Feld relativ zu deiner Position mithilfe von x
und y Koordinaten angeben.
Cooldown: 20000 Millisekunden (20 Sekunden)
Achtung: Falls du in einer Wand landest bist du sofort tot. Wenn du
allerdings einen Gegner erwischst, wird dieser sofort getötet.
https://game-dd.countit.at/api/player/{key}/teleport/{x}/{y}
text/x-csharp
xxxxxxxxxx
https://game-dd.countit.at/api/player/{key}/teleport/{x}/{y}
➯ Problem? <https://dd.countit.at/dojo-game/tipps#fehler>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#teleport>
Rückgabewert
Jede Aktion gibt im Prinzip immer einen gleich strukturierten JSON-
String zurück. Jedoch können sich die Datentypen der einzelnen Felder je
nach Aktion unterscheiden.
Hier zwei Beispiele, um dies zu verdeutlichen:
Die Move-Aktion, ohne speziellen Rückgabewert:
{"action":"move","move":"true","executed":"true"}
text/x-csharp
xxxxxxxxxx
{"action":"move","move":"true","executed":"true"}
Die Radar-Aktion, mit einem Integer als Rückgabewert:
{"action":"radar","radar":2,"executed":"true"}
text/x-csharp
xxxxxxxxxx
{"action":"radar","radar":2,"executed":"true"}
➯ Was ist JSON? <https://dd.countit.at/dojo-game/tipps#technologie>
➯ Postman-Request <https://dd.countit.at/dojo-game/postman#rueckgabewerte>
Bereit für die ultimative Programmier-Challenge an deiner Schule?
Alle Infos dazu findest du hier //
<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>
/
/
Besiege unseren Bot
Egal ob als Einzelspieler oder im Mulitplayer-Modus: Stelle dich dem
Dojo-Bot und beweise, dass du auf das Leaderboard gehörst!
Ich bin bereit! // <https://dd.countit.at/dojo-game>
/
/
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

453
main-aggro.py Normal file
View File

@@ -0,0 +1,453 @@
"""
Digital Dojo Bot v3 - Radar-based detection
============================================
Problem: scan returns (0,0) when no enemy nearby = useless for navigation.
Fix: Use RADAR (0.25s cooldown) to get actual enemy positions, log the raw
structure so we can understand the API, and chase accordingly.
Usage:
pip install httpx
python dojo_bot.py --token YOUR_API_KEY
"""
import asyncio, argparse, logging, random, sys, time
import httpx
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%H:%M:%S")
log = logging.getLogger("DojoBot")
BASE = "https://game-dd.countit.at"
# direction: 0=NORTH(-Y), 1=EAST(+X), 2=SOUTH(+Y), 3=WEST(-X)
NAMES = {0:"N", 1:"E", 2:"S", 3:"W"}
CD = {
"move": 0.27,
"hit": 0.27,
"radar": 0.27,
"special": 5.1,
"scan": 2.1,
"dash": 3.1,
"shoot": 1.1,
"peek": 1.1,
}
def dir_toward(dx, dy):
if abs(dx) >= abs(dy): return 1 if dx > 0 else 3
return 2 if dy > 0 else 0
class Bot:
def __init__(self, token, multiplayer=False):
self.token = token
self.multiplayer = multiplayer
self.http = httpx.AsyncClient(timeout=8.0)
self._last = {k: 0.0 for k in CD}
self.health = 10.0
self.kills = 0
self.deaths = 0
self.target_dx = None
self.target_dy = None
self.target_last_seen = 0.0
self.target_ttl = 1.8
self.last_known_dir = random.randint(0, 3)
self.radar_seen = False # have we printed raw radar yet?
self.explore_dir = random.randint(0, 3)
self.explore_steps = 0
def ready(self, a): return (time.monotonic() - self._last[a]) >= CD[a]
def mark(self, a): self._last[a] = time.monotonic()
def _set_target(self, dx, dy):
self.target_dx, self.target_dy = dx, dy
self.target_last_seen = time.monotonic()
self.last_known_dir = dir_toward(dx, dy)
def _expire_target(self):
if self.has_target and (time.monotonic() - self.target_last_seen) > self.target_ttl:
self.target_dx, self.target_dy = None, None
@property
def has_target(self): return self.target_dx is not None
@property
def dist(self):
if not self.has_target: return 999
return abs(self.target_dx) + abs(self.target_dy)
def _ci_get(self, data, *keys, default=None):
"""Case-insensitive dict getter for unstable API field casing."""
if not isinstance(data, dict):
return default
for key in keys:
if key in data:
return data[key]
lowered = {str(k).lower(): v for k, v in data.items()}
for key in keys:
hit = lowered.get(str(key).lower())
if hit is not None:
return hit
return default
@staticmethod
def _as_int(value, default=0):
try:
if value is None:
return default
return int(value)
except (TypeError, ValueError):
return default
@staticmethod
def _as_bool(value):
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return value != 0
if isinstance(value, str):
return value.strip().lower() in {"true", "1", "yes"}
return False
async def GET(self, path):
for attempt in range(2):
try:
r = await self.http.get(f"{BASE}{path}")
if r.status_code == 200:
try:
return r.json()
except ValueError:
log.warning(f"GET {path}: invalid JSON body")
return None
if r.status_code >= 500 and attempt == 0:
await asyncio.sleep(0.15)
continue
hint = (r.text or "")[:180].replace("\n", " ")
log.warning(f"GET {path}: status={r.status_code} body={hint}")
return None
except (httpx.TimeoutException, httpx.NetworkError, httpx.RemoteProtocolError) as e:
if attempt == 0:
await asyncio.sleep(0.15)
continue
log.warning(f"GET {path}: {e}")
return None
except Exception as e:
log.warning(f"GET {path}: {e}")
return None
return None
async def POST(self, path):
for attempt in range(2):
try:
r = await self.http.post(f"{BASE}{path}")
if r.status_code == 200:
try:
return r.json()
except ValueError:
log.warning(f"POST {path}: invalid JSON body")
return None
if r.status_code >= 500 and attempt == 0:
await asyncio.sleep(0.15)
continue
hint = (r.text or "")[:180].replace("\n", " ")
log.warning(f"POST {path}: status={r.status_code} body={hint}")
return None
except (httpx.TimeoutException, httpx.NetworkError, httpx.RemoteProtocolError) as e:
if attempt == 0:
await asyncio.sleep(0.15)
continue
log.warning(f"POST {path}: {e}")
return None
except Exception as e:
log.warning(f"POST {path}: {e}")
return None
return None
# ── Game ──────────────────────────────────────────────────────────────────
async def start(self):
if not self.multiplayer:
r = await self.POST(f"/api/game/{self.token}/create")
log.info(f"Game: {r}")
async def stop(self):
if not self.multiplayer: await self.POST(f"/api/game/{self.token}/close")
await self.http.aclose()
log.info("Done.")
# ── Sense ─────────────────────────────────────────────────────────────────
async def radar(self):
if not self.ready("radar"): return
res = await self.GET(f"/api/player/{self.token}/radar")
if not res: return
self.mark("radar")
raw = self._ci_get(res, "RadarResults", "radarResults", default={}) or {}
# Log full radar structure once to understand server payload shape
if not self.radar_seen:
log.info(f"=== RADAR STRUCTURE ===\n{res}\n=======================")
self.radar_seen = True
nearest = self._parse_radar(raw)
if nearest is not None:
self._set_target(*nearest)
def _parse_radar(self, raw):
"""Extract nearest enemy hint from RadarResults."""
if not raw:
return None
# Documented format: directional counts in radar sectors.
if isinstance(raw, dict) and all(isinstance(v, (int, float, str)) for v in raw.values()):
buckets = {
0: ["0", "n", "north", "up"],
1: ["1", "e", "east", "right"],
2: ["2", "s", "south", "down"],
3: ["3", "w", "west", "left"],
}
counts = {}
lowered = {str(k).lower(): self._as_int(v, 0) for k, v in raw.items()}
for direction, names in buckets.items():
counts[direction] = max((lowered.get(n, 0) for n in names), default=0)
best_dir = max(counts, key=counts.get)
if counts.get(best_dir, 0) > 0:
return [(0, -2), (2, 0), (0, 2), (-2, 0)][best_dir]
return None
# Fallback: try coordinate-like structures if API payload differs.
candidates = []
if isinstance(raw, dict):
values = list(raw.values())
elif isinstance(raw, list):
values = raw
else:
values = []
for v in values:
if not isinstance(v, dict):
continue
x = self._ci_get(v, "x", "posX", "X", "PosX")
y = self._ci_get(v, "y", "posY", "Y", "PosY")
if x is None or y is None:
continue
try:
xi, yi = int(x), int(y)
except (TypeError, ValueError):
continue
candidates.append((xi, yi))
if not candidates:
return None
return min(candidates, key=lambda c: abs(c[0]) + abs(c[1]))
async def scan(self):
"""Backup: scan returns relative delta to nearest player."""
if not self.ready("scan"): return
res = await self.GET(f"/api/player/{self.token}/scan")
if not res: return
self.mark("scan")
diff = self._ci_get(res, "DifferenceToNearestPlayer", default={}) or {}
x = self._as_int(self._ci_get(diff, "X", "x"), 0)
y = self._as_int(self._ci_get(diff, "Y", "y"), 0)
if x != 0 or y != 0:
self._set_target(x, y)
log.info(f"Scan: enemy at delta=({x:+d},{y:+d})")
async def peek_all(self):
"""Peek all 4 directions to spot enemies."""
for d in range(4):
if not self.ready("peek"): break
res = await self.GET(f"/api/player/{self.token}/peek/{d}")
if not res:
continue
self.mark("peek")
seen = self._as_int(self._ci_get(res, "PlayersInSight", default=0), 0)
if seen > 0:
dist = max(1, self._as_int(self._ci_get(res, "SightedPlayerDistance", default=1), 1))
dx, dy = [(0, -dist), (dist, 0), (0, dist), (-dist, 0)][d]
self._set_target(dx, dy)
log.info(f"Peek {NAMES[d]}: enemy dist={dist}")
return True
return False
async def stats(self):
res = await self.GET(f"/api/player/{self.token}/stats")
if not res: return
health = self._ci_get(res, "Health", default={}) or {}
self.health = float(self._ci_get(health, "Currenthealth", "CurrentHealth", "currenthealth", default=self.health))
s = self._ci_get(res, "Stats", default={}) or {}
k = self._as_int(self._ci_get(s, "Kills", "kills", default=self.kills), self.kills)
if k > self.kills: log.info(f"KILL! Total: {k}")
self.kills = k
self.deaths = self._as_int(self._ci_get(s, "Deaths", "deaths", default=self.deaths), self.deaths)
# ── Act ───────────────────────────────────────────────────────────────────
async def move(self, d):
if not self.ready("move"): return False
res = await self.POST(f"/api/player/{self.token}/move/{d}")
if not res: return False
self.mark("move")
moved = self._as_bool(self._ci_get(res, "Move", default=False))
if moved and self.has_target:
# Update relative target position as we move
deltas = [(0,1),(-1,0),(0,-1),(1,0)]
self.target_dx = (self.target_dx or 0) + deltas[d][0]
self.target_dy = (self.target_dy or 0) + deltas[d][1]
return moved
async def hit(self, d):
if not self.ready("hit"): return False
res = await self.POST(f"/api/player/{self.token}/hit/{d}")
if not res: return False
self.mark("hit")
dmg = self._as_int(self._ci_get(res, "Hit", default=0), 0)
crit = self._as_int(self._ci_get(res, "Critical", default=0), 0)
if dmg or crit:
log.info(f"Hit {NAMES[d]}! dmg={dmg} crit={crit}")
return self._as_bool(self._ci_get(res, "Executed", default=False))
async def special(self):
if not self.ready("special"): return False
res = await self.POST(f"/api/player/{self.token}/specialattack")
if not res: return False
self.mark("special")
h = self._as_int(self._ci_get(res, "Hitcount", "HitCount", default=0), 0)
if h: log.info(f"Special hit {h}!")
return self._as_bool(self._ci_get(res, "Executed", default=False))
async def dash(self, d):
if not self.ready("dash"): return False
res = await self.POST(f"/api/player/{self.token}/dash/{d}")
if not res: return False
self.mark("dash")
executed = self._as_bool(self._ci_get(res, "Executed", default=False))
if executed and self.has_target:
blocks = max(0, self._as_int(self._ci_get(res, "BlocksDashed", default=0), 0))
deltas = [(0, blocks), (-blocks, 0), (0, -blocks), (blocks, 0)]
self.target_dx = (self.target_dx or 0) + deltas[d][0]
self.target_dy = (self.target_dy or 0) + deltas[d][1]
return executed
async def shoot(self, d):
if not self.ready("shoot"): return False
res = await self.POST(f"/api/player/{self.token}/shoot/{d}")
if not res: return False
self.mark("shoot")
if self._as_bool(self._ci_get(res, "HitSomeone", default=False)):
log.info("Shoot HIT!")
return self._as_bool(self._ci_get(res, "Executed", default=False))
# ── Strategy ──────────────────────────────────────────────────────────────
async def engage(self):
d = dir_toward(self.target_dx, self.target_dy)
dist = self.dist
if dist <= 1:
await self.special()
await self.hit(d)
await self.hit((d + 1) % 4)
await self.hit((d + 3) % 4)
await self.move(d)
return
if dist <= 3:
await self.special()
await self.shoot(d)
await self.hit(d)
if self.ready("dash"):
await self.dash(d)
else:
await self.move(d)
return
await self.shoot(d)
if self.ready("dash"):
await self.dash(d)
await self.move(d)
async def explore(self):
"""Aggressive sweep while probing and pushing forward."""
self.explore_steps += 1
if self.explore_steps >= 3:
self.explore_dir = (self.explore_dir + 1) % 4
self.explore_steps = 0
# Bias exploration toward the most recent enemy direction.
preferred = self.last_known_dir if random.random() < 0.7 else self.explore_dir
moved = await self.move(preferred)
if not moved:
self.explore_dir = (self.explore_dir + 1) % 4
self.explore_steps = 0
await self.move(self.explore_dir)
else:
self.explore_dir = preferred
await self.move(preferred)
await self.shoot(preferred)
await self.special()
# ── Main ──────────────────────────────────────────────────────────────────
async def run(self):
await self.start()
log.info("Bot v3 running (aggressive mode). Ctrl+C to stop.")
tick = 0
try:
while True:
tick += 1
await self.radar() # primary detection (0.27s cooldown)
await self.scan() # backup detection (2s cooldown)
self._expire_target()
# Reacquire quickly when no target is known.
if not self.has_target and tick % 6 == 0:
await self.peek_all()
if tick % 8 == 0:
await self.stats()
t = f"delta=({self.target_dx:+d},{self.target_dy:+d}) d={self.dist}" if self.has_target else "searching"
log.info(f"HP={self.health:.0f} K={self.kills} D={self.deaths} {t}")
if self.has_target:
await self.engage()
else:
await self.explore()
await asyncio.sleep(0.1)
except (KeyboardInterrupt, asyncio.CancelledError):
pass
finally:
await self.stop()
async def main():
p = argparse.ArgumentParser()
p.add_argument("--token", required=True)
p.add_argument("--multiplayer", action="store_true")
args = p.parse_args()
await Bot(args.token, args.multiplayer).run()
if __name__ == "__main__":
try: asyncio.run(main())
except KeyboardInterrupt: sys.exit(0)

453
main-less-aggro.py Normal file
View File

@@ -0,0 +1,453 @@
"""
Digital Dojo Bot v3 - Radar-based detection
============================================
Problem: scan returns (0,0) when no enemy nearby = useless for navigation.
Fix: Use RADAR (0.25s cooldown) to get actual enemy positions, log the raw
structure so we can understand the API, and chase accordingly.
Usage:
pip install httpx
python dojo_bot.py --token YOUR_API_KEY
"""
import asyncio, argparse, logging, random, sys, time
import httpx
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%H:%M:%S")
log = logging.getLogger("DojoBot")
BASE = "https://game-dd.countit.at"
# direction: 0=NORTH(-Y), 1=EAST(+X), 2=SOUTH(+Y), 3=WEST(-X)
NAMES = {0:"N", 1:"E", 2:"S", 3:"W"}
CD = {
"move": 0.27,
"hit": 0.27,
"radar": 0.27,
"special": 5.1,
"scan": 2.1,
"dash": 3.1,
"shoot": 1.1,
"peek": 1.1,
}
def dir_toward(dx, dy):
if abs(dx) >= abs(dy): return 1 if dx > 0 else 3
return 2 if dy > 0 else 0
class Bot:
def __init__(self, token, multiplayer=False):
self.token = token
self.multiplayer = multiplayer
self.http = httpx.AsyncClient(timeout=8.0)
self._last = {k: 0.0 for k in CD}
self.health = 10.0
self.kills = 0
self.deaths = 0
self.target_dx = None
self.target_dy = None
self.target_last_seen = 0.0
self.target_ttl = 1.8
self.last_known_dir = random.randint(0, 3)
self.radar_seen = False # have we printed raw radar yet?
self.explore_dir = random.randint(0, 3)
self.explore_steps = 0
def ready(self, a): return (time.monotonic() - self._last[a]) >= CD[a]
def mark(self, a): self._last[a] = time.monotonic()
def _set_target(self, dx, dy):
self.target_dx, self.target_dy = dx, dy
self.target_last_seen = time.monotonic()
self.last_known_dir = dir_toward(dx, dy)
def _expire_target(self):
if self.has_target and (time.monotonic() - self.target_last_seen) > self.target_ttl:
self.target_dx, self.target_dy = None, None
@property
def has_target(self): return self.target_dx is not None
@property
def dist(self):
if not self.has_target: return 999
return abs(self.target_dx) + abs(self.target_dy)
def _ci_get(self, data, *keys, default=None):
"""Case-insensitive dict getter for unstable API field casing."""
if not isinstance(data, dict):
return default
for key in keys:
if key in data:
return data[key]
lowered = {str(k).lower(): v for k, v in data.items()}
for key in keys:
hit = lowered.get(str(key).lower())
if hit is not None:
return hit
return default
@staticmethod
def _as_int(value, default=0):
try:
if value is None:
return default
return int(value)
except (TypeError, ValueError):
return default
@staticmethod
def _as_bool(value):
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
return value != 0
if isinstance(value, str):
return value.strip().lower() in {"true", "1", "yes"}
return False
async def GET(self, path):
for attempt in range(2):
try:
r = await self.http.get(f"{BASE}{path}")
if r.status_code == 200:
try:
return r.json()
except ValueError:
log.warning(f"GET {path}: invalid JSON body")
return None
if r.status_code >= 500 and attempt == 0:
await asyncio.sleep(0.15)
continue
hint = (r.text or "")[:180].replace("\n", " ")
log.warning(f"GET {path}: status={r.status_code} body={hint}")
return None
except (httpx.TimeoutException, httpx.NetworkError, httpx.RemoteProtocolError) as e:
if attempt == 0:
await asyncio.sleep(0.15)
continue
log.warning(f"GET {path}: {e}")
return None
except Exception as e:
log.warning(f"GET {path}: {e}")
return None
return None
async def POST(self, path):
for attempt in range(2):
try:
r = await self.http.post(f"{BASE}{path}")
if r.status_code == 200:
try:
return r.json()
except ValueError:
log.warning(f"POST {path}: invalid JSON body")
return None
if r.status_code >= 500 and attempt == 0:
await asyncio.sleep(0.15)
continue
hint = (r.text or "")[:180].replace("\n", " ")
log.warning(f"POST {path}: status={r.status_code} body={hint}")
return None
except (httpx.TimeoutException, httpx.NetworkError, httpx.RemoteProtocolError) as e:
if attempt == 0:
await asyncio.sleep(0.15)
continue
log.warning(f"POST {path}: {e}")
return None
except Exception as e:
log.warning(f"POST {path}: {e}")
return None
return None
# ── Game ──────────────────────────────────────────────────────────────────
async def start(self):
if not self.multiplayer:
r = await self.POST(f"/api/game/{self.token}/create")
log.info(f"Game: {r}")
async def stop(self):
if not self.multiplayer: await self.POST(f"/api/game/{self.token}/close")
await self.http.aclose()
log.info("Done.")
# ── Sense ─────────────────────────────────────────────────────────────────
async def radar(self):
if not self.ready("radar"): return
res = await self.GET(f"/api/player/{self.token}/radar")
if not res: return
self.mark("radar")
raw = self._ci_get(res, "RadarResults", "radarResults", default={}) or {}
# Log full radar structure once to understand server payload shape
if not self.radar_seen:
log.info(f"=== RADAR STRUCTURE ===\n{res}\n=======================")
self.radar_seen = True
nearest = self._parse_radar(raw)
if nearest is not None:
self._set_target(*nearest)
def _parse_radar(self, raw):
"""Extract nearest enemy hint from RadarResults."""
if not raw:
return None
# Documented format: directional counts in radar sectors.
if isinstance(raw, dict) and all(isinstance(v, (int, float, str)) for v in raw.values()):
buckets = {
0: ["0", "n", "north", "up"],
1: ["1", "e", "east", "right"],
2: ["2", "s", "south", "down"],
3: ["3", "w", "west", "left"],
}
counts = {}
lowered = {str(k).lower(): self._as_int(v, 0) for k, v in raw.items()}
for direction, names in buckets.items():
counts[direction] = max((lowered.get(n, 0) for n in names), default=0)
best_dir = max(counts, key=counts.get)
if counts.get(best_dir, 0) > 0:
return [(0, -2), (2, 0), (0, 2), (-2, 0)][best_dir]
return None
# Fallback: try coordinate-like structures if API payload differs.
candidates = []
if isinstance(raw, dict):
values = list(raw.values())
elif isinstance(raw, list):
values = raw
else:
values = []
for v in values:
if not isinstance(v, dict):
continue
x = self._ci_get(v, "x", "posX", "X", "PosX")
y = self._ci_get(v, "y", "posY", "Y", "PosY")
if x is None or y is None:
continue
try:
xi, yi = int(x), int(y)
except (TypeError, ValueError):
continue
candidates.append((xi, yi))
if not candidates:
return None
return min(candidates, key=lambda c: abs(c[0]) + abs(c[1]))
async def scan(self):
"""Backup: scan returns relative delta to nearest player."""
if not self.ready("scan"): return
res = await self.GET(f"/api/player/{self.token}/scan")
if not res: return
self.mark("scan")
diff = self._ci_get(res, "DifferenceToNearestPlayer", default={}) or {}
x = self._as_int(self._ci_get(diff, "X", "x"), 0)
y = self._as_int(self._ci_get(diff, "Y", "y"), 0)
if x != 0 or y != 0:
self._set_target(x, y)
log.info(f"Scan: enemy at delta=({x:+d},{y:+d})")
async def peek_all(self):
"""Peek all 4 directions to spot enemies."""
for d in range(4):
if not self.ready("peek"): break
res = await self.GET(f"/api/player/{self.token}/peek/{d}")
if not res:
continue
self.mark("peek")
seen = self._as_int(self._ci_get(res, "PlayersInSight", default=0), 0)
if seen > 0:
dist = max(1, self._as_int(self._ci_get(res, "SightedPlayerDistance", default=1), 1))
dx, dy = [(0, -dist), (dist, 0), (0, dist), (-dist, 0)][d]
self._set_target(dx, dy)
log.info(f"Peek {NAMES[d]}: enemy dist={dist}")
return True
return False
async def stats(self):
res = await self.GET(f"/api/player/{self.token}/stats")
if not res: return
health = self._ci_get(res, "Health", default={}) or {}
self.health = float(self._ci_get(health, "Currenthealth", "CurrentHealth", "currenthealth", default=self.health))
s = self._ci_get(res, "Stats", default={}) or {}
k = self._as_int(self._ci_get(s, "Kills", "kills", default=self.kills), self.kills)
if k > self.kills: log.info(f"KILL! Total: {k}")
self.kills = k
self.deaths = self._as_int(self._ci_get(s, "Deaths", "deaths", default=self.deaths), self.deaths)
# ── Act ───────────────────────────────────────────────────────────────────
async def move(self, d):
if not self.ready("move"): return False
res = await self.POST(f"/api/player/{self.token}/move/{d}")
if not res: return False
self.mark("move")
moved = self._as_bool(self._ci_get(res, "Move", default=False))
if moved and self.has_target:
# Update relative target position as we move
deltas = [(0,1),(-1,0),(0,-1),(1,0)]
self.target_dx = (self.target_dx or 0) + deltas[d][0]
self.target_dy = (self.target_dy or 0) + deltas[d][1]
return moved
async def hit(self, d):
if not self.ready("hit"): return False
res = await self.POST(f"/api/player/{self.token}/hit/{d}")
if not res: return False
self.mark("hit")
dmg = self._as_int(self._ci_get(res, "Hit", default=0), 0)
crit = self._as_int(self._ci_get(res, "Critical", default=0), 0)
if dmg or crit:
log.info(f"Hit {NAMES[d]}! dmg={dmg} crit={crit}")
return self._as_bool(self._ci_get(res, "Executed", default=False))
async def special(self):
if not self.ready("special"): return False
res = await self.POST(f"/api/player/{self.token}/specialattack")
if not res: return False
self.mark("special")
h = self._as_int(self._ci_get(res, "Hitcount", "HitCount", default=0), 0)
if h: log.info(f"Special hit {h}!")
return self._as_bool(self._ci_get(res, "Executed", default=False))
async def dash(self, d):
if not self.ready("dash"): return False
res = await self.POST(f"/api/player/{self.token}/dash/{d}")
if not res: return False
self.mark("dash")
executed = self._as_bool(self._ci_get(res, "Executed", default=False))
if executed and self.has_target:
blocks = max(0, self._as_int(self._ci_get(res, "BlocksDashed", default=0), 0))
deltas = [(0, blocks), (-blocks, 0), (0, -blocks), (blocks, 0)]
self.target_dx = (self.target_dx or 0) + deltas[d][0]
self.target_dy = (self.target_dy or 0) + deltas[d][1]
return executed
async def shoot(self, d):
if not self.ready("shoot"): return False
res = await self.POST(f"/api/player/{self.token}/shoot/{d}")
if not res: return False
self.mark("shoot")
if self._as_bool(self._ci_get(res, "HitSomeone", default=False)):
log.info("Shoot HIT!")
return self._as_bool(self._ci_get(res, "Executed", default=False))
# ── Strategy ──────────────────────────────────────────────────────────────
async def engage(self):
d = dir_toward(self.target_dx, self.target_dy)
dist = self.dist
if dist <= 1:
await self.special()
await self.hit(d)
await self.hit((d + 1) % 4)
await self.hit((d + 3) % 4)
await self.move(d)
return
if dist <= 3:
await self.special()
await self.shoot(d)
await self.hit(d)
if self.ready("dash"):
await self.dash(d)
else:
await self.move(d)
return
await self.shoot(d)
if self.ready("dash"):
await self.dash(d)
await self.move(d)
async def explore(self):
"""Aggressive sweep while probing and pushing forward."""
self.explore_steps += 1
if self.explore_steps >= 3:
self.explore_dir = (self.explore_dir + 1) % 4
self.explore_steps = 0
# Bias exploration toward the most recent enemy direction.
preferred = self.last_known_dir if random.random() < 0.7 else self.explore_dir
moved = await self.move(preferred)
if not moved:
self.explore_dir = (self.explore_dir + 1) % 4
self.explore_steps = 0
await self.move(self.explore_dir)
else:
self.explore_dir = preferred
await self.move(preferred)
await self.shoot(preferred)
await self.special()
# ── Main ──────────────────────────────────────────────────────────────────
async def run(self):
await self.start()
log.info("Bot v3 running (aggressive mode). Ctrl+C to stop.")
tick = 0
try:
while True:
tick += 1
await self.radar() # primary detection (0.27s cooldown)
await self.scan() # backup detection (2s cooldown)
self._expire_target()
# Reacquire quickly when no target is known.
if not self.has_target and tick % 6 == 0:
await self.peek_all()
if tick % 8 == 0:
await self.stats()
t = f"delta=({self.target_dx:+d},{self.target_dy:+d}) d={self.dist}" if self.has_target else "searching"
log.info(f"HP={self.health:.0f} K={self.kills} D={self.deaths} {t}")
if self.has_target:
await self.engage()
else:
await self.explore()
await asyncio.sleep(0.1)
except (KeyboardInterrupt, asyncio.CancelledError):
pass
finally:
await self.stop()
async def main():
p = argparse.ArgumentParser()
p.add_argument("--token", required=True)
p.add_argument("--multiplayer", action="store_true")
args = p.parse_args()
await Bot(args.token, args.multiplayer).run()
if __name__ == "__main__":
try: asyncio.run(main())
except KeyboardInterrupt: sys.exit(0)