Skip to content

PBWebMedia/tank-game

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tank Game

A competitive AI programming environment where developers create intelligent tank controllers to battle against each other in a 2D arena. This project serves as both an educational tool for learning AI programming concepts and a competitive platform for testing different AI strategies.

🎮 Game Overview

Tank Game is a real-time combat simulation where AI-controlled tanks fight in a physics-based arena. Each tank is controlled by a custom AI script that makes decisions about movement, aiming, and firing. The last tank standing wins!

Key Features

  • Real-time Combat: Tanks move, aim, and fire projectiles with realistic physics
  • Multiple AI Support: Run up to 15 different AI implementations simultaneously
  • Physics-Based Movement: Acceleration, friction, and collision detection
  • Health & Damage System: Tanks start with 100 HP, projectiles deal 5 damage
  • Firing Mechanics: 0.5-second cooldown between shots prevents spam
  • Arena Boundaries: 600x450 pixel battlefield with water obstacles
  • Visual Feedback: SDL3-based graphics with colorful tank identification

Game Mechanics

  • Arena Size: 600x450 pixels
  • Tank Health: 100 HP (eliminated at 0 HP)
  • Projectile Damage: 5 HP per hit
  • Fire Cooldown: 0.5 seconds between shots
  • Tank Speed: Max 150 pixels/second with acceleration
  • Turn Rate: 180 degrees per second (π radians/second)
  • Tank Colors: Red, Green, Blue, Yellow, Magenta, Cyan

🚀 Installation & Setup

Prerequisites

SDL3 Library is required for graphics rendering.

Windows

  1. Download sdl3.dll from the SDL3 GitHub releases
  2. Place sdl3.dll in the project root directory

Linux (Ubuntu/Debian)

sudo apt-get install libsdl3 libsdl3-image

Linux (Fedora/Red Hat)

sudo dnf install SDL3 SDL3_image

macOS

brew install sdl3

Building and Running

  1. Clone the repository:

    git clone <repository-url>
    cd tank-game
  2. Install Go dependencies:

    go mod tidy
  3. Run the game:

    go run main.go

The game will automatically load all registered AI implementations and start the battle!

🎯 How to Play

The game runs automatically once started. You'll see:

  • Colored tanks representing different AI implementations
  • Projectiles flying across the battlefield
  • Real-time combat as AIs make decisions and battle
  • Health indicators showing tank status
  • Victory condition when only one tank remains

Controls

This is an AI-only game - there are no manual controls. All tanks are controlled by their respective AI scripts.

🤖 Creating Your Own AI

AI Interface

Every AI must implement the game.AI interface with three methods:

type AI interface {
    GetName() string
    Initialize(tank *Tank, gameState *GameState) error
    Update(tank *Tank, gameState *GameState, commands *Commands) error
}

Available Commands

Your AI can control the tank using these commands:

type Commands struct {
    MoveForward  bool  // Accelerate forward
    MoveBackward bool  // Accelerate backward
    TurnLeft     bool  // Turn counterclockwise
    TurnRight    bool  // Turn clockwise
    Fire         bool  // Shoot projectile (if cooldown ready)
}

Tank Properties

Your AI has access to your tank's properties:

// Position and movement
tank.Position     // Vector2 - current position
tank.Rotation     // float64 - current rotation in radians
tank.Velocity     // Vector2 - current velocity

// Combat stats
tank.Health       // int - current health (0-100)
tank.IsAlive      // bool - whether tank is still active
tank.CanFire()    // bool - whether tank can fire (cooldown ready)

// Physics properties
tank.MaxSpeed     // float64 - maximum speed (150)
tank.ViewRange    // float64 - sight range (200)

Game State Information

Your AI can access global game information:

// All tanks in the game
gameState.Tanks         // []*Tank - all tanks (including yours)
gameState.Projectiles   // []*Projectile - all active projectiles

// Arena information
gameState.ArenaWidth    // float64 - arena width (1024)
gameState.ArenaHeight   // float64 - arena height (512)
gameState.GameTime      // float64 - elapsed game time

// Utility methods
gameState.GetAliveTanks()                    // Get only living tanks
gameState.IsValidPosition(pos, radius)       // Check if position is valid

📝 Simple AI Example

