|
| 1 | +# 3D Tic Tac Toe Implementation Plan |
| 2 | + |
| 3 | +## Overview |
| 4 | +This plan introduces a 3D visualization for Tic Tac Toe using `react-three-fiber`. Users can toggle between the classic 2D grid and an interactive 3D scene where they can rotate the board, see hover effects, and enjoy celebratory effects when winning. |
| 5 | + |
| 6 | +## Implementation Steps |
| 7 | + |
| 8 | +### 1. Install 3D Dependencies |
| 9 | +Install the latest versions of required 3D libraries in the `web` workspace: |
| 10 | + |
| 11 | +```bash |
| 12 | +cd web |
| 13 | +npm install three@^0.170.0 @react-three/fiber@^9.0.0 @react-three/drei@^10.0.0 @react-three/cannon@^6.6.0 |
| 14 | +``` |
| 15 | + |
| 16 | +**Dependencies:** |
| 17 | +- `three` - Core Three.js library for 3D rendering |
| 18 | +- `@react-three/fiber` - React renderer for Three.js |
| 19 | +- `@react-three/drei` - Helper components and abstractions for react-three-fiber |
| 20 | +- `@react-three/cannon` - Physics and effects for celebratory animations |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +### 2. Create 3D Board Component |
| 25 | +**File:** `web/src/components/games/TicTacToeBoard3D.tsx` |
| 26 | + |
| 27 | +**Requirements:** |
| 28 | +- Implement the `TicTacToeBoardProps` interface from `TicTacToeBoard.tsx` |
| 29 | +- Accept the same props: `gameState`, `onMove`, `disabled` |
| 30 | +- Maintain functional parity with the 2D board component |
| 31 | + |
| 32 | +**Component Structure:** |
| 33 | +```typescript |
| 34 | +interface TicTacToeBoardProps { |
| 35 | + gameState: TicTacToeGameState |
| 36 | + onMove: (move: TicTacToeMove) => void |
| 37 | + disabled?: boolean |
| 38 | +} |
| 39 | + |
| 40 | +export function TicTacToeBoard3D({ gameState, onMove, disabled }: TicTacToeBoardProps) { |
| 41 | + // Implementation |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +### 3. Implement 3D Scene |
| 48 | +**Canvas Setup:** |
| 49 | +- Use `<Canvas>` component from `@react-three/fiber` |
| 50 | +- Configure `OrbitControls` from `@react-three/drei` for rotation and zoom |
| 51 | +- Set isometric camera position: `position={[5, 5, 5]}` with `lookAt={[0, 0, 0]}` |
| 52 | +- Allow user to rotate and zoom but maintain clear view of the board |
| 53 | + |
| 54 | +**3x3 Grid Implementation:** |
| 55 | +- Create 9 interactive mesh cells positioned in a 3x3 grid |
| 56 | +- Each cell should be a clickable 3D object |
| 57 | +- Use raycasting for click detection via `onClick` handlers |
| 58 | +- Implement hover effects via `onPointerOver` and `onPointerOut` |
| 59 | + |
| 60 | +**Interaction Model:** |
| 61 | +- Raycasting automatically handled by react-three-fiber's event system |
| 62 | +- Each valid cell should respond to: |
| 63 | + - `onClick` - trigger `onMove({ row, col })` |
| 64 | + - `onPointerOver` - show hover highlight |
| 65 | + - `onPointerOut` - remove hover highlight |
| 66 | +- Disable interactions when `disabled={true}` or cell is occupied |
| 67 | + |
| 68 | +--- |
| 69 | + |
| 70 | +### 4. Add Visual Feedback |
| 71 | + |
| 72 | +**Hover States:** |
| 73 | +- Highlight valid cells on hover (e.g., subtle glow or color change) |
| 74 | +- Only show hover effect on empty cells when it's the player's turn |
| 75 | +- No hover effect during AI turns or on occupied cells |
| 76 | + |
| 77 | +**Disabled State During AI Turns:** |
| 78 | +- Show overlay or dim the board when `disabled={true}` |
| 79 | +- Display "AI's Turn" indicator |
| 80 | +- Prevent all click interactions |
| 81 | + |
| 82 | +**Turn Indicator:** |
| 83 | +- Display current turn above the board |
| 84 | +- Options: |
| 85 | + - Use `<Html>` component from `@react-three/drei` for HTML overlay |
| 86 | + - OR use 3D text mesh |
| 87 | +- Show "Your turn (X)" or "AI thinking... (O)" |
| 88 | + |
| 89 | +**Win Celebration Effect:** |
| 90 | +- Trigger confetti/particle effect when game is won |
| 91 | +- Use `<Sparkles>` from `@react-three/drei` or similar |
| 92 | +- Consider using `@react-three/cannon` for more dynamic effects |
| 93 | +- Effect should be visible but not obstruct the final board state |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +### 5. Update GameControls Component |
| 98 | +**File:** `web/src/components/ui/GameControls.tsx` |
| 99 | + |
| 100 | +**Changes:** |
| 101 | +- Add new optional prop: `onViewToggle?: () => void` |
| 102 | +- Add new optional prop: `is3DMode?: boolean` |
| 103 | +- Render toggle button when `onViewToggle` is provided |
| 104 | +- Button text should indicate current mode: "Switch to 3D View" or "Switch to 2D Grid" |
| 105 | + |
| 106 | +**Example Button:** |
| 107 | +```tsx |
| 108 | +{onViewToggle && ( |
| 109 | + <button onClick={onViewToggle}> |
| 110 | + {is3DMode ? '2D Grid' : '3D View'} |
| 111 | + </button> |
| 112 | +)} |
| 113 | +``` |
| 114 | + |
| 115 | +**Placement:** |
| 116 | +- Add button to the controls panel alongside "New Game" and "Delete" buttons |
| 117 | +- Maintain consistent styling with existing buttons |
| 118 | + |
| 119 | +--- |
| 120 | + |
| 121 | +### 6. Update Tic Tac Toe Page |
| 122 | +**File:** `web/src/app/games/tic-tac-toe/page.tsx` |
| 123 | + |
| 124 | +**State Management:** |
| 125 | +- Add new state: `const [is3DMode, setIs3DMode] = useState<boolean>(false)` |
| 126 | +- Initialize from localStorage on mount |
| 127 | +- Persist to localStorage on toggle |
| 128 | + |
| 129 | +**localStorage Integration:** |
| 130 | +```typescript |
| 131 | +// On mount |
| 132 | +useEffect(() => { |
| 133 | + const saved = localStorage.getItem('ticTacToe_view3D') |
| 134 | + if (saved !== null) { |
| 135 | + setIs3DMode(saved === 'true') |
| 136 | + } |
| 137 | +}, []) |
| 138 | + |
| 139 | +// On toggle |
| 140 | +const handleViewToggle = () => { |
| 141 | + setIs3DMode(prev => { |
| 142 | + const newValue = !prev |
| 143 | + localStorage.setItem('ticTacToe_view3D', String(newValue)) |
| 144 | + return newValue |
| 145 | + }) |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +**Conditional Rendering:** |
| 150 | +```tsx |
| 151 | +const gameBoard = is3DMode ? ( |
| 152 | + <TicTacToeBoard3D |
| 153 | + gameState={gameSession.gameState} |
| 154 | + onMove={makeMove} |
| 155 | + disabled={isLoading || gameSession.gameState.currentPlayerId === 'ai'} |
| 156 | + /> |
| 157 | +) : ( |
| 158 | + <TicTacToeBoard |
| 159 | + gameState={gameSession.gameState} |
| 160 | + onMove={makeMove} |
| 161 | + disabled={isLoading || gameSession.gameState.currentPlayerId === 'ai'} |
| 162 | + /> |
| 163 | +) |
| 164 | +``` |
| 165 | + |
| 166 | +**Pass Props to GameControls:** |
| 167 | +```tsx |
| 168 | +<GameControls |
| 169 | + isLoading={isLoading} |
| 170 | + onNewGame={() => startNewGame()} |
| 171 | + onDelete={() => handleDeleteGame(gameSession.gameState.id)} |
| 172 | + onViewToggle={handleViewToggle} |
| 173 | + is3DMode={is3DMode} |
| 174 | + showDelete={true} |
| 175 | +/> |
| 176 | +``` |
| 177 | + |
| 178 | +**Memory Leak Prevention:** |
| 179 | +- Ensure proper cleanup when toggling views |
| 180 | +- Canvas should unmount cleanly |
| 181 | +- Three.js resources should be disposed properly |
| 182 | +- react-three-fiber handles most cleanup automatically, but verify no lingering references |
| 183 | + |
| 184 | +--- |
| 185 | + |
| 186 | +## Technical Specifications |
| 187 | + |
| 188 | +### Geometries for Game Symbols |
| 189 | + |
| 190 | +**Cell Base:** |
| 191 | +- `BoxGeometry` for the clickable cell surface |
| 192 | +- Material: Semi-transparent when empty, solid when occupied |
| 193 | +- Dimensions: Appropriate spacing for 3x3 grid |
| 194 | + |
| 195 | +**X Symbol:** |
| 196 | +- Two `CylinderGeometry` meshes arranged in an X pattern |
| 197 | +- Rotate cylinders 45° and -45° to form cross |
| 198 | +- Color: Blue (matching 2D version) |
| 199 | + |
| 200 | +**O Symbol:** |
| 201 | +- `TorusGeometry` for circular ring |
| 202 | +- Color: Red (matching 2D version) |
| 203 | + |
| 204 | +### Camera Configuration |
| 205 | +- **Position:** `[5, 5, 5]` (isometric view) |
| 206 | +- **LookAt:** `[0, 0, 0]` (center of board) |
| 207 | +- **Controls:** `OrbitControls` with: |
| 208 | + - Enable rotate: `true` |
| 209 | + - Enable zoom: `true` |
| 210 | + - Enable pan: `false` (optional) |
| 211 | + - Min/max distance limits to prevent extreme zoom |
| 212 | + |
| 213 | +### Performance Considerations |
| 214 | +- Desktop-first optimization (no mobile-specific handling in MVP) |
| 215 | +- Keep geometry complexity low for smooth 60fps |
| 216 | +- Use instanced meshes if performance issues arise |
| 217 | +- Proper disposal of Three.js resources on unmount |
| 218 | + |
| 219 | +### Accessibility Notes |
| 220 | +- 3D mode is optional; 2D mode remains fully functional |
| 221 | +- Toggle is clearly labeled in sidebar |
| 222 | +- Preference persists across page refreshes |
| 223 | +- No keyboard navigation in 3D mode for MVP (future enhancement) |
| 224 | + |
| 225 | +--- |
| 226 | + |
| 227 | +## Testing Strategy (Post-MVP) |
| 228 | +- Manual testing for MVP |
| 229 | +- Future considerations: |
| 230 | + - Mock Three.js for unit tests |
| 231 | + - Visual regression testing for 3D rendering |
| 232 | + - Performance benchmarks |
| 233 | + - Test localStorage persistence |
| 234 | + |
| 235 | +--- |
| 236 | + |
| 237 | +## localStorage Schema |
| 238 | +**Key:** `ticTacToe_view3D` |
| 239 | +**Value:** `"true"` or `"false"` (string representation of boolean) |
| 240 | +**Scope:** Per-browser, persists across page refreshes for the same game type |
| 241 | + |
| 242 | +--- |
| 243 | + |
| 244 | +## File Structure Summary |
| 245 | +``` |
| 246 | +web/ |
| 247 | +├── src/ |
| 248 | +│ ├── components/ |
| 249 | +│ │ ├── games/ |
| 250 | +│ │ │ ├── TicTacToeBoard.tsx (existing) |
| 251 | +│ │ │ └── TicTacToeBoard3D.tsx (NEW) |
| 252 | +│ │ └── ui/ |
| 253 | +│ │ └── GameControls.tsx (MODIFY) |
| 254 | +│ └── app/ |
| 255 | +│ └── games/ |
| 256 | +│ └── tic-tac-toe/ |
| 257 | +│ └── page.tsx (MODIFY) |
| 258 | +└── package.json (MODIFY - add dependencies) |
| 259 | +``` |
| 260 | + |
| 261 | +--- |
| 262 | + |
| 263 | +## Dependencies Impact |
| 264 | +**New packages added to `web/package.json`:** |
| 265 | +- `three@^0.170.0` |
| 266 | +- `@react-three/fiber@^9.0.0` |
| 267 | +- `@react-three/drei@^10.0.0` |
| 268 | +- `@react-three/cannon@^6.6.0` |
| 269 | + |
| 270 | +**Bundle size impact:** ~500KB (Three.js core + react-three libraries) |
| 271 | + |
| 272 | +--- |
| 273 | + |
| 274 | +## Success Criteria |
| 275 | +- [x] User can toggle between 2D and 3D views via sidebar button |
| 276 | +- [x] 3D view renders a rotatable 3x3 Tic Tac Toe board |
| 277 | +- [x] Clicking cells in 3D works identically to 2D |
| 278 | +- [x] Hover effects show which cells are clickable |
| 279 | +- [x] AI turn disables the board with visual feedback |
| 280 | +- [x] Turn indicator shows current player above board |
| 281 | +- [x] Win condition triggers celebratory effect in 3D |
| 282 | +- [x] View preference persists across page refreshes |
| 283 | +- [x] No memory leaks when toggling between views |
| 284 | +- [x] Existing 2D functionality remains unchanged |
| 285 | + |
| 286 | +--- |
| 287 | + |
| 288 | +## Future Enhancements (Out of Scope for MVP) |
| 289 | +- Mobile/touch optimization |
| 290 | +- Keyboard navigation in 3D mode |
| 291 | +- Custom camera presets (top-down, side view, etc.) |
| 292 | +- Animation transitions when placing symbols |
| 293 | +- Sound effects |
| 294 | +- Multiple 3D themes/skins |
| 295 | +- Unit and integration tests for 3D components |
0 commit comments