trigo / trigo-web /app /src /components /mcts /MCTSStatisticsPanel.vue
k-l-lambda's picture
Update trigo-web with VS People multiplayer mode
15f353f
<template>
<div class="mcts-statistics-panel">
<div v-if="!hasData" class="no-data">No data</div>
<div v-else class="stats-content">
<div class="stats-summary">
<div class="stat-item">
<span class="stat-label">Total visits:</span>
<span class="stat-value">{{ totalVisits }}</span>
</div>
</div>
<div class="stats-options">
<label class="option-label">Display:</label>
<div class="radio-group">
<label class="radio-option">
<input
type="radio"
value="N"
:checked="selectedStat === 'N'"
@change="onStatChange"
/>
<span>Visit Count (N)</span>
</label>
<label class="radio-option">
<input
type="radio"
value="pi"
:checked="selectedStat === 'pi'"
@change="onStatChange"
/>
<span>Policy (π)</span>
</label>
</div>
</div>
<div class="top-moves-table">
<table>
<thead>
<tr>
<th>#</th>
<th>Pos</th>
<th>N</th>
<th>%</th>
<th>π</th>
</tr>
</thead>
<tbody>
<tr
v-for="(move, idx) in topMoves"
:key="move.actionKey"
:class="{ selected: isSelected(move.actionKey) }"
@click="onMoveClick(move.actionKey)"
class="move-row"
>
<td>{{ idx + 1 }}</td>
<td>{{ formatPosition(move.position) }}</td>
<td>{{ move.N }}</td>
<td>{{ ((move.N / totalVisits) * 100).toFixed(1) }}%</td>
<td>{{ move.pi.toFixed(3) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { useMCTSStore } from "@/stores/mctsStore";
import { formatPosition } from "@/utils/mctsDataParser";
import type { MCTSStatisticType } from "@/types/mcts";
const mctsStore = useMCTSStore();
const hasData = computed(() => mctsStore.hasData);
const totalVisits = computed(() => mctsStore.totalVisits);
const topMoves = computed(() => mctsStore.topMoves);
const selectedStat = computed(() => mctsStore.selectedStatistic);
function isSelected(actionKey: string): boolean {
return mctsStore.selectedActionKey === actionKey;
}
function onMoveClick(actionKey: string) {
mctsStore.selectAction(actionKey);
}
function onStatChange(event: Event) {
const target = event.target as HTMLInputElement;
mctsStore.setSelectedStatistic(target.value as MCTSStatisticType);
}
</script>
<style scoped lang="scss">
.mcts-statistics-panel {
display: flex;
flex-direction: column;
}
.no-data {
text-align: center;
color: #606060;
font-size: 12px;
padding: 20px;
}
.stats-content {
display: flex;
flex-direction: column;
gap: 15px;
}
.stats-summary {
padding: 10px;
background-color: #1a1a1a;
border-radius: 4px;
}
.stat-item {
display: flex;
justify-content: space-between;
font-size: 13px;
}
.stat-label {
color: #a0a0a0;
}
.stat-value {
color: #e0e0e0;
font-weight: 500;
}
.stats-options {
display: flex;
flex-direction: column;
gap: 8px;
}
.option-label {
font-size: 12px;
color: #a0a0a0;
font-weight: 500;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.radio-option {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: #c0c0c0;
cursor: pointer;
input[type="radio"] {
cursor: pointer;
}
}
.top-moves-table {
max-height: 300px;
table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
thead {
position: sticky;
top: 0;
background-color: #2a2a2a;
z-index: 1;
th {
padding: 8px 6px;
text-align: left;
color: #4a90e2;
font-weight: 600;
border-bottom: 1px solid #404040;
}
}
tbody {
tr {
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: #3a3a3a;
}
&.selected {
background-color: #4a3030;
td {
color: #e94560;
font-weight: 500;
}
}
td {
padding: 8px 6px;
color: #c0c0c0;
border-bottom: 1px solid #303030;
&:first-child {
color: #808080;
}
}
}
}
}
}
</style>