/** * Pinia store for MCTS visualization state */ import { defineStore } from "pinia"; import { ref, computed } from "vue"; import type { MCTSMoveData, MCTSMoveStatistic, MCTSStatisticType, ColorScaleType } from "@/types/mcts"; import type { Position } from "../../../inc/trigo/types"; export const useMCTSStore = defineStore("mcts", () => { // ============================================================================ // State // ============================================================================ // Game data const gameHistory = ref([]); const currentMoveIndex = ref(0); // Visualization settings const selectedStatistic = ref("N"); const colorScale = ref("linear"); const topNFilter = ref(10); // Show top N moves in tree/table // Selection state const selectedActionKey = ref(null); // ============================================================================ // Getters // ============================================================================ const hasData = computed(() => gameHistory.value.length > 0); const currentMoveData = computed((): MCTSMoveData | null => { if (!hasData.value) return null; return gameHistory.value[currentMoveIndex.value] || null; }); const maxMoveIndex = computed(() => { return Math.max(0, gameHistory.value.length - 1); }); const currentStatistics = computed((): MCTSMoveStatistic[] => { return currentMoveData.value?.statistics || []; }); const topMoves = computed((): MCTSMoveStatistic[] => { const stats = currentStatistics.value; return stats .slice() .sort((a, b) => b.N - a.N) .slice(0, topNFilter.value); }); const totalVisits = computed((): number => { return currentStatistics.value.reduce((sum, stat) => sum + stat.N, 0); }); const selectedMove = computed((): MCTSMoveStatistic | null => { if (!selectedActionKey.value) return null; return ( currentStatistics.value.find( (stat) => stat.actionKey === selectedActionKey.value ) || null ); }); // ============================================================================ // Actions // ============================================================================ function setGameHistory(history: MCTSMoveData[]) { gameHistory.value = history; currentMoveIndex.value = 0; selectedActionKey.value = null; } function clearGameHistory() { gameHistory.value = []; currentMoveIndex.value = 0; selectedActionKey.value = null; } function setCurrentMoveIndex(index: number) { if (index < 0 || index > maxMoveIndex.value) return; currentMoveIndex.value = index; // Clear selection when moving to different move selectedActionKey.value = null; } function nextMove() { if (currentMoveIndex.value < maxMoveIndex.value) { setCurrentMoveIndex(currentMoveIndex.value + 1); } } function prevMove() { if (currentMoveIndex.value > 0) { setCurrentMoveIndex(currentMoveIndex.value - 1); } } function firstMove() { setCurrentMoveIndex(0); } function lastMove() { setCurrentMoveIndex(maxMoveIndex.value); } function setSelectedStatistic(stat: MCTSStatisticType) { selectedStatistic.value = stat; } function setColorScale(scale: ColorScaleType) { colorScale.value = scale; } function setTopNFilter(n: number) { topNFilter.value = Math.max(3, Math.min(50, n)); } function selectAction(actionKey: string | null) { selectedActionKey.value = actionKey; } function selectPosition(position: Position | null) { if (!position) { selectedActionKey.value = "pass"; return; } selectedActionKey.value = `${position.x},${position.y},${position.z}`; } // ============================================================================ // Return // ============================================================================ return { // State gameHistory, currentMoveIndex, selectedStatistic, colorScale, topNFilter, selectedActionKey, // Getters hasData, currentMoveData, maxMoveIndex, currentStatistics, topMoves, totalVisits, selectedMove, // Actions setGameHistory, clearGameHistory, setCurrentMoveIndex, nextMove, prevMove, firstMove, lastMove, setSelectedStatistic, setColorScale, setTopNFilter, selectAction, selectPosition }; });