@@ -3,12 +3,14 @@ import { Canvas, useFrame } from '@react-three/fiber';
33import { OrbitControls , Text as DreiText , Stars , Line } from '@react-three/drei' ;
44import { Button , Text , Card } from '@geist-ui/core' ;
55import { Link } from 'react-router-dom' ;
6+ import InfoPanel from '../components/InfoPanel.jsx' ;
67import * as THREE from 'three' ;
78import config from '../config.js' ;
89import { useResponsive } from '../hooks/useResponsive.js' ;
910import { EffectComposer , Bloom } from '@react-three/postprocessing' ;
1011
11- const WS_URL = `${ config . WS_BASE_URL } /ws/pirate-ship` ;
12+ const WS_URL = `${ config . WS_BASE_URL } /ws/kraken` ;
13+ const GRID_SIZE = 200 ;
1214
1315// Water surface component (adapted from example)
1416const WaterSurface = ( { waterSize } ) => {
@@ -71,14 +73,37 @@ const Kraken = ({ position, health }) => {
7173 ) ;
7274} ;
7375
74- // Tentacle component
75- const Tentacle = ( { position } ) => {
76- return (
77- < mesh position = { [ position [ 0 ] - GRID_SIZE / 2 , 1 , position [ 1 ] - GRID_SIZE / 2 ] } >
78- < cylinderGeometry args = { [ 0.5 , 0.5 , 3 ] } />
79- < meshStandardMaterial color = "darkgreen" />
80- </ mesh >
81- ) ;
76+ // Tentacle component - PURPLE tentacles emerging from water and reaching UP!
77+ const Tentacle = ( { start, end } ) => {
78+ // Safety check for undefined end position
79+ if ( ! end || ! Array . isArray ( end ) || end . length < 2 ) {
80+ return null ;
81+ }
82+
83+ // Start at water surface (y = 0)
84+ const startAdj = [ end [ 0 ] - GRID_SIZE / 2 , 0 , end [ 1 ] - GRID_SIZE / 2 ] ;
85+ // End point high in the air (y = 15)
86+ const endAdj = [ end [ 0 ] - GRID_SIZE / 2 , 15 , end [ 1 ] - GRID_SIZE / 2 ] ;
87+ // Mid-point for nice curve
88+ const midRef = useRef ( [ startAdj [ 0 ] , 8 , startAdj [ 2 ] ] ) ;
89+ const lineRef = useRef ( ) ;
90+
91+ useFrame ( ( { clock } ) => {
92+ const time = clock . getElapsedTime ( ) ;
93+ const wave = Math . sin ( time * 2 + ( start [ 0 ] + start [ 1 ] ) ) * 2 ;
94+ const sway = Math . cos ( time * 1.5 + ( start [ 0 ] + start [ 1 ] ) ) * 1.5 ;
95+ // Animate mid-point for writhing effect
96+ midRef . current [ 1 ] = 8 + wave ;
97+ midRef . current [ 0 ] = startAdj [ 0 ] + sway ;
98+ midRef . current [ 2 ] = startAdj [ 2 ] + sway * 0.7 ;
99+ if ( lineRef . current ) {
100+ lineRef . current . points = [ startAdj , midRef . current , endAdj ] ;
101+ lineRef . current . geometry . setPositions ( lineRef . current . points . flat ( ) ) ;
102+ }
103+ } ) ;
104+
105+ const points = [ startAdj , midRef . current , endAdj ] ;
106+ return < Line ref = { lineRef } points = { points } color = "purple" lineWidth = { 5 } /> ;
82107} ;
83108
84109const StatusPanel = ( { ships, kraken } ) => {
@@ -91,26 +116,63 @@ const StatusPanel = ({ ships, kraken }) => {
91116 ) ;
92117} ;
93118
94- export default function PirateShip ( ) {
95- const [ state , setState ] = useState ( null ) ;
119+ export default function KrakenGame ( ) {
120+ const initialState = {
121+ ships : Array ( 4 ) . fill ( ) . map ( ( ) => ( { pos : [ Math . random ( ) * 200 , Math . random ( ) * 200 ] , health : 100 } ) ) ,
122+ kraken : { pos : [ 100 , 100 ] , health : 500 } ,
123+ tentacles : Array ( 6 ) . fill ( ) . map ( ( ) => [ Math . random ( ) * 200 , Math . random ( ) * 200 ] ) ,
124+ grid_size : 200
125+ } ;
126+ const [ state , setState ] = useState ( initialState ) ;
96127 const [ running , setRunning ] = useState ( false ) ;
97128 const [ training , setTraining ] = useState ( false ) ;
98129 const [ trained , setTrained ] = useState ( false ) ;
130+ const [ logs , setLogs ] = useState ( [ ] ) ;
131+ const [ chartState , setChartState ] = useState ( { labels : [ ] , rewards : [ ] , losses : [ ] } ) ;
99132 const wsRef = useRef ( null ) ;
100133 const { isMobile } = useResponsive ( ) ;
101134
135+ const addLog = ( txt ) => {
136+ setLogs ( ( l ) => {
137+ const upd = [ ...l , txt ] ;
138+ return upd . length > 200 ? upd . slice ( upd . length - 200 ) : upd ;
139+ } ) ;
140+ } ;
141+
142+ const resetTraining = ( ) => {
143+ setTraining ( false ) ;
144+ setTrained ( false ) ;
145+ setChartState ( { labels : [ ] , rewards : [ ] , losses : [ ] } ) ;
146+ setState ( initialState ) ;
147+ addLog ( 'Training has been reset.' ) ;
148+ } ;
149+
102150 useEffect ( ( ) => {
103151 const ws = new WebSocket ( WS_URL ) ;
104152 wsRef . current = ws ;
153+ ws . onopen = ( ) => addLog ( 'WS opened' ) ;
105154 ws . onmessage = ( ev ) => {
106- const parsed = JSON . parse ( ev . data ) ;
155+ addLog ( ev . data ) ;
156+ let parsed ;
157+ try {
158+ parsed = JSON . parse ( ev . data ) ;
159+ } catch {
160+ return ;
161+ }
107162 if ( parsed . type === 'train_step' || parsed . type === 'run_step' ) {
108163 setState ( parsed . state ) ;
164+ } else if ( parsed . type === 'progress' ) {
165+ setChartState ( ( prev ) => ( {
166+ labels : [ ...prev . labels , parsed . episode ] ,
167+ rewards : [ ...prev . rewards , parsed . reward ] ,
168+ losses : [ ...prev . losses , parsed . loss ?? null ] ,
169+ } ) ) ;
109170 } else if ( parsed . type === 'trained' ) {
110171 setTraining ( false ) ;
111172 setTrained ( true ) ;
112173 }
113174 } ;
175+ ws . onclose = ( ) => addLog ( 'WS closed' ) ;
114176 return ( ) => ws . close ( ) ;
115177 } , [ ] ) ;
116178
@@ -121,11 +183,14 @@ export default function PirateShip() {
121183 } ;
122184
123185 const startTraining = ( ) => {
186+ if ( training || trained ) return ;
124187 setTraining ( true ) ;
188+ addLog ( 'Starting training...' ) ;
125189 send ( { cmd : 'train' } ) ;
126190 } ;
127191
128192 const startRun = ( ) => {
193+ if ( ! trained ) return ;
129194 setRunning ( true ) ;
130195 send ( { cmd : 'run' } ) ;
131196 } ;
@@ -146,7 +211,7 @@ export default function PirateShip() {
146211 { state && < Kraken position = { state . kraken . pos } health = { state . kraken . health } /> }
147212
148213 { state && state . tentacles . map ( ( tentacle , i ) => (
149- < Tentacle key = { i } position = { tentacle } />
214+ < Tentacle key = { i } start = { state . kraken . pos } end = { tentacle } />
150215 ) ) }
151216
152217 < EffectComposer >
@@ -160,7 +225,9 @@ export default function PirateShip() {
160225 < Text h1 > Pirate Ship vs Kraken</ Text >
161226 < Button type = "secondary" disabled = { training || trained } onClick = { startTraining } > Train</ Button >
162227 < Button type = "success" disabled = { ! trained || running } onClick = { startRun } > Run</ Button >
228+ { trained && < Button type = "error" onClick = { resetTraining } > Reset</ Button > }
163229 </ div >
230+ < InfoPanel logs = { logs } chartState = { chartState } />
164231
165232 { state && (
166233 < div style = { { position : 'absolute' , top : 10 , right : 10 , zIndex : 1 } } >
0 commit comments