import React, { useState, useRef } from 'react'; import { Canvas, useFrame } from '@react-three/fiber'; import { OrbitControls, Float, Html } from '@react-three/drei'; // Simple 3D X Component function XMark({ position }) { const groupRef = useRef(); useFrame((state) => { if (groupRef.current) { groupRef.current.rotation.y = Math.sin(state.clock.elapsedTime) * 0.1; } }); return ( <group ref={groupRef} position={position}> <Float speed={2} rotationIntensity={0.5} floatIntensity={0.2}> <group> {/* First line of X */} <mesh rotation={[0, 0, Math.PI / 4]}> <boxGeometry args={[1.5, 0.2, 0.2]} /> <meshStandardMaterial color="#ff6b6b" /> </mesh> {/* Second line of X */} <mesh rotation={[0, 0, -Math.PI / 4]}> <boxGeometry args={[1.5, 0.2, 0.2]} /> <meshStandardMaterial color="#ff6b6b" /> </mesh> </group> </Float> </group> ); } // Simple 3D O Component using cylinders function OMark({ position }) { const groupRef = useRef(); useFrame((state) => { if (groupRef.current) { groupRef.current.rotation.x = Math.sin(state.clock.elapsedTime * 0.5) * 0.1; groupRef.current.rotation.z = Math.cos(state.clock.elapsedTime * 0.3) * 0.1; } }); return ( <group ref={groupRef} position={position}> <Float speed={1.5} rotationIntensity={0.3} floatIntensity={0.3}> <group scale={1}> {/* Create O using multiple small cylinders in a circle */} {Array.from({ length: 12 }, (_, i) => { const angle = (i / 12) * Math.PI * 2; const x = Math.cos(angle) * 0.7; const y = Math.sin(angle) * 0.7; return ( <mesh key={i} position={[x, y, 0]} rotation={[0, 0, angle]}> <cylinderGeometry args={[0.08, 0.08, 0.3]} /> <meshStandardMaterial color="#4ecdc4" /> </mesh> ); })} </group> </Float> </group> ); } // Simple cube for testing function TestCube() { const meshRef = useRef(); useFrame((state) => { if (meshRef.current) { meshRef.current.rotation.x = state.clock.elapsedTime; meshRef.current.rotation.y = state.clock.elapsedTime * 0.5; } }); return ( <mesh ref={meshRef} position={[4, 0, 0]}> <boxGeometry args={[1, 1, 1]} /> <meshStandardMaterial color="orange" /> </mesh> ); } // Game Board Component function GameBoard({ board, onCellClick, winningLine }) { const positions = [ [-2, 2, 0], [0, 2, 0], [2, 2, 0], [-2, 0, 0], [0, 0, 0], [2, 0, 0], [-2, -2, 0], [0, -2, 0], [2, -2, 0] ]; return ( <group> {/* Grid lines */} <group> {/* Vertical lines */} <mesh position={[-1, 0, -0.1]}> <boxGeometry args={[0.1, 6, 0.1]} /> <meshStandardMaterial color="#666" /> </mesh> <mesh position={[1, 0, -0.1]}> <boxGeometry args={[0.1, 6, 0.1]} /> <meshStandardMaterial color="#666" /> </mesh> {/* Horizontal lines */} <mesh position={[0, 1, -0.1]}> <boxGeometry args={[6, 0.1, 0.1]} /> <meshStandardMaterial color="#666" /> </mesh> <mesh position={[0, -1, -0.1]}> <boxGeometry args={[6, 0.1, 0.1]} /> <meshStandardMaterial color="#666" /> </mesh> </group> {/* Clickable cells */} {positions.map((position, index) => ( <mesh key={index} position={position} onClick={() => onCellClick(index)} onPointerOver={(e) => { if (!board[index]) { e.object.material.color.setHex(0x555555); document.body.style.cursor = 'pointer'; } }} onPointerOut={(e) => { e.object.material.color.setHex(0x333333); document.body.style.cursor = 'default'; }} > <planeGeometry args={[1.8, 1.8]} /> <meshStandardMaterial color="#333" transparent opacity={0.3} /> </mesh> ))} {/* Render X's and O's */} {board.map((cell, index) => { if (cell === 'X') { return <XMark key={`x-${index}`} position={positions[index]} />; } else if (cell === 'O') { return <OMark key={`o-${index}`} position={positions[index]} />; } return null; })} {/* Winning line highlight */} {winningLine && ( <group> {winningLine.map((index) => ( <mesh key={`highlight-${index}`} position={[...positions[index].slice(0, 2), 0.2]}> <planeGeometry args={[1.9, 1.9]} /> <meshStandardMaterial color="#ffd93d" transparent opacity={0.5} /> </mesh> ))} </group> )} </group> ); } // Main Game Component export default function TicTacToe3D() { const [board, setBoard] = useState(Array(9).fill(null)); const [isXNext, setIsXNext] = useState(true); const [winner, setWinner] = useState(null); const [winningLine, setWinningLine] = useState(null); const checkWinner = (squares) => { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], // rows [0, 3, 6], [1, 4, 7], [2, 5, 8], // columns [0, 4, 8], [2, 4, 6] // diagonals ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return { winner: squares[a], line: lines[i] }; } } return null; }; const handleCellClick = (index) => { if (board[index] || winner) return; const newBoard = [...board]; newBoard[index] = isXNext ? 'X' : 'O'; setBoard(newBoard); const result = checkWinner(newBoard); if (result) { setWinner(result.winner); setWinningLine(result.line); } else { setIsXNext(!isXNext); } }; const resetGame = () => { setBoard(Array(9).fill(null)); setIsXNext(true); setWinner(null); setWinningLine(null); }; const isDraw = !winner && board.every(cell => cell !== null); const Header = ({ }) => ( <div className="text-center py-6 text-white w-64 absolute top-2 left-2"> <h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-pink-400 to-purple-400 bg-clip-text text-transparent"> 3D Tic Tac Toe </h1> <div className="text-xl"> {winner ? ( <div className="text-yellow-300 font-semibold"> 🎉 Player {winner} Wins! 🎉 </div> ) : isDraw ? ( <div className="text-orange-300 font-semibold">It's a Draw!</div> ) : ( <div> Next Player: <span className={isXNext ? 'text-red-400' : 'text-teal-400'}> {isXNext ? 'X' : 'O'} </span> </div> )} </div> </div> ); return ( <div className="w-full h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900 flex flex-col"> <Header /> {/* 3D Game */} <div className="flex-1 relative"> <Canvas camera={{ position: [0, 0, 10], fov: 50 }} style={{ background: 'linear-gradient(135deg, #1a1a2e, #16213e)' }} > {/* Lighting */} <ambientLight intensity={0.6} /> <pointLight position={[10, 10, 10]} intensity={1} /> <pointLight position={[-10, -10, 5]} intensity={0.5} color="#4ecdc4" /> {/* Game board */} <GameBoard board={board} onCellClick={handleCellClick} winningLine={winningLine} /> {/* Camera controls */} <OrbitControls enablePan={false} enableZoom={true} maxPolarAngle={Math.PI / 2} minPolarAngle={Math.PI / 6} autoRotate={winner || isDraw} autoRotateSpeed={2} /> </Canvas> {/* Debug info */} <div className="absolute top-4 left-4 text-white text-sm bg-black bg-opacity-50 p-2 rounded"> <div>Canvas Status: Active</div> <div>Game State: {winner ? `Winner: ${winner}` : isDraw ? 'Draw' : 'Playing'}</div> <div>Next: {isXNext ? 'X' : 'O'}</div> </div> </div> {/* Controls */} <div className="text-center pb-6"> <button onClick={resetGame} className="px-8 py-3 bg-gradient-to-r from-pink-500 to-purple-600 text-white font-semibold rounded-lg shadow-lg hover:from-pink-600 hover:to-purple-700 transform hover:scale-105 transition-all duration-200" > New Game </button> <p className="text-gray-300 text-sm mt-3"> Click cells to play • Drag to rotate • Scroll to zoom </p> </div> </div> ); }
Comments (0)
Loading comments...