115 lines
3.7 KiB
Vue
115 lines
3.7 KiB
Vue
<script setup lang="ts">
|
|||
|
|
import { ref, computed, onMounted } from 'vue'
|
||
|
|
import { useRouter } from 'vue-router'
|
||
|
|
import { useNostr } from '../composables/useNostr'
|
||
|
|
|
||
|
|
const router = useRouter()
|
||
|
|
const { bot: myBot, isLoggedIn } = useNostr()
|
||
|
|
|
||
|
|
const selectedTier = ref(0)
|
||
|
|
const isStarting = ref(false)
|
||
|
|
const error = ref('')
|
||
|
|
|
||
|
|
const tiers = [
|
||
|
|
{ value: 0, label: 'ROOKIE', desc: 'Easy challenges, forgiving scoring' },
|
||
|
|
{ value: 1, label: 'BRONZE', desc: 'Moderate difficulty' },
|
||
|
|
{ value: 2, label: 'SILVER', desc: 'Harder prompts, faster pace' },
|
||
|
|
{ value: 3, label: 'GOLD', desc: 'Tough challenges, strict scoring' },
|
||
|
|
{ value: 4, label: 'DIAMOND', desc: 'Expert difficulty' },
|
||
|
|
{ value: 5, label: 'LEGEND', desc: 'Maximum difficulty' },
|
||
|
|
]
|
||
|
|
|
||
|
|
const tierColor = computed(() => {
|
||
|
|
const colors = ['text-text-muted', 'text-amber-600', 'text-gray-300', 'text-neon-gold', 'text-neon-cyan', 'text-neon-pink']
|
||
|
|
return colors[selectedTier.value] || 'text-text-muted'
|
||
|
|
})
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
if (!isLoggedIn.value || !myBot.value) {
|
||
|
|
router.push('/register')
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
async function startPractice() {
|
||
|
|
if (!myBot.value || isStarting.value) return
|
||
|
|
isStarting.value = true
|
||
|
|
error.value = ''
|
||
|
|
|
||
|
|
try {
|
||
|
|
const res = await fetch(`/api/fights/practice/${myBot.value.id}`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
})
|
||
|
|
|
||
|
|
if (!res.ok) {
|
||
|
|
const data = await res.json()
|
||
|
|
error.value = data.error || 'Failed to start practice fight'
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const data = await res.json()
|
||
|
|
// Navigate to human fight page for the practice fight
|
||
|
|
if (myBot.value.isHuman) {
|
||
|
|
router.push(`/play/${data.fightId}`)
|
||
|
|
} else {
|
||
|
|
router.push(`/arena/${data.fightId}`)
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
error.value = 'Network error'
|
||
|
|
} finally {
|
||
|
|
isStarting.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div class="max-w-lg mx-auto px-4 py-8 space-y-6">
|
||
|
|
<h1 class="font-display font-black text-2xl text-neon-cyan tracking-wider">PRACTICE</h1>
|
||
|
|
<p class="font-mono text-sm text-text-muted">
|
||
|
|
Fight against training bots. No sats at stake.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<!-- Tier selection -->
|
||
|
|
<div class="space-y-2">
|
||
|
|
<h2 class="font-display font-bold text-sm text-text-secondary tracking-wider">DIFFICULTY</h2>
|
||
|
|
<div class="grid grid-cols-2 gap-2">
|
||
|
|
<button
|
||
|
|
v-for="tier in tiers"
|
||
|
|
:key="tier.value"
|
||
|
|
class="p-3 border-2 rounded text-left transition-all"
|
||
|
|
:class="selectedTier === tier.value
|
||
|
|
? 'border-neon-cyan bg-neon-cyan/5'
|
||
|
|
: 'border-border hover:border-border/80'"
|
||
|
|
@click="selectedTier = tier.value"
|
||
|
|
>
|
||
|
|
<span class="font-display font-bold text-sm tracking-wider" :class="tierColor">
|
||
|
|
T{{ tier.value }}
|
||
|
|
</span>
|
||
|
|
<span class="font-display font-bold text-xs text-text-primary ml-2">{{ tier.label }}</span>
|
||
|
|
<p class="font-mono text-[10px] text-text-muted mt-0.5">{{ tier.desc }}</p>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Start button -->
|
||
|
|
<button
|
||
|
|
class="w-full min-h-[48px] py-4 bg-neon-green/10 border-2 border-neon-green text-neon-green
|
||
|
|
font-display font-black text-lg tracking-[0.15em] rounded
|
||
|
|
hover:bg-neon-green/20 transition-all
|
||
|
|
disabled:opacity-30 disabled:cursor-not-allowed"
|
||
|
|
:disabled="isStarting"
|
||
|
|
@click="startPractice"
|
||
|
|
>
|
||
|
|
{{ isStarting ? 'STARTING...' : 'START PRACTICE' }}
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<div v-if="error" class="p-3 border-2 border-ko/30 bg-ko/5 text-center rounded">
|
||
|
|
<p class="font-mono text-xs text-ko">{{ error }}</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<p class="font-mono text-[10px] text-text-muted text-center">
|
||
|
|
Practice fights use dampened Elo (minimal rating impact)
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</template>
|