trigo / trigo-web /app /src /components /mcts /MCTSDataLoader.vue
k-l-lambda's picture
Update trigo-web with VS People multiplayer mode
15f353f
<template>
<div class="mcts-data-loader">
<div class="file-inputs">
<!-- TGN File Input -->
<div class="file-input-group">
<label for="tgn-file" class="file-label">
TGN Game File
<span class="file-status" :class="{ ready: tgnFile }">
{{ tgnFile ? `✓ ${tgnFile.name}` : "Not selected" }}
</span>
</label>
<input
id="tgn-file"
type="file"
accept=".tgn"
@change="onTGNFileChange"
class="file-input"
/>
</div>
<!-- Visit Counts JSON Input -->
<div class="file-input-group">
<label for="json-file" class="file-label">
Visit Counts JSON
<span class="file-status" :class="{ ready: jsonFile }">
{{ jsonFile ? `✓ ${jsonFile.name}` : "Not selected" }}
</span>
</label>
<input
id="json-file"
type="file"
accept=".json"
@change="onJSONFileChange"
class="file-input"
/>
</div>
</div>
<!-- Load Button -->
<button
@click="loadData"
:disabled="!canLoad || loading"
class="btn-load"
:class="{ loading }"
>
{{ loading ? "Loading..." : "Load Data" }}
</button>
<!-- Status Messages -->
<div v-if="loading" class="status-message loading">
<div class="spinner"></div>
<span>Parsing data...</span>
</div>
<div v-if="error" class="status-message error">
<strong>Error:</strong> {{ error }}
</div>
<div v-if="success" class="status-message success">
<strong>Success:</strong> Loaded {{ movesCount }} moves from a {{ boardShape }} board
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
import { useMCTSStore } from "@/stores/mctsStore";
import { parseMCTSData } from "@/utils/mctsDataParser";
const mctsStore = useMCTSStore();
// File state
const tgnFile = ref<File | null>(null);
const jsonFile = ref<File | null>(null);
// Loading state
const loading = ref(false);
const error = ref<string | null>(null);
const success = ref(false);
// Success data
const movesCount = ref(0);
const boardShape = ref("");
// Computed
const canLoad = computed(() => tgnFile.value !== null && jsonFile.value !== null);
// Handlers
function onTGNFileChange(event: Event) {
const target = event.target as HTMLInputElement;
tgnFile.value = target.files?.[0] || null;
clearMessages();
}
function onJSONFileChange(event: Event) {
const target = event.target as HTMLInputElement;
jsonFile.value = target.files?.[0] || null;
clearMessages();
}
function clearMessages() {
error.value = null;
success.value = false;
}
async function loadData() {
if (!canLoad.value) return;
loading.value = true;
clearMessages();
try {
// Read files
const tgnContent = await tgnFile.value!.text();
const jsonContent = await jsonFile.value!.text();
// Parse data
const data = await parseMCTSData(tgnContent, jsonContent);
// Store data
mctsStore.setGameHistory(data);
// Show success
success.value = true;
movesCount.value = data.length;
const shape = data[0].gameState.getShape();
boardShape.value = `${shape.x}×${shape.y}×${shape.z}`;
console.log(`[MCTS Loader] Successfully loaded ${data.length} moves`);
} catch (err) {
error.value = err instanceof Error ? err.message : String(err);
console.error("[MCTS Loader] Error loading data:", err);
} finally {
loading.value = false;
}
}
</script>
<style scoped lang="scss">
.mcts-data-loader {
display: flex;
flex-direction: column;
gap: 15px;
}
.file-inputs {
display: flex;
flex-direction: column;
gap: 12px;
}
.file-input-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.file-label {
font-size: 13px;
font-weight: 500;
color: #c0c0c0;
display: flex;
justify-content: space-between;
align-items: center;
}
.file-status {
font-size: 11px;
color: #808080;
font-weight: normal;
&.ready {
color: #4a90e2;
}
}
.file-input {
padding: 8px;
background-color: #1a1a1a;
border: 1px solid #404040;
border-radius: 4px;
color: #e0e0e0;
font-size: 13px;
cursor: pointer;
&:hover {
border-color: #4a90e2;
}
&:focus {
outline: none;
border-color: #4a90e2;
}
&::file-selector-button {
padding: 6px 12px;
background-color: #2a2a2a;
border: 1px solid #404040;
border-radius: 3px;
color: #e0e0e0;
font-size: 12px;
cursor: pointer;
margin-right: 10px;
&:hover {
background-color: #3a3a3a;
border-color: #4a90e2;
}
}
}
.btn-load {
padding: 12px 20px;
background-color: #4a90e2;
border: none;
border-radius: 4px;
color: #ffffff;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s, opacity 0.2s;
&:hover:not(:disabled) {
background-color: #357abd;
}
&:active:not(:disabled) {
background-color: #2a6ba8;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&.loading {
opacity: 0.7;
}
}
.status-message {
padding: 10px;
border-radius: 4px;
font-size: 13px;
display: flex;
align-items: center;
gap: 10px;
&.loading {
background-color: #2a3a4a;
color: #4a90e2;
}
&.error {
background-color: #4a2a2a;
color: #ff6b6b;
}
&.success {
background-color: #2a4a2a;
color: #6bff6b;
}
strong {
font-weight: 600;
}
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid #4a90e2;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>