diff --git a/frontend/src/pages/HumanFightPage.vue b/frontend/src/pages/HumanFightPage.vue index 0a1f6d3..388b016 100644 --- a/frontend/src/pages/HumanFightPage.vue +++ b/frontend/src/pages/HumanFightPage.vue @@ -22,12 +22,17 @@ const currentChallenge = ref<{ timeoutMs: number remainingMs: number scoring: string + choices: string[] } | null>(null) const answer = ref('') const trashTalk = ref('') const remainingSeconds = ref(45) const showTrashTalk = ref(false) +// Feedback state +const feedback = ref<'correct' | 'wrong' | null>(null) +const feedbackTimer = ref | null>(null) + // Round results tracking const roundResults = ref { }) const amSideA = computed(() => fight.value?.botAId === myBot.value?.id) +const timerColor = computed(() => { + if (remainingSeconds.value <= 3) return 'text-ko' + if (remainingSeconds.value <= 10) return 'text-neon-yellow' + return 'text-neon-green' +}) + +const timerShake = computed(() => remainingSeconds.value <= 3 && remainingSeconds.value > 0) + +const hasChoices = computed(() => + currentChallenge.value?.choices && currentChallenge.value.choices.length > 0 +) + onMounted(() => { if (!myBot.value) { router.push('/join') @@ -62,6 +79,7 @@ onMounted(() => { onUnmounted(() => { stopPolling() stopTimer() + if (feedbackTimer.value) clearTimeout(feedbackTimer.value) }) function startPolling() { @@ -82,6 +100,7 @@ function startTimer(remainingMs: number) { stopTimer() if (phase.value === 'challenge') { phase.value = 'submitted' + showFeedback('wrong') } } }, 1000) @@ -91,6 +110,12 @@ function stopTimer() { if (timerHandle) { clearInterval(timerHandle); timerHandle = null } } +function showFeedback(type: 'correct' | 'wrong') { + feedback.value = type + if (feedbackTimer.value) clearTimeout(feedbackTimer.value) + feedbackTimer.value = setTimeout(() => { feedback.value = null }, 1500) +} + async function pollForChallenge() { if (!myBotId.value || phase.value === 'finished' || phase.value === 'replay' || phase.value === 'error') return @@ -106,10 +131,11 @@ async function pollForChallenge() { answer.value = '' trashTalk.value = '' showTrashTalk.value = false + feedback.value = null phase.value = 'challenge' startTimer(data.remainingMs) await nextTick() - answerInput.value?.focus() + if (!hasChoices.value) answerInput.value?.focus() } } else if (data.fightStatus === 'finished') { stopPolling() @@ -117,7 +143,6 @@ async function pollForChallenge() { await loadFight() phase.value = 'finished' } else if (phase.value === 'submitted') { - // Between rounds — waiting for next challenge or fight end phase.value = 'between' } } catch { @@ -125,6 +150,11 @@ async function pollForChallenge() { } } +function selectChoice(choice: string) { + answer.value = choice + submitAnswer() +} + async function submitAnswer() { if (!currentChallenge.value || phase.value !== 'challenge') return if (!answer.value.trim()) return @@ -142,7 +172,13 @@ async function submitAnswer() { }), }) - if (!res.ok) { + if (res.ok) { + const data = await res.json() + // Show feedback if server tells us if answer was right + if (data.correct !== undefined) { + showFeedback(data.correct ? 'correct' : 'wrong') + } + } else { const data = await res.json() error.value = data.error || 'Failed to submit.' } @@ -162,9 +198,7 @@ async function loadFight() { opponentName.value = enemy?.name || 'Unknown' } if (data.rounds) { - let hpA = 200, hpB = 200 roundResults.value = data.rounds.map((r: any) => { - // Approximate HP from scores — actual HP tracked in fight record return { round: r.roundNumber, won: r.winnerId === myBotId.value, hpA: 0, hpB: 0 } }) myHp.value = amSideA.value ? data.botAHp : data.botBHp @@ -193,6 +227,22 @@ function goToArena() {
+ + +
+ + {{ feedback === 'correct' ? 'CORRECT' : 'WRONG' }} + +
+
+
@@ -209,14 +259,27 @@ function goToArena() { ROUND {{ currentChallenge.roundNumber }} {{ remainingSeconds }}s
-
+ +
+
+
+ +
{{ currentChallenge.label }} @@ -230,49 +293,69 @@ function goToArena() {

-