Skip to content

Conversation

@DvaMishkiLapa
Copy link

@DvaMishkiLapa DvaMishkiLapa commented Aug 30, 2025

Thinking about PR #8, I tried to solve a strange problem and improve the overall behavior of the AI.
I wouldn't say I achieved everything I wanted, but the bots started shooting better and no longer circle around the player if he flies in circles all the time.

In addition, it's now really interesting to watch two bots play.
This doesn't solve the issue of the strange "low-speed-to-back" tactic of the plane, but I didn't try to solve it.

I tried to describe what I did in more detail. But I suspect that there is a more elegant way to do it, as this is my first time doing this :D

Description of changes

  • Pattern Recognition watches how the opponent moves and detects when they're stuck in predictable patterns
  • Personality System gives each bot unique characteristics that affect how they approach combat
  • Tactical Strategy adapts behavior based on the current situation health, distance, and tactical advantage
  • Pattern Breaking prevents the AI from getting stuck in the same loops that plagued the old system

Core Components

1. Pattern Recognition System

Detects when opponents are stuck in predictable patterns and responds appropriately.

Key Features:

  • Position History: Tracks up to 32 recent opponent positions and directions
  • Circular Pattern Detection: Identifies when opponent is flying in circles
  • Stale Pursuit Detection: Detects when AI is stuck following the same pattern
  • Pattern Breaking Logic: Determines when and how to break out of patterns

include/ai_patterns.hpp (lines 37-47) - AiPatternTracker structure

How Circular Pattern Detection Works:

  1. Centroid Calculation: Computes center of mass of all opponent positions
  2. Average Distance: Calculates average distance from center to all positions
  3. Variance Analysis: Measures how much distances vary from the average
  4. Threshold Comparison: If variance is low, positions form a circle
  5. Hysteresis: Uses adaptive thresholds to prevent rapid state switching

2. Pattern Breaking Strategy

Prevents infinite circular pursuit loops by forcing AI to change course.

Breaking Conditions:

  • Close to Center: AI is near pattern center (good position to break)
  • Pattern Too Long: Pursuit has been ongoing for too long
  • Time Exceeded: Pattern has lasted more than 0.5 seconds
  • Opponent Too Close: Dangerous proximity requires immediate action

src/ai_patterns.cpp (lines 130-174) - shouldBreakCircularPattern() method

Breaking Mechanism:

  • Creates strong interest in opposite direction (180° from opponent)
  • Adds interest in perpendicular directions (90° and -90°)
  • Clears interest in opponent's direction
  • Forces AI to change course and break the pattern

src/ai_stuff.cpp (lines 940-1041) - Pattern breaking implementation

3. Enhanced Combat with Movement Prediction

AI uses enhanced shooting logic with movement prediction for better accuracy in all combat situations.

Key Behaviors:

  • Movement Prediction: Predicts opponent's future position based on current velocity
  • Multiple Prediction Points: Uses linear and lead prediction for better accuracy
  • Enhanced Accuracy: Improved hit rate against moving targets in all situations
  • Pattern Breaking: Forces AI to change direction when stuck in circular pursuit

src/ai_stuff.cpp (lines 98-161) - shouldShootWithPrediction() function

4. Personality System

Gives each bot unique behavioral characteristics and tactical preferences.

Core Parameters:

  • Aggressiveness (0.0-1.0): Tendency to seek combat vs. avoid conflict
  • Prediction Weight (0.0-1.0): Use of complex tactics and anticipation
  • Risk Tolerance (0.0-1.0): Willingness to take calculated risks
  • Adaptability (0.0-1.0): Ability to change strategy based on opponent

Personality Types:

  • AGGRESSIVE: Always attacks, doesn't fear risk, high combat engagement
  • DEFENSIVE: Prefers to avoid conflicts, prioritizes survival
  • BALANCED: Mixed strategy, adapts to situation
  • PREDICTIVE: Uses complex prediction tactics, anticipates moves

src/ai_patterns.cpp (lines 177-227) - updateBasedOnDifficulty() method

5. Tactical Strategy System

Adapts AI behavior based on personality and tactical situation.

Strategy Selection Logic:

Defensive Strategy (when to use):

  • AI has significantly less health than opponent
  • Opponent is dangerously close (< 0.2 units)
  • Personality is naturally defensive (aggressiveness < 0.3)

Defensive Behavior:

  • Creates wide danger zone (7 directions) around opponent
  • Danger strength inversely proportional to aggressiveness
  • Forces AI to avoid engagement and maintain distance

src/ai_patterns.cpp (lines 248-266) - shouldUseDefensiveStrategy() method

