Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d02589ed1 | |||
| 49156bcda4 | |||
| 1e3c2b176d | |||
| f004205069 | |||
| fa42cec01c | |||
| 39e943b25e | |||
| f8454d8950 | |||
| 284a45ece1 | |||
| e0ee552012 | |||
| e5a4b6a1c9 | |||
|
|
16ff0a9497 |
3
DDApp/.gitignore
vendored
Normal file
3
DDApp/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.idea
|
||||
bin
|
||||
obj
|
||||
16
DDApp/DDApp.sln
Normal file
16
DDApp/DDApp.sln
Normal 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
|
||||
10
DDApp/DDApplication/App.axaml
Normal file
10
DDApp/DDApplication/App.axaml
Normal 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>
|
||||
23
DDApp/DDApplication/App.axaml.cs
Normal file
23
DDApp/DDApplication/App.axaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
250
DDApp/DDApplication/BotSessionLogger.cs
Normal file
250
DDApp/DDApplication/BotSessionLogger.cs
Normal 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
30
DDApp/DDApplication/DDApplication.csproj
Normal file
30
DDApp/DDApplication/DDApplication.csproj
Normal 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>
|
||||
1253
DDApp/DDApplication/DigitalDojoApi.json
Normal file
1253
DDApp/DDApplication/DigitalDojoApi.json
Normal file
File diff suppressed because it is too large
Load Diff
62
DDApp/DDApplication/MainWindow.axaml
Normal file
62
DDApp/DDApplication/MainWindow.axaml
Normal 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>
|
||||
1645
DDApp/DDApplication/MainWindow.axaml.cs
Normal file
1645
DDApp/DDApplication/MainWindow.axaml.cs
Normal file
File diff suppressed because it is too large
Load Diff
30
DDApp/DDApplication/Program.cs
Normal file
30
DDApp/DDApplication/Program.cs
Normal 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();
|
||||
}
|
||||
48
DDApp/DDApplication/README.md
Normal file
48
DDApp/DDApplication/README.md
Normal 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
|
||||
|
||||
|
||||
|
||||
18
DDApp/DDApplication/app.manifest
Normal file
18
DDApp/DDApplication/app.manifest
Normal 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>
|
||||
338
DDApp/DDApplication/levelübersicht.txt
Normal file
338
DDApp/DDApplication/levelübersicht.txt
Normal file
@@ -0,0 +1,338 @@
|
||||
Digital Dojo <https://dd.countit.at/> Menü //
|
||||
|
||||
* //Coden lernen<https://dd.countit.at/programmieren-lernen>
|
||||
o //Programmier-Roadmap <https://dd.countit.at/programmieren-lernen>
|
||||
o //Programmierschule <https://dd.countit.at/programmierschule>
|
||||
o //Übungsräume <https://dd.countit.at/dojos>
|
||||
o //Katas <https://dd.countit.at/katas>
|
||||
o //Tutorials <https://dd.countit.at/tutorials>
|
||||
* //Dojo Game<https://dd.countit.at/dojo-game/multiplayer-modus>
|
||||
o //Besiege unseren Bot <https://dd.countit.at/dojo-game>
|
||||
o //Spieleinstieg <https://dd.countit.at/dojo-game/spieleinstieg>
|
||||
o //Levels & Bots <https://dd.countit.at/dojo-game/levels>
|
||||
o //Leaderboard <https://dd.countit.at/dojo-game/leaderboard>
|
||||
o //Multiplayer Modus <https://dd.countit.at/dojo-game/
|
||||
multiplayer-modus>
|
||||
o //Tipps <https://dd.countit.at/dojo-game/tipps>
|
||||
* //Programmier-Challenge <https://dd.countit.at/programmierwettbewerb>
|
||||
* //Bewerben <https://dd.countit.at/bewerben>
|
||||
* //Feedback <https://dd.countit.at/feedback>
|
||||
* // <#>
|
||||
Ausloggen
|
||||
|
||||
/
|
||||
|
||||
/
|
||||
/
|
||||
|
||||
/
|
||||
|
||||
//Teilen //Facebook <https://www.facebook.com/sharer/sharer.php?
|
||||
u=https%3A%2F%2Fdd.countit.at%2Fdojo-game%2Flevels> //Instagram
|
||||
<https://www.instagram.com/count_it_group/> //LinkedIn <https://
|
||||
www.linkedin.com/shareArticle?
|
||||
mini=true&url=https%3A%2F%2Fdd.countit.at%2Fdojo-
|
||||
game%2Flevels&title=&summary=&source=> //E-Mail
|
||||
<mailto:m.mustermann@countit.at?subject=Lese-
|
||||
Tipp%3A%20Dojo%20Game%20%7C%20%C3%9Cbersicht%20der%20Levels%20%26%20Bots&body=Hey%2C%0D%0A%0D%0Aich%20habe%20auf%20https%3A%2F%2Fkarriere.countit.at%20einen%20Beitrag%20gefunden%2C%20der%20dich%20interessieren%20k%C3%B6nnte%3A%0D%0A%22Dojo%20Game%20%7C%20%C3%9Cbersicht%20der%20Levels%20%26%20Bots%22%0D%0ASchau%20ihn%20dir%20mal%20an%3A%20https%3A%2F%2Fkarriere.countit.at%2Fdojo-game%2Flevels%3Fkaid%3Dsharemail%0D%0A%0D%0ABeste%20Gr%C3%BC%C3%9Fe>
|
||||
|
||||
|
||||
Übersicht der Levels & Bots
|
||||
|
||||
|
||||
Dojo Game
|
||||
|
||||
|
||||
//Dojo Game | Levelübersicht
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Im Folgenden findest du wichtige Infos und Eckdaten zu den einzelnen
|
||||
Levels, welche im Spiel implementiert sind. Bestimmte Informationen
|
||||
können sich unter den einzelnen Levels unterscheiden, daher ist es
|
||||
wichtig, in deinem Algorithmus auf diese Attribute einzugehen.
|
||||
Einen Einblick in die verschiedenen Bot-Typen findest du hier. <https://
|
||||
dd.countit.at/dojo-game/levels#bots>
|
||||
|
||||
|
||||
//1. Default Level
|
||||
|
||||
Das Default Level ist das erste Level und gleichzeitig auch das
|
||||
Einsteiger-Level im Dojo Game.
|
||||
|
||||
Während der Laufzeit des Spiels wird jeder Gegner und auch dein Ritter,
|
||||
sobald dieser getötet wird, sofort an einer anderen Stelle des
|
||||
Spielfelds neu geboren. Nach Ablauf der Zeit wird das Spiel automatisch
|
||||
beendet und die Kills und Deaths dieses Spiels dem Leaderboard
|
||||
hinzugefügt. Danach kann direkt ein neues Spiel erstellt werden.
|
||||
Generell ist es möglich, mehrere Gegner auf einmal zu schlagen. Stehen 2
|
||||
Spieler auf demselben Block oder ineinander, so wird beiden gleich viel
|
||||
Schaden zugefügt.
|
||||
|
||||
*Spielfeldgröße:* 15x15
|
||||
|
||||
*Bots:* (4) Default Bot x2, Tank Bot x2
|
||||
|
||||
*Dauer des Spiels:* erfolgreicher Abschluss des Levels oder 15 Minuten
|
||||
|
||||
*Erfolgreicher Abschluss:* 20 Kills
|
||||
|
||||
*Cooldowns:* Move = 250, Hit = 250, Radar = 250, SpecialAttack = 5000,
|
||||
Peek = 1000, Scan = 2000, Dash = 3000, Teleport = 20000, Shoot = 1000
|
||||
|
||||
|
||||
//2. Bigger Map Level
|
||||
|
||||
Im 2. Level des Games vergrößert sich die Spielfläche und die Default
|
||||
Bots werden mehr!
|
||||
|
||||
Dieses Level wird nach 15 Minuten beendet. Wenn du nach Ablauf der Zeit
|
||||
100% Progress hast, wirst du ins nächste Level weitergeleitet.
|
||||
|
||||
Während der Laufzeit des Spiels wird jeder Gegner und auch dein Ritter,
|
||||
sobald dieser getötet wird, sofort an einer anderen Stelle des
|
||||
Spielfelds neu geboren. Nach Ablauf der Zeit wird das Spiel automatisch
|
||||
beendet und die Kills und Deaths dieses Spiels dem Leaderboard
|
||||
hinzugefügt. Danach kann direkt ein neues Spiel erstellt werden.
|
||||
Generell ist es möglich, mehrere Gegner auf einmal zu schlagen. Stehen 2
|
||||
Spieler auf demselben Block oder ineinander, so wird beiden gleich viel
|
||||
Schaden zugefügt.
|
||||
|
||||
*Spielfeldgröße:* 25x25
|
||||
|
||||
*Bots:* (5) Default Bot x5
|
||||
|
||||
*Dauer des Spiels:* 15 Minuten
|
||||
|
||||
*Erfolgreicher Abschluss:* 5 Kills
|
||||
|
||||
Cooldowns: Move = 250, Hit = 250, Radar = 250, SpecialAttack = 5000,
|
||||
Peek = 1000, Scan = 2000, Dash = 3000, Teleport = 20000, Shoot = 1000
|
||||
|
||||
|
||||
//3. More Bots Level
|
||||
|
||||
In diesem Level kämpfst du gegen 6 Tank Bots - pass auf, der Radius der
|
||||
Specialattack ist in diesem Level von 3 Blöcken auf 1 Block geschrumpft!
|
||||
|
||||
Während der Laufzeit des Spiels wird jeder Gegner und auch dein Ritter,
|
||||
sobald dieser getötet wird, sofort an einer anderen Stelle des
|
||||
Spielfelds neu geboren. Nach Ablauf der Zeit wird das Spiel automatisch
|
||||
beendet und die Kills und Deaths dieses Spiels dem Leaderboard
|
||||
hinzugefügt. Danach kann direkt ein neues Spiel erstellt werden.
|
||||
Generell ist es möglich, mehrere Gegner auf einmal zu schlagen. Stehen 2
|
||||
Spieler auf demselben Block oder ineinander, so wird beiden gleich viel
|
||||
Schaden zugefügt.
|
||||
|
||||
*Spielfeldgröße:* 25x25
|
||||
|
||||
*Bots:* (6) Tank Bot x6
|
||||
|
||||
*Dauer des Spiels: *15 Minuten oder erfolgreicher Abschluss des Levels
|
||||
|
||||
*Erfolgreicher Abschluss:* 10 Kills
|
||||
|
||||
Cooldowns: Move = 250, Hit = 250, Radar = 250, SpecialAttack = 5000,
|
||||
Peek = 1000, Scan = 2000, Dash = 3000, Teleport = 20000, Shoot = 1000
|
||||
|
||||
|
||||
//4. New Bots Level
|
||||
|
||||
Hier kämpfst du gegen 5 Psy Bots, welche mit einer neuen Fähigkeit
|
||||
ausgestattet wurden.
|
||||
|
||||
Während der Laufzeit des Spiels wird jeder Gegner und auch dein Ritter,
|
||||
sobald dieser getötet wird, sofort an einer anderen Stelle des
|
||||
Spielfelds neu geboren. Nach Ablauf der Zeit wird das Spiel automatisch
|
||||
beendet und die Kills und Deaths dieses Spiels dem Leaderboard
|
||||
hinzugefügt. Danach kann direkt ein neues Spiel erstellt werden.
|
||||
Generell ist es möglich, mehrere Gegner auf einmal zu schlagen. Stehen 2
|
||||
Spieler auf demselben Block oder ineinander, so wird beiden gleich viel
|
||||
Schaden zugefügt.
|
||||
|
||||
*Spielfeldgröße:* 15x15
|
||||
|
||||
*Bots:* (5) Psy Bot x5
|
||||
|
||||
*Dauer des Spiels: *15 Minuten
|
||||
|
||||
*Erfolgreicher Abschluss:* 10 Kills
|
||||
|
||||
Cooldowns: Move = 250, Hit = 250, Radar = 250, SpecialAttack = 5000,
|
||||
Peek = 1000, Scan = 2000, Dash = 3000, Teleport = 20000, Shoot = 1000
|
||||
|
||||
|
||||
//Übersicht der Bots
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
In den Dojo Game Levels sind verschiedene Bot-Typen vorhanden. Jeder Bot
|
||||
hat eine individuelle Fähigkeit und eigene Attribute, welche ihn
|
||||
einzigartig machen. Hier findest du eine Liste der Bots und ihren Eckdaten.
|
||||
|
||||
|
||||
//The Default Bot
|
||||
|
||||
Der Default Bot hat die selben Rahmenbedingungen wie ein echter Spieler.
|
||||
Dies macht ihn, die Werte betreffend, gleich stark wie einen Spieler.
|
||||
Sie unterscheiden sich nur in deren Implementierung.
|
||||
|
||||
*Lebenspunkte beim Start:* 10.0
|
||||
|
||||
*Maximale Lebenspunkte:* 10.0
|
||||
|
||||
*Einfacher Schaden:* 3
|
||||
|
||||
*Special-Attack:* 3 Blöcke Rundumschlag mit einfachem Schaden
|
||||
|
||||
|
||||
//The Tank Bot
|
||||
|
||||
Der Tank Bot ist ein auf den ersten Blick schwächer erscheinender Bot,
|
||||
welcher aber mit ansteigender Spieldauer immer stärker wird.
|
||||
|
||||
*Lebenspunkte beim Start:* 5.0
|
||||
|
||||
*Maximale Lebenspunkte:* 10.0
|
||||
|
||||
*Einfacher Schaden:* 3
|
||||
|
||||
*Special-Attack:* +1 Lebenspunkt zusätzlich und 3 Blöcke Rundumschlag
|
||||
mit einfachem Schaden
|
||||
|
||||
|
||||
//The Psy Bot
|
||||
|
||||
Der Psy Bot verhält sich gleich wie der Default Bot, jedoch
|
||||
unterscheidet er sich deutlich von diesem durch seinen Special-Attack.
|
||||
Dieser hat nämlich die Fähigkeit, sich beim Erhalten von Schaden oder
|
||||
alle 5 Sekunden seinen Standort zu ändern und sich an eine zufällige
|
||||
Position zu teleportieren.
|
||||
|
||||
*Lebenspunkte beim Start:* 10.0
|
||||
|
||||
*Maximale Lebenspunkte: *10.0
|
||||
|
||||
*Einfacher Schaden:* 3
|
||||
|
||||
*Special-Attack:* Standort ändern an einen zufälligen Ort am Spielfeld
|
||||
|
||||
|
||||
Los geht's mit dem Dojo-Game!
|
||||
|
||||
Erster Platz am Leaderboard? Klingt genial!
|
||||
|
||||
Dojo-Game //
|
||||
<https://dd.countit.at/dojo-game>
|
||||
/
|
||||
|
||||
/
|
||||
|
||||
|
||||
Programmier-Challenge
|
||||
|
||||
Stell dich der Programmier-Challenge und miss dich mit deinen
|
||||
Klassenkolleg*innen.
|
||||
|
||||
Jetzt teilnehmen! // <https://dd.countit.at/programmierwettbewerb>
|
||||
/
|
||||
|
||||
/
|
||||
|
||||
|
||||
Ultimative Coding-Roadmap
|
||||
|
||||
Unsere Roadmap weist dir den Weg zum Coding-Profi!
|
||||
|
||||
Mehr erfahren // <https://dd.countit.at/programmieren-lernen>
|
||||
/
|
||||
|
||||
/
|
||||
|
||||
|
||||
Bewerbung bei COUNT IT
|
||||
|
||||
Starte deine Karriere als Softwareentwickler*in bei COUNT IT.
|
||||
|
||||
Zum Formular! // <https://dd.countit.at/bewerben>
|
||||
|
||||
|
||||
Über Digital Dojo
|
||||
|
||||
Das Digital Dojo ist der virtuelle Übungsraum von COUNT IT.
|
||||
|
||||
Angehende Programmierer*innen, Code-Neulinge, Wiedereinsteiger*innen und
|
||||
Fortgeschrittene finden hier das nötige Rüstzeug für ihre Karriere.
|
||||
|
||||
Du möchtest deine Lehre bei COUNT IT starten? Dann bist du hier richtig
|
||||
- besiege deine Gegner im Dojo Game und sichere dir deine Lehrstelle!
|
||||
|
||||
Inspire your career.
|
||||
|
||||
|
||||
Navigation
|
||||
|
||||
Programmieren lernen <https://dd.countit.at/programmieren-lernen> News
|
||||
<https://dd.countit.at/news>
|
||||
Programmierschule <https://dd.countit.at/programmierschule> Partner
|
||||
<https://dd.countit.at/partner>
|
||||
Programmier-Challenge <https://dd.countit.at/programmierwettbewerb>
|
||||
Dojo Game <https://dd.countit.at/dojo-game>
|
||||
|
||||
|
||||
Datenschutz <https://www.countit.at/datenschutz> Karriere @ COUNT IT
|
||||
<https://karriere.countit.at/>
|
||||
Impressum <https://www.countit.at/impressum> COUNT IT
|
||||
Softwarehouse <https://it.countit.at/>
|
||||
|
||||
|
||||
Newsletter abonnieren
|
||||
|
||||
Der COUNT IT Newsletter liefert viermal jährlich interessante
|
||||
Neuigkeiten über das Unternehmen. Gleich anfordern!
|
||||
|
||||
E-Mail-Adresse
|
||||
Senden
|
||||
|
||||
// <https://de-de.facebook.com/COUNTITGroup/> // <https://
|
||||
www.instagram.com/count_it_group/> // <https://de.linkedin.com/company/
|
||||
count-it-group>
|
||||
|
||||
//
|
||||
|
||||
COUNT IT Group verwendet Cookies, um die Website bestmöglich an die
|
||||
Bedürfnisse der User anpassen zu können.
|
||||
|
||||
* Google
|
||||
* Microsoft
|
||||
* Ahrefs
|
||||
* Statistik
|
||||
|
||||
Akzeptieren
|
||||
Individuelle EinstellungenIndividuelle Einstellungen ausblenden |
|
||||
Datenschutzerklärung <https://www.countit.at/datenschutz>
|
||||
|
||||
Google
|
||||
|
||||
Google Analytics und Optimize aggregiert Daten über unsere Besucher und
|
||||
ihr Verhalten auf unserer Website. Wir nutzen diese Informationen zur
|
||||
Verbesserung unserer Seite.
|
||||
|
||||
Microsoft
|
||||
|
||||
Microsoft aggregiert Daten über unsere Besucher und ihr Verhalten auf
|
||||
unserer Website. Wir nutzen diese Informationen zur Verbesserung unserer
|
||||
Seite.
|
||||
|
||||
Ahrefs
|
||||
|
||||
Ahrefs aggregiert Daten über unsere Besucher und ihr Verhalten auf
|
||||
unserer Website. Wir nutzen diese Informationen zur Verbesserung unserer
|
||||
Seite.
|
||||
|
||||
Statistik
|
||||
|
||||
Es werden grundlegend notwendige, anonymisierte Userdaten aufgezeichnet,
|
||||
welche nur von uns für Auswertungen verwendet werden!
|
||||
|
||||
Cookie Entscheidung speichern
|
||||
288
DDApp/DDApplication/multiplayer.txt
Normal file
288
DDApp/DDApplication/multiplayer.txt
Normal 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 |
|
||||
Datenschutzerklärung <https://www.countit.at/datenschutz>
|
||||
|
||||
Google
|
||||
|
||||
Google Analytics und Optimize aggregiert Daten über unsere Besucher und
|
||||
ihr Verhalten auf unserer Website. Wir nutzen diese Informationen zur
|
||||
Verbesserung unserer Seite.
|
||||
|
||||
Microsoft
|
||||
|
||||
Microsoft aggregiert Daten über unsere Besucher und ihr Verhalten auf
|
||||
unserer Website. Wir nutzen diese Informationen zur Verbesserung unserer
|
||||
Seite.
|
||||
|
||||
Ahrefs
|
||||
|
||||
Ahrefs aggregiert Daten über unsere Besucher und ihr Verhalten auf
|
||||
unserer Website. Wir nutzen diese Informationen zur Verbesserung unserer
|
||||
Seite.
|
||||
|
||||
Statistik
|
||||
|
||||
Es werden grundlegend notwendige, anonymisierte Userdaten aufgezeichnet,
|
||||
welche nur von uns für Auswertungen verwendet werden!
|
||||
|
||||
Cookie Entscheidung speichern
|
||||
633
DDApp/DDApplication/spieleinstieg.txt
Normal file
633
DDApp/DDApplication/spieleinstieg.txt
Normal 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 |
|
||||
Datenschutzerklärung <https://www.countit.at/datenschutz>
|
||||
|
||||
Google
|
||||
|
||||
Google Analytics und Optimize aggregiert Daten über unsere Besucher und
|
||||
ihr Verhalten auf unserer Website. Wir nutzen diese Informationen zur
|
||||
Verbesserung unserer Seite.
|
||||
|
||||
Microsoft
|
||||
|
||||
Microsoft aggregiert Daten über unsere Besucher und ihr Verhalten auf
|
||||
unserer Website. Wir nutzen diese Informationen zur Verbesserung unserer
|
||||
Seite.
|
||||
|
||||
Ahrefs
|
||||
|
||||
Ahrefs aggregiert Daten über unsere Besucher und ihr Verhalten auf
|
||||
unserer Website. Wir nutzen diese Informationen zur Verbesserung unserer
|
||||
Seite.
|
||||
|
||||
Statistik
|
||||
|
||||
Es werden grundlegend notwendige, anonymisierte Userdaten aufgezeichnet,
|
||||
welche nur von uns für Auswertungen verwendet werden!
|
||||
|
||||
Cookie Entscheidung speichern
|
||||
453
main-aggro.py
Normal file
453
main-aggro.py
Normal 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
453
main-less-aggro.py
Normal 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)
|
||||
Reference in New Issue
Block a user