Creating New Labs
This guide provides a complete process for creating new labs in the AGT system. We’ll use a simple “Coin Flip” game as an example.
Lab Creation Overview
A new lab requires these components:
Game Implementation - Defines game mechanics and rules
Agent Examples - Reference implementations for students
Server Integration - Enables the lab on the server
Student Stencil - Starting point for students
Documentation - Student and administrator guides
Testing - Validates everything works correctly
Step 1: Design Your Game
Game Concept
Define your game clearly:
Objective: What are players trying to achieve?
Actions: What can players do on their turn?
State: What information do players have?
Rewards: How do players score points?
Example: Coin Flip Game
A simple 2-player coordination game:
Players simultaneously choose “Heads” or “Tails”
If both choose the same, they both get 1 point
If they choose differently, they both get 0 points
Game runs for 100 rounds
Step 2: Implement the Game
2.1 Create Game Class
Create core/game/CoinFlipGame.py:
import numpy as np
from core.game.MatrixGame import MatrixGame
class CoinFlipGame(MatrixGame):
"""Coin Flip coordination game."""
def __init__(self, rounds: int = 100):
# Payoff matrix: both players get same reward
payoff_tensor = np.array([
[[1.0, 1.0], [0.0, 0.0]], # Heads vs Heads, Tails
[[0.0, 0.0], [1.0, 1.0]] # Tails vs Heads, Tails
]).reshape(1, 2, 2, 2)
action_labels = ["Heads", "Tails"]
super().__init__(payoff_tensor, rounds)
self.action_labels = action_labels
2.2 Understanding MatrixGame Pattern
The MatrixGame class provides:
Automatic stage management - Creates new stages for each round
Reward accumulation - Tracks cumulative scores
Standard interface - Implements BaseGame methods
Key Methods to Override:
__init__()- Set up payoff matrix and game parametersaction_labels- Human-readable action names (optional)
2.3 Alternative: Custom Game Implementation
For complex games, implement BaseGame directly:
from core.game.base_game import BaseGame, ObsDict, ActionDict, RewardDict, InfoDict
class CustomGame(BaseGame):
def __init__(self, rounds: int = 100):
self.rounds = rounds
self.current_round = 0
self.metadata = {"num_players": 2}
def reset(self, seed: int | None = None) -> ObsDict:
if seed is not None:
np.random.seed(seed)
self.current_round = 0
return {0: {}, 1: {}}
def players_to_move(self) -> List[int]:
return [0, 1]
def step(self, actions: ActionDict) -> Tuple[ObsDict, RewardDict, bool, InfoDict]:
action1, action2 = actions[0], actions[1]
# Calculate rewards based on game logic
if action1 == action2:
reward1 = reward2 = 1.0 # Both get 1 if they coordinate
else:
reward1 = reward2 = 0.0 # Both get 0 if they don't coordinate
self.current_round += 1
done = self.current_round >= self.rounds
return (
{0: {}, 1: {}},
{0: reward1, 1: reward2},
done,
{0: {"opponent_action": action2}, 1: {"opponent_action": action1}}
)
Step 3: Create Agent Examples
3.1 Random Agent
Create core/agents/labXX/random_coinflip_agent.py:
from core.agents.common.base_agent import BaseAgent
import random
class RandomCoinFlipAgent(BaseAgent):
def __init__(self, name: str):
super().__init__(name)
self.actions = [0, 1] # Heads, Tails
def get_action(self, observation: Dict[str, Any]) -> Any:
return random.choice(self.actions)
def update(self, reward: float, info: Dict[str, Any]):
super().update(reward, info)
def reset(self):
super().reset()
3.2 Example Solution
Create core/agents/labXX/example_coinflip_solution.py:
from core.agents.common.base_agent import BaseAgent
class ExampleCoinFlipSolution(BaseAgent):
def __init__(self, name: str):
super().__init__(name)
self.opponent_history = []
def get_action(self, observation: Dict[str, Any]) -> Any:
if len(self.opponent_history) == 0:
return 0 # Start with Heads
else:
# Try to coordinate on most common opponent action
opponent_freq = self.analyze_opponent()
return 0 if opponent_freq[0] > opponent_freq[1] else 1
def update(self, reward: float, info: Dict[str, Any]):
super().update(reward, info)
if "opponent_action" in info:
self.opponent_history.append(info["opponent_action"])
def reset(self):
super().reset()
self.opponent_history = []
def analyze_opponent(self):
if not self.opponent_history:
return {0: 0.5, 1: 0.5}
freq = {}
for action in [0, 1]:
freq[action] = self.opponent_history.count(action) / len(self.opponent_history)
return freq
Step 4: Update Server Configuration
4.1 Add Game to Server
Add to server/server.py in the game_configs dictionary:
"coinflip": {
"name": "Coin Flip",
"game_class": CoinFlipGame,
"num_players": 2,
"num_rounds": 100,
"description": "Simple coordination game"
},
4.2 Import the Game
Add the import at the top of server/server.py:
from core.game.CoinFlipGame import CoinFlipGame
4.3 Create Configuration File
Create server/configs/labXX_coinflip.json:
{
"game_type": "coinflip",
"num_players": 2,
"rounds_per_game": 100,
"timeout": 300,
"description": "Coin Flip Coordination Lab"
}
Step 5: Create Student Stencil
5.1 Stencil Structure
Create directory stencils/labXX_stencil/:
stencils/labXX_stencil/
├── README.md
├── requirements.txt
├── my_agent.py
├── example_solution.py
└── test_agent.py
5.2 Student Agent Template
Create stencils/labXX_stencil/my_agent.py:
from core.agents.common.base_agent import BaseAgent
class MyAgent(BaseAgent):
def __init__(self, name: str):
super().__init__(name)
self.opponent_history = []
def get_action(self, observation: Dict[str, Any]) -> Any:
# TODO: Implement your coordination strategy
# You can access self.opponent_history to see opponent's actions
return 0 # Example: always choose Heads
def update(self, reward: float, info: Dict[str, Any]):
super().update(reward, info)
if "opponent_action" in info:
self.opponent_history.append(info["opponent_action"])
def reset(self):
super().reset()
self.opponent_history = []
5.3 Example Solution
Create stencils/labXX_stencil/example_solution.py:
#!/usr/bin/env python3
"""
Example solution for Lab XX - Coin Flip
This shows what a completed implementation looks like.
"""
import sys
import os
import numpy as np
from core.agents.common.base_agent import BaseAgent
from core.engine import Engine
from core.game.CoinFlipGame import CoinFlipGame
from core.agents.labXX.random_coinflip_agent import RandomCoinFlipAgent
class ExampleCoordinationAgent(BaseAgent):
"""Example implementation of coordination strategy for Coin Flip."""
def __init__(self, name: str = "ExampleCoord"):
super().__init__(name)
self.HEADS, self.TAILS = 0, 1
self.actions = [self.HEADS, self.TAILS]
self.opponent_history = []
self.coordination_count = 0
def get_action(self, obs):
"""Return action based on opponent analysis."""
if len(self.opponent_history) == 0:
return self.HEADS # Start with Heads
else:
# Analyze opponent's behavior
opponent_freq = self.analyze_opponent()
return self.best_coordination_action(opponent_freq)
def update(self, reward: float, info: Dict[str, Any]):
"""Store the reward and update opponent action counts."""
super().update(reward, info)
# Store opponent's action if available
if "opponent_action" in info:
self.opponent_history.append(info["opponent_action"])
# Track coordination success
if reward > 0:
self.coordination_count += 1
def analyze_opponent(self):
"""Analyze opponent's action frequencies."""
if not self.opponent_history:
return {0: 0.5, 1: 0.5} # Default to 50/50
freq = {}
for action in self.actions:
freq[action] = self.opponent_history.count(action) / len(self.opponent_history)
return freq
def best_coordination_action(self, opponent_freq):
"""Choose action that maximizes coordination probability."""
if opponent_freq[self.HEADS] > opponent_freq[self.TAILS]:
return self.HEADS # Opponent prefers Heads
else:
return self.TAILS # Opponent prefers Tails
def reset(self):
"""Reset for new game."""
super().reset()
self.opponent_history = []
self.coordination_count = 0
if __name__ == "__main__":
print("Example Solution for Lab XX")
print("=" * 40)
# Test coordination strategy vs Random
print("\nTesting Coordination vs Random:")
game = CoinFlipGame(rounds=100)
agents = [
ExampleCoordinationAgent("ExampleCoord"),
RandomCoinFlipAgent("Random")
]
engine = Engine(game, agents, rounds=100)
final_rewards = engine.run()
print(f"Final rewards: {final_rewards}")
print(f"Cumulative rewards: {engine.cumulative_reward}")
# Print detailed statistics
coord_agent = agents[0]
action_counts = [0, 0] # Heads, Tails
for action in coord_agent.action_history:
action_counts[action] += 1
print(f"\n{coord_agent.name} statistics:")
print(f"Heads: {action_counts[0]}, Tails: {action_counts[1]}")
print(f"Total reward: {sum(coord_agent.reward_history)}")
print(f"Average reward: {sum(coord_agent.reward_history) / len(coord_agent.reward_history):.3f}")
print(f"Coordination rate: {coord_agent.coordination_count / len(coord_agent.reward_history):.1%}")
print("\nExample solution completed!")
print("Use this as reference for implementing your own agents.")
5.4 Test Script
Create stencils/labXX_stencil/test_agent.py:
from core.engine import Engine
from core.game.CoinFlipGame import CoinFlipGame
from core.agents.labXX.random_coinflip_agent import RandomCoinFlipAgent
from my_agent import MyAgent
def test_agent():
my_agent = MyAgent("MyAgent")
random_agent = RandomCoinFlipAgent("Random")
game = CoinFlipGame(rounds=100)
engine = Engine(game, [my_agent, random_agent], rounds=100)
results = engine.run()
print(f"My score: {results[0]}")
print(f"Random opponent score: {results[1]}")
coordination_rate = results[0] / 100.0
print(f"Coordination rate: {coordination_rate:.1%}")
if __name__ == "__main__":
test_agent()
Step 6: Create Documentation
6.1 Student Documentation
Create docs/students/labXX-coin-flip.md:
# Lab XX: Coin Flip
This lab introduces coordination games through a simple coin flip scenario.
## Game Overview
**Type:** Coordination game
**Players:** 2 players
**Rounds:** 100 rounds per game
**Stages:** Single stage that repeats
## Games
### Coin Flip Game
- **Actions:** Heads (0), Tails (1)
- **State Space:** Empty observations (players know the game structure)
- **Key Concept:** Coordination and strategic thinking
## State Space
### Observations
```python
observation = {} # Empty - you know the game structure
Actions
action = 0 # Heads
action = 1 # Tails
Rewards
Coordination payoffs:
if my_action == opponent_action: # Both choose same
reward = 1 # Both get 1 point
else: # Different choices
reward = 0 # Both get 0 points
Game Structure
Stage Type
Single stage that repeats for all rounds
Simultaneous moves - both players act at same time
Coordination challenge - players must choose the same action
Learning Opportunities
Opponent modeling - understand what your opponent will do
Coordination strategies - find ways to coordinate effectively
Strategic thinking - balance between leading and following
Testing
Local Testing
from core.engine import Engine
from core.game.CoinFlipGame import CoinFlipGame
from core.agents.labXX.random_coinflip_agent import RandomCoinFlipAgent
my_agent = MyAgent("MyAgent")
opponent = RandomCoinFlipAgent("Random")
engine = Engine(CoinFlipGame(), [my_agent, opponent], rounds=100)
results = engine.run()
print(f"My score: {results[0]}")
print(f"Opponent score: {results[1]}")
Next Steps
Implement a coordination agent using the common patterns
Test against different opponents to understand performance
Analyze results to see what works
Compete against other students
Focus on understanding coordination and strategic thinking!
## Step 7: Create Tests
### 7.1 Unit Tests
Create `tests/labs/labXX/test_coinflip.py`:
```python
import unittest
from core.game.CoinFlipGame import CoinFlipGame
from core.agents.labXX.random_coinflip_agent import RandomCoinFlipAgent
from core.engine import Engine
class TestCoinFlipGame(unittest.TestCase):
def test_game_creation(self):
game = CoinFlipGame()
self.assertIsNotNone(game)
def test_game_execution(self):
game = CoinFlipGame(rounds=10)
agent1 = RandomCoinFlipAgent("Agent1")
agent2 = RandomCoinFlipAgent("Agent2")
engine = Engine(game, [agent1, agent2], rounds=10)
results = engine.run()
self.assertEqual(len(results), 2)
self.assertIsInstance(results[0], (int, float))
self.assertIsInstance(results[1], (int, float))
def test_coordination_rewards(self):
game = CoinFlipGame(rounds=100)
agent1 = RandomCoinFlipAgent("Agent1")
agent2 = RandomCoinFlipAgent("Agent2")
engine = Engine(game, [agent1, agent2], rounds=100)
results = engine.run()
# Both agents should have similar scores (coordination game)
self.assertAlmostEqual(results[0], results[1], delta=20)
def test_agent_interface(self):
agent = RandomCoinFlipAgent("TestAgent")
# Test required methods exist
self.assertTrue(hasattr(agent, 'get_action'))
self.assertTrue(hasattr(agent, 'update'))
self.assertTrue(hasattr(agent, 'reset'))
# Test method signatures
action = agent.get_action({})
self.assertIn(action, [0, 1]) # Valid actions
agent.update(1.0, {})
agent.reset()
if __name__ == '__main__':
unittest.main()
Step 8: Integration Checklist
8.1 Pre-Deployment Checklist
Game implementation works correctly
Agent interface is properly implemented
Server configuration includes new game
Student stencil is complete and functional
Documentation is clear and accurate
Tests pass and cover key functionality
Integration with existing system works
8.2 Testing Procedures
# Test game implementation
python -c "from core.game.CoinFlipGame import CoinFlipGame; print('Game OK')"
# Test agent loading
python -c "from core.agents.labXX.random_coinflip_agent import RandomCoinFlipAgent; print('Agent OK')"
# Test engine integration
python tests/labs/labXX/test_coinflip.py
# Test server integration
python server/server.py --test-game coinflip
# Test student stencil
cd stencils/labXX_stencil
python test_agent.py
8.3 Validation Steps
Test locally - Run the game with sample agents
Test server - Verify server can handle the new game
Test stencil - Ensure students can use the provided template
Test documentation - Verify all instructions are clear
Test integration - Make sure it works with existing labs
Best Practices
Game Design
Start simple - Begin with basic mechanics
Test thoroughly - Ensure game works correctly
Document clearly - Explain rules and objectives
Consider learning - Design for educational value
Implementation
Follow patterns - Use existing code as templates
Test incrementally - Test each component separately
Validate thoroughly - Ensure all edge cases work
Document everything - Include clear comments and docs
Deployment
Test with small group - Validate with limited users
Monitor performance - Watch for issues during rollout
Gather feedback - Collect student and instructor input
Iterate quickly - Fix issues promptly
Common Pitfalls
Game Implementation
Forgetting metadata - Always set
self.metadata["num_players"]Incorrect payoff matrix - Double-check reward calculations
Missing imports - Ensure all dependencies are imported
Agent Implementation
Not calling super() - Always call parent class methods
Forgetting reset() - Clear state between games
Incorrect action types - Match expected action format
Server Integration
Missing game config - Add to
game_configsdictionaryWrong import path - Use correct import statements
Incorrect game ID - Use consistent naming
Next Steps
Follow this guide step by step for your new lab
Adapt the example to your specific game mechanics
Test thoroughly before deploying to students
Gather feedback and improve the lab
Document lessons learned for future labs
Creating new labs is now straightforward and systematic!