Aggressive Strategy (when to use):

  • AI has health advantage over opponent (> 80% of opponent's health)
  • Close enough to engage (< 0.4 units)
  • Personality is naturally aggressive (aggressiveness > 0.7)

Aggressive Behavior:

  • Creates focused interest zone (3 directions) around opponent
  • Interest strength proportional to aggressiveness
  • Encourages AI to engage and attack

src/ai_patterns.cpp (lines 229-266) - shouldUseAggressiveStrategy() method
src/ai_stuff.cpp (lines 1087-1100) - Aggressive behavior implementation

6. Enhanced Aiming System

Improves shooting accuracy based on personality.

src/ai_patterns.cpp (lines 268-272)

How it works:

  • Higher prediction weight = more accurate aiming
  • Reduces aim cone spread for better bots
  • Makes difficulty progression more meaningful

src/ai_patterns.cpp (lines 268-275) - getModifiedAimCone() method

Difficulty Levels and Behavior

EASY

  • Personality: DEFENSIVE
  • Pattern Detection: Basic only
  • Strategy: Primarily defensive, avoids conflict
  • Aiming: Wide aim cone, less accurate
  • Reaction Time: Slow (0.35s for most actions)

MEDIUM

  • Personality: BALANCED
  • Pattern Detection: Full circular pattern detection
  • Strategy: Mixed defensive/aggressive based on situation
  • Aiming: Moderate accuracy improvement
  • Reaction Time: Moderate (0.25s for most actions)

HARD

  • Personality: AGGRESSIVE
  • Pattern Detection: Full system with pattern breaking
  • Strategy: Primarily aggressive, seeks combat
  • Aiming: Good accuracy
  • Reaction Time: Fast (0.17s for most actions)
  • Tactical Positioning: Uses perpendicular directions for better angles

DEVELOPER

  • Personality: PREDICTIVE
  • Pattern Detection: Full system with advanced breaking
  • Strategy: Complex prediction-based tactics
  • Aiming: Excellent accuracy
  • Reaction Time: Very fast (0.1s for most actions)
  • Advanced Features: All HARD features plus maximum prediction

New Behavior Patterns

The system introduces five distinct behavior patterns that make the AI feel more intelligent and unpredictable:

1. Adaptive Pursuit

  • When: AI detects circular or stale pattern
  • Behavior: Analyzes opponent movement and adapts strategy
  • Result: Switches between normal pursuit and tactical positioning

2. Tactical Positioning

  • When: High-difficulty bots (HARD and above) or circular pattern detected
  • Behavior: Looks for better attack angles instead of direct pursuit
  • Result: Creates interest in perpendicular directions for better positioning

3. Dynamic Avoidance

  • When: Defensive mode or low health
  • Behavior: Creates danger zones around opponent to avoid engagement
  • Result: Wide danger zone (7 directions) around opponent

4. Targeted Pursuit

  • When: Aggressive strategy or health advantage
  • Behavior: Creates focused interest zones to encourage engagement
  • Result: Narrow interest zone (3 directions) around opponent

5. Intelligent Pattern Breaking

  • When: Dangerous or prolonged patterns detected
  • Behavior: Forces course change to escape tactical rut
  • Result: Creates strong interest in opposite direction

Technical Implementation

Data Structures

include/ai_patterns.hpp (lines 37-57) - AiPatternTracker structure
include/ai_patterns.hpp (lines 60-79) - AiPersonality structure

New Constants

What circularPatternThreshold regulates:

  • Controls how "perfect" a circle must be to be detected as a circular pattern
  • How it works:
    • AI calculates variance of distances from pattern center
    • Lower variance = more circular pattern (points equidistant from center)
    • circularPatternThreshold * 2.0f = maximum allowed variance
    • If variance < threshold → pattern is considered circular
  • Effect:
    • Lower value (0.1f): Only detects near-perfect circles
    • Higher value (0.3f): Detects loose/approximate circles
    • Current (0.23f): Balanced detection for typical circular movements

What stalePursuitThreshold regulates:

  • Controls how long AI can pursue the same pattern before considering it "stale"
  • How it works:
    • AI tracks opponent movement with mPursuitTimer
    • Timer increments when opponent moves < 0.01f units and direction changes < 5°
    • Timer resets when opponent makes significant movement
    • If timer exceeds stalePursuitThreshold → pattern is considered stale
  • Effect:
    • Lower value (2.0f): AI breaks patterns quickly (more reactive)
    • Higher value (8.0f): AI persists longer in patterns (more patient)
    • Current (3.0f): Balanced persistence for typical gameplay
  • Usage:
    • isStalePursuit(): Detects stale pursuit state
    • shouldBreakCircularPattern(): Uses stalePursuitThreshold * 0.5f for breaking decisions

What movementPredictionTime regulates:

  • Controls how far ahead AI predicts opponent movement for better shooting accuracy
  • How it works:
    • AI calculates opponent's velocity from position history
    • Predicts future position using movementPredictionTime seconds ahead
    • Uses both linear and lead prediction for enhanced accuracy
    • Applied in ALL combat situations, not just circular patterns
  • Effect:
    • Lower value (0.05f): Minimal prediction, more reactive shooting
    • Higher value (0.3f): Longer prediction, better for fast-moving targets
    • Current (0.15f): Balanced prediction for typical gameplay
  • Usage:
    • Pattern-based combat: Enhanced accuracy during circular patterns
    • Normal combat: Improved hit rate against moving targets

@Casqade
Copy link
Collaborator

Casqade commented Aug 31, 2025

@DvaMishkiLapa, thanks for your commitment!

I've played against Hard & Developer bots with your changes and I've noticed two things:

  1. Bots are less predictable and harder to hit, which is a good thing
  2. At the same time, I felt less pressure because they tend to shoot less frequently. Maybe it's because of aiming prediction? When I was implementing AI, I also wanted to add a similar system, but direct aiming proved accurate enough to annihilate players, so I dropped the idea

However, there are several issues with your implementation. Some of the systems you implemented don't work as you described. To make them work, I applied some fixes but I'm not sure they affect bot behaviour as intended.
First of all, please verify that you don't have any uncommitted changes, because your branch doesn't even build (new sources missing in CMakeLists.txt).

And now to more serious stuff.
Are you sure AiPatternTracker::isCircularPattern() algorithm is suitable for points which are VERY close to each other? As the game tick is fixed to ~8ms, the mRecentPositions buffer will be filled up in 256ms. Given plane pitch cooldown of 100ms, this results in only ~2.5 turns out of 16 forming a full circle. And the distance between consecutive points will likely be around
0.303 (base plane speed, units per second) * 0.008 (tick time, seconds per tick) = ~0.0024 units per tick.

Also, I printed out mIsInCircularPattern every tick and found two problems:

  1. Most of the time it's false regardless of my flying behaviour
  2. It becomes true (for a few moments) only when I cross the screen border

In my attempt at fixing the first problem I used a timer giving enough time to make a full (or at least half) circle until MAX_POSITIONS points are pushed into history buffer. Maybe it will be reasonable to have 16 (or even 8) recent positions which are sampled every 100ms (plane pitch cooldown).

Problem 1 fix proposal for AiPatternTracker::update():

mPositionsHistoryTimer.Update(); // initialised to e.g. 100ms

bool positionsUpdated = mPositionsHistoryTimer.isReady();

if ( positionsUpdated == true )
{
  mRecentPositions.push_back({opponent.x(), opponent.y()});
  mRecentDirections.push_back(opponent.dir());

  if (mRecentPositions.size() > MAX_POSITIONS)
  {
    mRecentPositions.erase(mRecentPositions.begin());
    mRecentDirections.erase(mRecentDirections.begin());
  }

  mPositionsHistoryTimer.Start();
}

// ...

// no need to recalculate `mRecentPositions`-dependent variables, as the buffer is unchanged
if ( positionsUpdated == false )
  return;

mIsInCircularPattern = isCircularPattern();
// ...
}

