https://github.com/pbwebmedia/tank-game
https://github.com/pbwebmedia/tank-game
Last synced: 9 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/pbwebmedia/tank-game
- Owner: PBWebMedia
- Created: 2025-09-23T09:49:30.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-09-23T12:09:34.000Z (9 months ago)
- Last Synced: 2025-09-23T12:29:46.211Z (9 months ago)
- Language: Go
- Size: 24.6 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
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! 🚀