Files
botfights/frontend/src/components/HumanPreview.vue
T
DorianandClaude Opus 4.6 6bf9fe27b3 feat: production deployment — Dockerfile, docker-compose, SPA serving, classic bot fights
- Add Dockerfile (multi-stage: build frontend + server, serve from single container)
- Add docker-compose.yml for Portainer stack deployment
- Server serves frontend SPA in production (static assets + SPA fallback)
- Auto-run migrations and seed mock bots on server startup
- DB path configurable via DB_PATH env var
- Add "Fight a Classic Bot" button for instant mock bot matches
- FIGHT button queues for real AI opponents

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:42:18 +00:00

67 lines
1.8 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { generateHumanSpriteSheet, getBotColors, HUMAN_ANIMATIONS, HUMAN_MAX_FRAMES, HUMAN_FRAME_SIZE } from '../game/sprites'
const props = defineProps<{
seed: string
archetype?: string
size?: number
winRate?: number
anim?: keyof typeof HUMAN_ANIMATIONS
}>()
const canvasRef = ref<HTMLCanvasElement>()
let img: HTMLImageElement | null = null
let frame = 0
let animHandle: ReturnType<typeof setTimeout> | null = null
function render() {
if (!canvasRef.value || !img?.complete) return
const ctx = canvasRef.value.getContext('2d')!
const displaySize = props.size || 64
canvasRef.value.width = displaySize
canvasRef.value.height = displaySize
ctx.clearRect(0, 0, displaySize, displaySize)
ctx.imageSmoothingEnabled = false
const animName = props.anim || 'idle'
const animCfg = HUMAN_ANIMATIONS[animName]
const f = frame % animCfg.frames
ctx.drawImage(
img,
f * HUMAN_FRAME_SIZE, animCfg.row * HUMAN_FRAME_SIZE, HUMAN_FRAME_SIZE, HUMAN_FRAME_SIZE,
0, 0, displaySize, displaySize,
)
frame++
animHandle = setTimeout(() => render(), 180)
}
function loadSprite() {
const colors = getBotColors(props.seed)
const dataUrl = generateHumanSpriteSheet(props.seed, props.archetype || 'standard', colors.primary, colors.secondary, props.winRate ?? 0.5)
img = new Image()
img.onload = () => render()
img.src = dataUrl
}
onMounted(() => loadSprite())
watch(() => [props.seed, props.archetype, props.winRate, props.anim], () => {
if (animHandle) clearTimeout(animHandle)
frame = 0
loadSprite()
})
onUnmounted(() => {
if (animHandle) clearTimeout(animHandle)
})
</script>
<template>
<canvas
ref="canvasRef"
:style="{ width: `${size || 64}px`, height: `${size || 64}px`, imageRendering: 'pixelated' }"
/>
</template>