Replace continuous free-form movement with discrete 20x50 grid-based gameplay featuring asymmetric movement mechanics: - Blue/Red teams with 3 units each - Zone-based movement: orthogonal (4 dir) in offense, diagonal (8 dir) in defense - Alternating turns with click-to-select, click-to-move input - Fog of war: 3-cell Chebyshev vision radius per unit - Defense speed nerf: skip every 4th move in own zone - AI opponent that chases flag carriers and advances toward enemy flag - Collision resolution: defender wins in their zone, lower ID wins in neutral Implements all 3 phases from the plan: - Phase 1: Playable grid with hot-seat two-player - Phase 2: Fog of war + defense speed nerf - Phase 3: AI opponent Deleted obsolete files: Flag.cs, Unit.cs, RouteDrawer.cs, SimpleAI.cs, Visibility.cs Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
title, type, date, brainstorm
| title | type | date | brainstorm |
|---|---|---|---|
| feat: Asymmetric Grid CTF Board | feat | 2026-02-04 | docs/brainstorms/2026-02-04-asymmetric-grid-ctf-brainstorm.md |
feat: Asymmetric Grid CTF Board
Overview
Replace the current real-time continuous movement CTF game with a turn-based grid system featuring asymmetric movement mechanics. Each team's 3 pieces move differently based on which zone they occupy: orthogonal (Manhattan) in offensive zones, diagonal (Chebyshev) in defensive zones.
Key changes from current implementation:
- Continuous free-form movement → Discrete grid-based movement
- Real-time gameplay → Alternating turn-based
- Uniform movement → Zone-dependent movement rules
Problem Statement
The current implementation is a real-time CTF game where:
- Units move continuously along drawn paths
- All units have the same movement capabilities
- The defense has no inherent advantage
The game design analysis (see docs/CAPTURE THE FLAG.pdf) shows that asymmetric movement creates interesting strategic depth:
- Defense moving diagonally covers more ground (Chebyshev distance)
- Offense moving orthogonally is predictable (Manhattan distance)
- With 75% defense speed nerf, this becomes a balanced mixed-strategy game
Proposed Solution
Build a 20x50 grid-based board with three zones and alternating turn-based gameplay.
Board Layout
┌────────────────────────────────────────────────────────┐
│ TEAM B DEFENSE │ Y: 30-49
│ (Team A Offense) │ (20 rows)
│ [Diagonal Movement] │
│ 🚩 Flag B │
├────────────────────────────────────────────────────────┤
│ NEUTRAL ZONE │ Y: 20-29
│ (Both teams on offense) │ (10 rows)
│ [Orthogonal Movement] │
├────────────────────────────────────────────────────────┤
│ TEAM A DEFENSE │ Y: 0-19
│ (Team B Offense) │ (20 rows)
│ [Diagonal Movement] │
│ 🚩 Flag A │
└────────────────────────────────────────────────────────┘
X: 0 ←────────── 20 cells ──────────→ 19
Movement Rules
| Zone (for piece) | Movement Type | Speed |
|---|---|---|
| Own Defense | Diagonal (8 directions) | 75% (skip every 4th turn) |
| Enemy Defense (Offense) | Orthogonal (4 directions: N/S/E/W) | 100% |
| Neutral | Orthogonal (4 directions) | 100% |
Turn Flow (Alternating)
┌─────────────────┐
│ PLAYER TURN │ Click unit → click destination → unit moves
└────────┬────────┘
▼
┌─────────────────┐
│ RESOLVE │ Check collision, flag pickup, scoring
└────────┬────────┘
▼
┌─────────────────┐
│ AI TURN │ AI selects and executes move
└────────┬────────┘
▼
┌─────────────────┐
│ RESOLVE │ Check collision, flag pickup, scoring
└────────┬────────┘
▼
Next turn
Note: Start with alternating turns for simplicity. Simultaneous turns can be added later if alternating feels too simple.
Technical Approach
Architecture (3 Classes)
Assets/Scripts/
├── Grid/
│ ├── GridBoard.cs # Board state, zones, rendering, input, turn logic
│ ├── GridUnit.cs # Unit data struct
│ └── GridAI.cs # AI decision-making (Phase 3)
├── Game.cs # Bootstrap, update for grid
├── Flag.cs # Adapt for grid coordinates
└── CameraController.cs # Keep as-is
Zone Boundaries (Constants)
public static class ZoneBoundaries
{
public const int TeamADefenseEnd = 20; // Y < 20 is Team A defense
public const int NeutralEnd = 30; // Y < 30 is neutral (if >= TeamADefenseEnd)
public const int BoardWidth = 20;
public const int BoardHeight = 50;
}
Data Structures
public enum Team { Blue, Red } // Canonical names used everywhere
public enum ZoneOwner { Blue, Neutral, Red }
// GridUnit.cs - minimal data struct
public class GridUnit
{
public int UnitId; // Stable identifier for determinism
public Team Team;
public Vector2Int GridPosition;
public int ConsecutiveDefenseMoves; // Reset when leaving defense zone
public bool IsTaggedOut;
public int RespawnTurnsRemaining; // 0 when active
// Note: HasFlag NOT stored here - query Flag.CarriedBy instead
}
// GridBoard.cs - all game logic in one place
public class GridBoard : MonoBehaviour
{
// Board is a 2D array of unit references (null = empty)
GridUnit[,] cellOccupants = new GridUnit[BoardWidth, BoardHeight];
// Canonical position storage - single source of truth
Dictionary<GridUnit, Vector2Int> unitPositions = new();
public ZoneOwner GetZoneOwner(int y) => y switch
{
< ZoneBoundaries.TeamADefenseEnd => ZoneOwner.Blue,
< ZoneBoundaries.NeutralEnd => ZoneOwner.Neutral,
_ => ZoneOwner.Red
};
public bool IsDefending(GridUnit unit) =>
(unit.Team == Team.Blue && GetZoneOwner(unit.GridPosition.y) == ZoneOwner.Blue) ||
(unit.Team == Team.Red && GetZoneOwner(unit.GridPosition.y) == ZoneOwner.Red);
// Atomic move that keeps both data structures in sync
public void MoveUnit(GridUnit unit, Vector2Int to)
{
var from = unitPositions[unit];
cellOccupants[from.x, from.y] = null;
cellOccupants[to.x, to.y] = unit;
unitPositions[unit] = to;
unit.GridPosition = to;
}
}
Collision Resolution (Deterministic)
Collisions are resolved deterministically by unit ID (lower ID wins ties):
void ResolveCollision(GridUnit unitA, GridUnit unitB, Vector2Int cell)
{
// Same team = no collision (can share cell temporarily)
if (unitA.Team == unitB.Team) return;
var zoneOwner = GetZoneOwner(cell.y);
// Defender in their zone always wins
if (unitA.Team == Team.Blue && zoneOwner == ZoneOwner.Blue)
TagOut(unitB);
else if (unitB.Team == Team.Blue && zoneOwner == ZoneOwner.Blue)
TagOut(unitA);
else if (unitA.Team == Team.Red && zoneOwner == ZoneOwner.Red)
TagOut(unitB);
else if (unitB.Team == Team.Red && zoneOwner == ZoneOwner.Red)
TagOut(unitA);
else
{
// Neutral zone: lower UnitId wins (deterministic)
if (unitA.UnitId < unitB.UnitId)
TagOut(unitB);
else
TagOut(unitA);
}
}
Turn Execution Order
Each turn resolves in this exact order:
- Speed nerf check - If unit is defending and
ConsecutiveDefenseMoves % 4 == 3, skip move - Execute move - Update position
- Collision check - Tag losing unit if two enemies on same cell
- Flag pickup - Unit on enemy flag cell picks it up
- Flag drop - Tagged unit drops flag at current position
- Score check - Flag carrier in own base scores
- Respawn tick - Decrement
RespawnTurnsRemaining, respawn if 0 - Update visibility - Toggle enemy sprite visibility (Phase 2)
Implementation Phases
Phase 1: Playable Grid
Goal: Two humans can play hot-seat CTF on a grid.
Tasks:
- Create
GridBoard.cs:- Render 20x50 grid using existing
CreateSprite()helper - Color zones (blue tint for Blue defense, gray for neutral, red tint for Red defense)
- Handle mouse input (click unit, click destination)
- Implement
GetValidMoves()with zone-aware movement - Implement
MoveUnit()with atomic position update - Implement collision resolution
- Render 20x50 grid using existing
- Create
GridUnit.csas minimal data struct - Adapt
Flag.csfor grid coordinates:- Flag pickup on entering cell
- Flag drop on tag (stays at cell)
- Flag return when friendly touches it
- Score when carrier reaches own base
- Modify
Game.csto instantiateGridBoardinstead of current map - Alternating turns:
bool isPlayerTurn, swap after each move - Win at 3 points, use existing
gameOverText
Files to create:
Assets/Scripts/Grid/GridBoard.csAssets/Scripts/Grid/GridUnit.cs
Files to modify:
Assets/Scripts/Game.csAssets/Scripts/Flag.cs
Verification:
Run game → See colored grid with 6 pieces and 2 flags
Click unit → Valid moves highlight (4 or 8 based on zone)
Click valid cell → Unit moves, turn swaps
Move to enemy flag → Pick up flag
Return to base with flag → Score point
Move onto enemy in your zone → Enemy respawns
First to 3 → "You Win" / "You Lose"
Phase 2: Asymmetric Mechanics
Goal: Add the mechanics that create strategic depth.
Tasks:
- Add fog of war:
- Each unit sees 3 cells (Chebyshev distance)
- Toggle enemy sprite
enabledbased on visibility - Recalculate after each move
- Add defense speed nerf:
- Track
ConsecutiveDefenseMoveson GridUnit - Skip every 4th move when defending
- Reset counter when leaving defense zone
- Visual indicator (dim sprite) when next move will be skipped
- Track
Fog visibility (method in GridBoard):
HashSet<Vector2Int> visibleToBlue = new();
void RecalculateVisibility()
{
visibleToBlue.Clear();
foreach (var unit in blueUnits)
{
if (unit.IsTaggedOut) continue;
for (int dx = -3; dx <= 3; dx++)
for (int dy = -3; dy <= 3; dy++)
{
var cell = unit.GridPosition + new Vector2Int(dx, dy);
if (IsInBounds(cell)) visibleToBlue.Add(cell);
}
}
foreach (var enemy in redUnits)
enemy.Sprite.enabled = visibleToBlue.Contains(enemy.GridPosition);
}
Verification:
Start game → Red units partially hidden
Move Blue unit → Fog reveals new cells
Defender moves 3 times → 4th move skipped (unit dims beforehand)
Defender leaves zone → Counter resets
Phase 3: AI Opponent
Goal: Single-player mode.
Tasks:
- Create
GridAI.cs:- AI respects fog (only sees what its units see)
- Simple strategy: chase visible flag carrier, else advance toward flag
- AI takes turn after player
- Delete
Assets/Scripts/SimpleAI.cs
AI decision (single difficulty):
Vector2Int? DecideMove(GridUnit unit)
{
var validMoves = board.GetValidMoves(unit);
if (validMoves.Count == 0) return null;
// Priority 1: Chase visible flag carrier
var carrier = GetVisibleEnemyFlagCarrier();
if (carrier != null)
return validMoves.OrderBy(m => Distance(m, carrier.GridPosition)).First();
// Priority 2: Advance toward enemy flag
var flagPos = enemyFlag.GridPosition;
return validMoves.OrderBy(m => Distance(m, flagPos)).First();
}
Files to create:
Assets/Scripts/Grid/GridAI.cs
Files to delete:
Assets/Scripts/SimpleAI.cs
Verification:
Start game → AI takes turns after player
AI chases if it sees flag carrier
AI advances toward flag otherwise
AI doesn't react to units outside fog
Full game completes without errors
Deferred (Not MVP)
These features are explicitly deferred until the core loop is validated:
- Simultaneous turns (adds: planned moves storage, submit button, ghost indicators, conflict resolution)
- Turn timer
- Sound effects
- Camera auto-zoom
- Zone labels
- Win/lose screen (use
Debug.Logor existinggameOverText) - Multiple AI difficulties
- Respawn delay (use instant respawn)
- Flag auto-return timer (flags stay where dropped)
Edge Cases to Handle
| Edge Case | Resolution |
|---|---|
| Three+ units on same cell | Process collisions pairwise by UnitId order |
| Zone crossing during move | Speed nerf based on starting position |
| Flag dropped in neutral | Can be "returned" by either team touching it |
| Both carriers tagged same turn | Both flags drop (alternating turns makes this impossible) |
| Tagged while about to score | Tag resolves before score check |
| Skip turn with flag | Carrier stays in place, keeps flag |
| Respawn location occupied | Respawn at nearest empty cell in base |
Acceptance Criteria
Functional (Phase 1 - MVP)
- 20x50 grid renders with three colored zones
- 6 pieces (3 per team) at starting positions
- Orthogonal movement in offense zones, diagonal in defense
- Alternating turns
- Collision: defender wins in their zone
- Flags: pickup, drop on tag, return on touch, score on base
- First to 3 wins
Functional (Phase 2)
- 3-cell vision radius per unit
- Enemy units hidden outside fog
- Defenders skip every 4th move
Functional (Phase 3)
- AI plays Red team
- AI respects fog
- Full game completes
Success Metrics
- Playable: Full game loop from start to victory works
- Fun check: Does asymmetric movement feel strategically interesting?
Balance testing and engagement metrics deferred until core loop is validated.
References
- Brainstorm:
docs/brainstorms/2026-02-04-asymmetric-grid-ctf-brainstorm.md - Game design PDF:
docs/CAPTURE THE FLAG.pdf - Current movement:
Assets/Scripts/Unit.cs:42 - Bootstrap pattern:
Assets/Scripts/Game.cs:15