Here's a basic AI that moves forward and shoots at the nearest enemy:

package ai

import (
    "math"
    "tank-game/lib/game"
)

type SimpleAI struct {
    name string
}

func NewSimpleAI() *SimpleAI {
    return &SimpleAI{name: "Simple AI"}
}

func (ai *SimpleAI) GetName() string {
    return ai.name
}

func (ai *SimpleAI) Initialize(tank *game.Tank, gameState *game.GameState) error {
    return nil
}

func (ai *SimpleAI) Update(tank *game.Tank, gameState *game.GameState, commands *game.Commands) error {
    // Always move forward
    commands.MoveForward = true
    
    // Find nearest enemy
    nearestEnemy := ai.findNearestEnemy(tank, gameState)
    if nearestEnemy != nil {
        // Calculate angle to enemy
        dx := nearestEnemy.Position.X - tank.Position.X
        dy := nearestEnemy.Position.Y - tank.Position.Y
        targetAngle := math.Atan2(dy, dx)
        
        // Calculate angle difference
        angleDiff := targetAngle - tank.Rotation
        
        // Normalize angle (-π to π)
        for angleDiff > math.Pi {
            angleDiff -= 2 * math.Pi
        }
        for angleDiff < -math.Pi {
            angleDiff += 2 * math.Pi
        }
        
        // Turn towards enemy
        if angleDiff > 0.1 {
            commands.TurnRight = true
        } else if angleDiff < -0.1 {
            commands.TurnLeft = true
        }
        
        // Fire if aimed at enemy
        if math.Abs(angleDiff) < 0.2 {
            commands.Fire = true
        }
    }
    
    return nil
}

func (ai *SimpleAI) findNearestEnemy(myTank *game.Tank, gameState *game.GameState) *game.Tank {
    var nearest *game.Tank
    minDistance := math.MaxFloat64
    
    for _, tank := range gameState.Tanks {
        if tank == myTank || !tank.IsAlive {
            continue
        }
        
        dx := tank.Position.X - myTank.Position.X
        dy := tank.Position.Y - myTank.Position.Y
        distance := math.Sqrt(dx*dx + dy*dy)
        
        if distance < minDistance {
            minDistance = distance
            nearest = tank
        }
    }
    
    return nearest
}

🧠 Advanced AI Example

Here's a more sophisticated AI with projectile dodging and tactical positioning:

package ai

import (
    "math"
    "tank-game/lib/common"
    "tank-game/lib/game"
)

type AdvancedAI struct {
    name            string
    lastDodgeTime   float64
    targetPosition  *common.Vector2
    patrolRadius    float64
}

func NewAdvancedAI() *AdvancedAI {
    return &AdvancedAI{
        name:         "Advanced AI",
        patrolRadius: 150.0,
    }
}

func (ai *AdvancedAI) GetName() string {
    return ai.name
}

func (ai *AdvancedAI) Initialize(tank *game.Tank, gameState *game.GameState) error {
    // Set initial patrol center
    ai.targetPosition = &common.Vector2{
        X: tank.Position.X,
        Y: tank.Position.Y,
    }
    return nil
}

func (ai *AdvancedAI) Update(tank *game.Tank, gameState *game.GameState, commands *game.Commands) error {
    // Priority 1: Dodge incoming projectiles
    if ai.shouldDodge(tank, gameState) {
        ai.performDodge(tank, gameState, commands)
        ai.lastDodgeTime = gameState.GameTime
        return nil
    }
    
    // Priority 2: Attack nearest enemy
    nearestEnemy := ai.findNearestEnemy(tank, gameState)
    if nearestEnemy != nil && ai.getDistance(tank.Position, nearestEnemy.Position) < tank.ViewRange {
        ai.attackEnemy(tank, nearestEnemy, commands)
        return nil
    }
    
    // Priority 3: Tactical positioning
    ai.tacticalMovement(tank, gameState, commands)
    
    return nil
}

func (ai *AdvancedAI) shouldDodge(tank *game.Tank, gameState *game.GameState) bool {
    // Don't dodge too frequently
    if gameState.GameTime-ai.lastDodgeTime < 1.0 {
        return false
    }
    
    // Check for incoming projectiles
    for _, projectile := range gameState.Projectiles {
        if !projectile.IsActive {
            continue
        }
        
        // Calculate if projectile is heading towards us
        distance := ai.getDistance(tank.Position, projectile.Position)
        if distance < 100 { // Danger zone
            // Predict projectile path
            futurePos := common.Vector2{
                X: projectile.Position.X + projectile.Velocity.X*0.5,
                Y: projectile.Position.Y + projectile.Velocity.Y*0.5,
            }
            
            if ai.getDistance(tank.Position, futurePos) < 30 {
                return true
            }
        }
    }
    
    return false
}

func (ai *AdvancedAI) performDodge(tank *game.Tank, gameState *game.GameState, commands *game.Commands) {
    // Find safest direction to dodge
    bestAngle := tank.Rotation + math.Pi/2 // Default: turn 90 degrees
    
    // Check multiple dodge directions
    for angle := 0.0; angle < 2*math.Pi; angle += math.Pi / 4 {
        testPos := common.Vector2{
            X: tank.Position.X + math.Cos(angle)*50,
            Y: tank.Position.Y + math.Sin(angle)*50,
        }
        
        if ai.isPositionSafe(testPos, gameState) {
            bestAngle = angle
            break
        }
    }
    
    // Turn towards safe direction
    angleDiff := bestAngle - tank.Rotation
    ai.normalizeAngle(&angleDiff)
    
    if angleDiff > 0.1 {
        commands.TurnRight = true
    } else if angleDiff < -0.1 {
        commands.TurnLeft = true
    }
    
    commands.MoveForward = true
}

func (ai *AdvancedAI) attackEnemy(tank *game.Tank, enemy *game.Tank, commands *game.Commands) {
    distance := ai.getDistance(tank.Position, enemy.Position)
    
    // Maintain optimal combat distance
    optimalDistance := 120.0
    
    if distance > optimalDistance+20 {
        // Too far - move closer
        ai.moveTowards(tank, enemy.Position, commands)
    } else if distance < optimalDistance-20 {
        // Too close - back away
        commands.MoveBackward = true
    }
    
    // Aim at enemy with prediction
    predictedPos := ai.predictEnemyPosition(enemy, 0.3) // Predict 0.3 seconds ahead
    ai.aimAt(tank, predictedPos, commands)
    
    // Fire if well-aimed
    targetAngle := math.Atan2(predictedPos.Y-tank.Position.Y, predictedPos.X-tank.Position.X)
    aimError := targetAngle - tank.Rotation
    ai.normalizeAngle(&aimError)
    
    if math.Abs(aimError) < 0.15 && tank.CanFire() {
        commands.Fire = true
    }
}

func (ai *AdvancedAI) tacticalMovement(tank *game.Tank, gameState *game.GameState, commands *game.Commands) {
    // Move to strategic position
    centerX := gameState.ArenaWidth / 2
    centerY := gameState.ArenaHeight / 2
    
    // Patrol around center with some randomness
    patrolAngle := gameState.GameTime * 0.5 // Slow patrol
    targetX := centerX + math.Cos(patrolAngle)*ai.patrolRadius
    targetY := centerY + math.Sin(patrolAngle)*ai.patrolRadius
    
    targetPos := common.Vector2{X: targetX, Y: targetY}
    ai.moveTowards(tank, targetPos, commands)
}

func (ai *AdvancedAI) moveTowards(tank *game.Tank, target common.Vector2, commands *game.Commands) {
    targetAngle := math.Atan2(target.Y-tank.Position.Y, target.X-tank.Position.X)
    angleDiff := targetAngle - tank.Rotation
    ai.normalizeAngle(&angleDiff)
    
    // Turn towards target
    if angleDiff > 0.1 {
        commands.TurnRight = true
    } else if angleDiff < -0.1 {
        commands.TurnLeft = true
    }
    
    // Move forward if roughly facing target
    if math.Abs(angleDiff) < math.Pi/2 {
        commands.MoveForward = true
    }
}

func (ai *AdvancedAI) aimAt(tank *game.Tank, target common.Vector2, commands *game.Commands) {
    targetAngle := math.Atan2(target.Y-tank.Position.Y, target.X-tank.Position.X)
    angleDiff := targetAngle - tank.Rotation
    ai.normalizeAngle(&angleDiff)
    
    if angleDiff > 0.05 {
        commands.TurnRight = true
    } else if angleDiff < -0.05 {
        commands.TurnLeft = true
    }
}

func (ai *AdvancedAI) predictEnemyPosition(enemy *game.Tank, timeAhead float64) common.Vector2 {
    return common.Vector2{
        X: enemy.Position.X + enemy.Velocity.X*timeAhead,
        Y: enemy.Position.Y + enemy.Velocity.Y*timeAhead,
    }
}

func (ai *AdvancedAI) isPositionSafe(pos common.Vector2, gameState *game.GameState) bool {
    // Check arena bounds
    margin := 30.0
    if pos.X < margin || pos.X > gameState.ArenaWidth-margin ||
       pos.Y < margin || pos.Y > gameState.ArenaHeight-margin {
        return false
    }
    
    // Check for nearby projectiles
    for _, projectile := range gameState.Projectiles {
        if ai.getDistance(pos, projectile.Position) < 40 {
            return false
        }
    }
    
    return gameState.IsValidPosition(pos, 25.0)
}

func (ai *AdvancedAI) findNearestEnemy(myTank *game.Tank, gameState *game.GameState) *game.Tank {
    var nearest *game.Tank
    minDistance := math.MaxFloat64
    
    for _, tank := range gameState.Tanks {
        if tank == myTank || !tank.IsAlive {
            continue
        }
        
        distance := ai.getDistance(myTank.Position, tank.Position)
        if distance < minDistance {
            minDistance = distance
            nearest = tank
        }
    }
    
    return nearest
}

func (ai *AdvancedAI) getDistance(pos1, pos2 common.Vector2) float64 {
    dx := pos1.X - pos2.X
    dy := pos1.Y - pos2.Y
    return math.Sqrt(dx*dx + dy*dy)
}

func (ai *AdvancedAI) normalizeAngle(angle *float64) {
    for *angle > math.Pi {
        *angle -= 2 * math.Pi
    }
    for *angle < -math.Pi {
        *angle += 2 * math.Pi
    }
}

🔧 Registering Your AI

To add your AI to the game:

  1. Create your AI file in the ai/ directory (e.g., ai/my_ai.go)

  2. Implement the AI interface as shown in the examples above

  3. Register your AI in ai/registry.go:

    func init() {
        RegisterAI("example", func() game.AI { return NewExampleAI() })
        RegisterAI("example_2", func() game.AI { return NewExampleAI2() })
        RegisterAI("my_ai", func() game.AI { return NewMyAI() })  // Add this line
    }
  4. Run the game - your AI will automatically be loaded and assigned a colored tank!

🎯 AI Strategy Tips

Basic Strategies

  • Aggressive: Always move towards enemies and fire frequently
  • Defensive: Maintain distance and only engage when advantageous
  • Evasive: Focus on dodging and hit-and-run tactics

Advanced Techniques

  • Projectile Prediction: Calculate where enemies will be when your shot arrives
  • Collision Avoidance: Use gameState.IsValidPosition() for pathfinding
  • Tactical Positioning: Control key areas of the battlefield
  • Resource Management: Balance aggression with survival

Performance Considerations

  • Avoid expensive calculations in the Update() method (called 60 times per second)
  • Cache frequently used calculations
  • Use efficient algorithms for distance calculations and enemy detection

🏆 Victory Conditions

The game ends when only one tank remains alive. Victory strategies include:

  • Elimination: Destroy all enemy tanks
  • Survival: Outlast opponents through superior tactics
  • Positioning: Control the battlefield and force enemies into disadvantageous positions

🐛 Troubleshooting

Common Issues

Game won't start: Ensure SDL3 is properly installed and sdl3.dll is in the project directory (Windows)

AI not appearing: Check that your AI is registered in ai/registry.go and implements all required methods

Compilation errors: Run go mod tidy to ensure all dependencies are installed

Tank gets stuck: Use gameState.IsValidPosition() to check for valid movement positions

Debug Tips

  • Use fmt.Printf() in your AI's Update() method for debugging (remove before final submission)
  • Check tank health and position values to understand behavior
  • Monitor gameState.GameTime for time-based debugging

Ready to create your AI? Start with the simple example and gradually add more sophisticated behaviors. Good luck in the arena! 🚀

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages