strat-gameplay-webapp/frontend-sba/components/Decisions/DefensiveSetup.vue
Cal Corum 197d91edfb CLAUDE: Remove defensive alignment field completely
Removed the unused alignment field from DefensiveDecision model and all
related code across backend and frontend.

Backend changes:
- models/game_models.py: Removed alignment field and validator
- terminal_client/display.py: Removed alignment from display
- core/ai_opponent.py: Updated log message
- tests/unit/models/test_game_models.py: Removed alignment tests
- tests/unit/core/test_validators.py: Removed alignment validation test

Frontend changes:
- types/game.ts: Removed alignment from DefensiveDecision interface
- components/Decisions/DefensiveSetup.vue:
  * Removed alignment section from template
  * Removed alignment from localSetup initialization
  * Removed alignmentOptions array
  * Removed alignmentDisplay computed property
  * Removed alignment from hasChanges comparison
  * Removed alignment from visual preview (reorganized to col-span-2)

Rationale: Defensive alignment is not active in the game and will not be
used. Per Cal's decision, remove completely rather than keep as dead code.

Tests: All 728 backend unit tests passing (100%)

Session 1 Part 3 - Change #6 complete
Part of cleanup work from demo review

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 13:02:22 -06:00

229 lines
7.1 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
<span class="text-2xl">🛡</span>
Defensive Setup
</h3>
<span
v-if="!isActive"
class="px-3 py-1 text-xs font-semibold rounded-full bg-gray-200 text-gray-600"
>
Opponent's Turn
</span>
</div>
<!-- Form -->
<form @submit.prevent="handleSubmit" class="space-y-6">
<!-- Infield Depth -->
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">
Infield Depth
</label>
<ButtonGroup
v-model="localSetup.infield_depth"
:options="infieldDepthOptions"
:disabled="!isActive"
size="md"
variant="primary"
vertical
/>
</div>
<!-- Outfield Depth -->
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">
Outfield Depth
</label>
<ButtonGroup
v-model="localSetup.outfield_depth"
:options="outfieldDepthOptions"
:disabled="!isActive"
size="md"
variant="primary"
/>
</div>
<!-- Hold Runners -->
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">
Hold Runners
</label>
<div class="space-y-2">
<ToggleSwitch
v-model="holdFirst"
label="Hold runner at 1st base"
:disabled="!isActive"
size="md"
/>
<ToggleSwitch
v-model="holdSecond"
label="Hold runner at 2nd base"
:disabled="!isActive"
size="md"
/>
<ToggleSwitch
v-model="holdThird"
label="Hold runner at 3rd base"
:disabled="!isActive"
size="md"
/>
</div>
</div>
<!-- Visual Preview -->
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-700 dark:to-gray-600 rounded-lg p-4">
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">
Current Setup
</h4>
<div class="grid grid-cols-2 gap-2 text-xs">
<div>
<span class="font-medium text-gray-600 dark:text-gray-400">Infield:</span>
<span class="ml-1 text-gray-900 dark:text-white">{{ infieldDisplay }}</span>
</div>
<div>
<span class="font-medium text-gray-600 dark:text-gray-400">Outfield:</span>
<span class="ml-1 text-gray-900 dark:text-white">{{ outfieldDisplay }}</span>
</div>
<div class="col-span-2">
<span class="font-medium text-gray-600 dark:text-gray-400">Holding:</span>
<span class="ml-1 text-gray-900 dark:text-white">{{ holdingDisplay }}</span>
</div>
</div>
</div>
<!-- Submit Button -->
<ActionButton
type="submit"
variant="success"
size="lg"
:disabled="!isActive || !hasChanges"
:loading="submitting"
full-width
>
{{ submitButtonText }}
</ActionButton>
</form>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import type { DefensiveDecision } from '~/types/game'
import ButtonGroup from '~/components/UI/ButtonGroup.vue'
import type { ButtonGroupOption } from '~/components/UI/ButtonGroup.vue'
import ToggleSwitch from '~/components/UI/ToggleSwitch.vue'
import ActionButton from '~/components/UI/ActionButton.vue'
interface Props {
gameId: string
isActive: boolean
currentSetup?: DefensiveDecision
}
const props = withDefaults(defineProps<Props>(), {
isActive: false,
})
const emit = defineEmits<{
submit: [setup: DefensiveDecision]
}>()
// Local state
const submitting = ref(false)
const localSetup = ref<DefensiveDecision>({
infield_depth: props.currentSetup?.infield_depth || 'normal',
outfield_depth: props.currentSetup?.outfield_depth || 'normal',
hold_runners: props.currentSetup?.hold_runners || [],
})
// Hold runner toggles
const holdFirst = ref(localSetup.value.hold_runners.includes(1))
const holdSecond = ref(localSetup.value.hold_runners.includes(2))
const holdThird = ref(localSetup.value.hold_runners.includes(3))
// Watch hold toggles and update hold_runners array
watch([holdFirst, holdSecond, holdThird], () => {
const runners: number[] = []
if (holdFirst.value) runners.push(1)
if (holdSecond.value) runners.push(2)
if (holdThird.value) runners.push(3)
localSetup.value.hold_runners = runners
})
// Options for ButtonGroup components
const infieldDepthOptions: ButtonGroupOption[] = [
{ value: 'in', label: 'Infield In', icon: '⬆️' },
{ value: 'normal', label: 'Normal', icon: '•' },
{ value: 'back', label: 'Back', icon: '⬇️' },
{ value: 'double_play', label: 'Double Play', icon: '⚡' },
{ value: 'corners_in', label: 'Corners In', icon: '◀️▶️' },
]
const outfieldDepthOptions: ButtonGroupOption[] = [
{ value: 'in', label: 'Shallow', icon: '⬆️' },
{ value: 'normal', label: 'Normal', icon: '•' },
{ value: 'back', label: 'Deep', icon: '⬇️' },
]
// Display helpers
const infieldDisplay = computed(() => {
const option = infieldDepthOptions.find(opt => opt.value === localSetup.value.infield_depth)
return option?.label || 'Normal'
})
const outfieldDisplay = computed(() => {
const option = outfieldDepthOptions.find(opt => opt.value === localSetup.value.outfield_depth)
return option?.label || 'Normal'
})
const holdingDisplay = computed(() => {
if (localSetup.value.hold_runners.length === 0) return 'None'
return localSetup.value.hold_runners.map(base => {
if (base === 1) return '1st'
if (base === 2) return '2nd'
if (base === 3) return '3rd'
return base
}).join(', ')
})
// Check if setup has changed from initial
const hasChanges = computed(() => {
if (!props.currentSetup) return true
return (
localSetup.value.infield_depth !== props.currentSetup.infield_depth ||
localSetup.value.outfield_depth !== props.currentSetup.outfield_depth ||
JSON.stringify(localSetup.value.hold_runners) !== JSON.stringify(props.currentSetup.hold_runners)
)
})
const submitButtonText = computed(() => {
if (!props.isActive) return 'Wait for Your Turn'
if (!hasChanges.value) return 'No Changes'
return 'Submit Defensive Setup'
})
// Handle form submission
const handleSubmit = async () => {
if (!props.isActive || !hasChanges.value) return
submitting.value = true
try {
emit('submit', { ...localSetup.value })
} finally {
submitting.value = false
}
}
// Watch for prop changes and update local state
watch(() => props.currentSetup, (newSetup) => {
if (newSetup) {
localSetup.value = { ...newSetup }
holdFirst.value = newSetup.hold_runners.includes(1)
holdSecond.value = newSetup.hold_runners.includes(2)
holdThird.value = newSetup.hold_runners.includes(3)
}
}, { deep: true })
</script>