feat: Initialize Turtle Control Center with React and WebSocket server

- Added index.html for the main entry point of the client application.
- Created package.json for client dependencies and scripts.
- Implemented App component with view controls and layout.
- Added CSS styles for the application and components.
- Developed ControlPanel component for turtle management and commands.
- Created Map3D component for 3D visualization of turtles.
- Established Zustand store for state management of turtles and WebSocket connection.
- Set up server with Express and WebSocket for handling turtle updates and commands.
- Added REST API endpoints for turtle status updates and command handling.
- Implemented start.sh script for setting up and running the application.
This commit is contained in:
MayaTheShy
2026-02-15 23:49:46 -05:00
parent dc6a9a94b7
commit 29dfde9f25
14 changed files with 1398 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
import React, { useRef, useMemo } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, Grid, Text, Line } from '@react-three/drei';
import * as THREE from 'three';
import { useTurtleStore } from '../store/turtleStore';
// Turtle marker component
function TurtleMarker({ turtle, isSelected, onClick }) {
const meshRef = useRef();
const { position, mode } = turtle;
useFrame((state) => {
if (meshRef.current && isSelected) {
meshRef.current.rotation.y += 0.02;
}
});
if (!position) return null;
const color = mode === 'mining' ? '#4ade80' :
mode === 'exploring' ? '#60a5fa' :
mode === 'returning' ? '#f59e0b' :
'#9ca3af';
return (
<group position={[position.x, position.y, position.z]} onClick={onClick}>
{/* Turtle body */}
<mesh ref={meshRef}>
<boxGeometry args={[0.8, 0.6, 0.8]} />
<meshStandardMaterial
color={color}
emissive={color}
emissiveIntensity={isSelected ? 0.5 : 0.2}
/>
</mesh>
{/* Selection indicator */}
{isSelected && (
<>
<mesh position={[0, 1, 0]}>
<coneGeometry args={[0.3, 0.5, 4]} />
<meshStandardMaterial color="#ffffff" emissive="#ffffff" emissiveIntensity={0.8} />
</mesh>
{/* Selection ring */}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.4, 0]}>
<ringGeometry args={[1, 1.2, 32]} />
<meshBasicMaterial color="#ffffff" side={THREE.DoubleSide} transparent opacity={0.5} />
</mesh>
</>
)}
{/* Turtle ID label */}
<Text
position={[0, 1.5, 0]}
fontSize={0.4}
color="white"
anchorX="center"
anchorY="middle"
>
T-{turtle.turtleID}
</Text>
</group>
);
}
// Path trail component
function PathTrail({ turtle }) {
const { position, homePosition } = turtle;
if (!position || !homePosition) return null;
const points = [
new THREE.Vector3(position.x, position.y, position.z),
new THREE.Vector3(homePosition.x, homePosition.y, homePosition.z)
];
return (
<Line
points={points}
color="#60a5fa"
lineWidth={2}
dashed
dashScale={2}
transparent
opacity={0.5}
/>
);
}
// Home marker component
function HomeMarker({ position }) {
if (!position) return null;
return (
<group position={[position.x, position.y, position.z]}>
<mesh>
<cylinderGeometry args={[1, 0.5, 0.5, 6]} />
<meshStandardMaterial color="#10b981" emissive="#10b981" emissiveIntensity={0.3} />
</mesh>
<Text
position={[0, 1.2, 0]}
fontSize={0.5}
color="#10b981"
anchorX="center"
anchorY="middle"
>
HOME
</Text>
</group>
);
}
// Main scene component
function Scene() {
const turtles = useTurtleStore((state) => state.getTurtleArray());
const selectedTurtleId = useTurtleStore((state) => state.selectedTurtleId);
const selectTurtle = useTurtleStore((state) => state.selectTurtle);
// Calculate center point for camera focus
const centerPoint = useMemo(() => {
if (turtles.length === 0) return [0, 0, 0];
let sumX = 0, sumY = 0, sumZ = 0;
let count = 0;
turtles.forEach(turtle => {
if (turtle.position) {
sumX += turtle.position.x;
sumY += turtle.position.y;
sumZ += turtle.position.z;
count++;
}
});
if (count === 0) return [0, 0, 0];
return [sumX / count, sumY / count, sumZ / count];
}, [turtles]);
// Get home position from first turtle
const homePosition = turtles.find(t => t.homePosition)?.homePosition;
return (
<>
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} intensity={1} />
<pointLight position={[-10, -10, -10]} intensity={0.5} />
{/* Grid */}
<Grid
args={[100, 100]}
cellSize={1}
cellThickness={0.5}
cellColor="#1e293b"
sectionSize={5}
sectionThickness={1}
sectionColor="#334155"
fadeDistance={50}
fadeStrength={1}
followCamera={false}
infiniteGrid
/>
{/* Home marker */}
{homePosition && <HomeMarker position={homePosition} />}
{/* Turtles and paths */}
{turtles.map((turtle) => (
<React.Fragment key={turtle.turtleID}>
<PathTrail turtle={turtle} />
<TurtleMarker
turtle={turtle}
isSelected={selectedTurtleId === turtle.turtleID}
onClick={() => selectTurtle(turtle.turtleID)}
/>
</React.Fragment>
))}
{/* Camera controls */}
<OrbitControls
target={centerPoint}
enableDamping
dampingFactor={0.05}
minDistance={5}
maxDistance={100}
/>
</>
);
}
// Main Map3D component
export default function Map3D() {
return (
<div style={{ width: '100%', height: '100%', background: '#0a0e1a' }}>
<Canvas
camera={{ position: [15, 15, 15], fov: 60 }}
style={{ background: '#0a0e1a' }}
>
<Scene />
</Canvas>
</div>
);
}