StavanKhobare's picture
Update documentation, add blog, and simplify inference script
312c390
// ── Game Store (useReducer) ───────────────────────────────────────
// Manages all game state consumed by the UI.
import { useReducer, useCallback } from 'react'
import { apiReset, apiStep } from '../services/api.js'
// ── Initial State ─────────────────────────────────────────────────
const initialState = {
obs: null, // current BoardSimObservation
prevObs: null, // previous obs for delta rendering
done: false,
loading: false,
error: null,
lastReward: null,
lastInfo: null,
rewardTrace: [], // per-step reward history this episode (drives RewardTrace panel)
cumReward: 0, // running total reward this episode
speed: 1.5, // playback speed multiplier
paused: true, // start paused, user clicks Run
seed: 42,
}
// ── Reducer ───────────────────────────────────────────────────────
function reducer(state, action) {
switch (action.type) {
case 'RESET_START':
return { ...initialState, speed: state.speed, loading: true, paused: true }
case 'RESET_SUCCESS':
return {
...state,
loading: false,
obs: action.payload.observation,
prevObs: null,
done: action.payload.done,
lastReward: null,
lastInfo: action.payload.info,
rewardTrace: [],
cumReward: 0,
error: null,
}
case 'STEP_START':
return { ...state, loading: true }
case 'STEP_SUCCESS': {
const r = Number(action.payload.reward ?? 0)
return {
...state,
loading: false,
prevObs: state.obs,
obs: action.payload.observation,
done: action.payload.done,
lastReward: action.payload.reward,
lastInfo: action.payload.info,
rewardTrace: [...state.rewardTrace, r],
cumReward: state.cumReward + r,
error: null,
}
}
case 'SET_SPEED':
return { ...state, speed: action.payload }
case 'TOGGLE_PAUSE':
return { ...state, paused: !state.paused }
case 'SET_PAUSED':
return { ...state, paused: action.payload }
case 'ERROR':
return { ...state, loading: false, error: action.payload, paused: true }
default:
return state
}
}
// ── Hook ──────────────────────────────────────────────────────────
export function useGameStore() {
const [state, dispatch] = useReducer(reducer, initialState)
const resetGame = useCallback(async (seed = 42) => {
dispatch({ type: 'RESET_START' })
try {
const data = await apiReset(seed)
dispatch({ type: 'RESET_SUCCESS', payload: data })
} catch (err) {
dispatch({ type: 'ERROR', payload: err.message })
}
}, [])
const stepGame = useCallback(async (decision, pitch = '') => {
dispatch({ type: 'STEP_START' })
try {
const data = await apiStep(decision, pitch)
dispatch({ type: 'STEP_SUCCESS', payload: data })
if (data.done) dispatch({ type: 'SET_PAUSED', payload: true })
return data
} catch (err) {
dispatch({ type: 'ERROR', payload: err.message })
return null
}
}, [])
const setSpeed = useCallback((v) => dispatch({ type: 'SET_SPEED', payload: v }), [])
const togglePause = useCallback(() => dispatch({ type: 'TOGGLE_PAUSE' }), [])
const setPaused = useCallback((v) => dispatch({ type: 'SET_PAUSED', payload: v }), [])
return { state, resetGame, stepGame, setSpeed, togglePause, setPaused }
}