//@ts-check import ReactDOM from 'https://esm.sh/react-dom' import React from 'https://esm.sh/react' import * as RTF from 'https://esm.sh/@react-three/fiber' import { useGLTF, useAnimations, PerspectiveCamera } from 'https://esm.sh/@react-three/drei' import {html} from 'https://esm.sh/htm/react' /**@typedef {[x:number, y:number, z:number]} V3 */ /** * @typedef {{Type:"click", Payload:number}} ActionClick * @typedef {{Type:"round"}} ActionRound * @typedef {ActionClick | ActionRound} Action */ /** * @typedef {{Done:boolean, Pos:V3}} StateGoal */ /** * @typedef {{Round:number, Done:boolean, Goals:Array}} State */ /** @typedef {(inState:State, inAction:Action)=>void} Reducer */ /** @typedef {{Initial:State, Reducer:React.Reducer, Consume:()=>GameBinding}} Game */ /** @typedef {[State:State, Dispatcher:React.Dispatch]} GameBinding */ /** @type Game */ const Game = { Initial: { Round:0, Goals:[{Done:false, Pos:[0, 0, 0]}, {Done:true, Pos:[2, 0, 0]}], Done:false }, Reducer(inState, inAction) { /**@type {(inGoals:Array)=>boolean} */ const allDone =(inGoals)=> { for(let i=0; ivoid})=>JSX.Element}*/ function Model(props) { const { nodes, animations } = useGLTF("/bounce.gltf"); const Anim = useAnimations(animations); React.useEffect(()=> { const action = Anim.actions.CubeAction; props.goal.Done ? action.play() : action.stop(); }, [props.goal.Done]); return html` { e.stopPropagation(); props.handler(); } } /> ` } useGLTF.preload("/bounce.gltf"); function App() { const [State, Dispatch] = Game.Consume(); /** @type Array */ const children = State.Goals.map((g, i)=> { return React.createElement(Model, { goal:g, handler:()=>Dispatch({Type:"click", Payload:i}), key:i }); }); return html`
Round
${State.Round}
Done
${ State.Done ? "Done!" : "Not Done :("}
<${RTF.Canvas}> <${PerspectiveCamera} makeDefault fov=${100} position=${[0, 0, 10]} /> <${React.Suspense}> ${children}
`; } ReactDOM.render( React.createElement(App), document.getElementById('root') )