The second problem exists because your algorithm doesn't take X coordinate wrapping into account. When the plane crosses screen border, the difference between two point sequences becomes too big and that messes up all calculations.

Problem 2 fix proposal for AiPatternTracker::isCircularPattern().
For each of 3 loops, shift the saved position to account for coordinate wrapping:

auto prevPos = mRecentPositions.front();
for ( auto pos : mRecentPositions )
{
  const float wrapMargin = 0.5f;
  float distance = pos.x - prevPos.x;
  
  if ( distance <= -wrapMargin  )
    pos.x += 1.f;
  else if ( distance >= wrapMargin  )
    pos.x -= 1.f;
  
  prevPos = pos;
//  loop operations with the pos
}

Also, I'm a bit confused with wasRecentlyCircular calculation at the end of AiPatternTracker::isCircularPattern(). You check for mRecentPositions.size() >= 6, which always evaluates to true since you also have if (mRecentPositions.size() < 8) guard at the top of the same function. By the way, for this condition I suggest changing 8 to MAX_POSITIONS, at least if the point sampling period guarantees 8-16 turns reflected in the buffer.

I won't say how the bots behave with my fixes because I'm not sure it's still the intended way of calculating things. I had to lower circularPatternThreshold to almost 1/20th of your default value to account for a different sampling period, and even given that I don't believe my threshold value is balanced enough because the algorithm fails to detect circle pattern when my plane speeds up to maxSpeedBoosted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants