An open API service indexing awesome lists of open source software.

https://github.com/pbwebmedia/tank-game


https://github.com/pbwebmedia/tank-game

Last synced: 9 months ago
JSON representation

Awesome Lists containing this project

README

          

# 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](https://github.com/libsdl-org/SDL/releases)
2. Place `sdl3.dll` in the project root directory

#### Linux (Ubuntu/Debian)
```bash
sudo apt-get install libsdl3 libsdl3-image
```

#### Linux (Fedora/Red Hat)
```bash
sudo dnf install SDL3 SDL3_image
```

#### macOS
```bash
brew install sdl3
```

### Building and Running

1. **Clone the repository**:
```bash
git clone
cd tank-game
```

2. **Install Go dependencies**:
```bash
go mod tidy
```

3. **Run the game**:
```bash
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:

```go
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:

```go
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:

```go
// 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:

```go
// 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:

```go
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:

```go
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`:
```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! 🚀