Headless Engines
Headless engines provide game logic without UI, letting you build custom visuals while reusing battle-tested state machines.
Philosophy
Traditional component libraries couple logic and presentation:
vue
<!-- Coupled - can't change visuals without forking -->
<SlotMachine :reels="3" theme="vegas" />Fortune Kit separates them:
vue
<script setup>
// Logic is a composable
const { spin, isSpinning, result } = useWheel(options)
</script>
<template>
<!-- UI is yours -->
<MyCustomWheel :rotation="rotation" @click="spin" />
</template>Available Engines
useWheel
Spin-to-win wheel logic:
ts
import { useWheel } from '@fortune-kit/headless'
const { spin, isSpinning, result, segmentAngle } = useWheel({
segments: [
{ id: '1', label: '100 pts', value: 100, weight: 10 },
{ id: '2', label: '500 pts', value: 500, weight: 3 },
{ id: '3', label: 'JACKPOT', value: 10000, weight: 1 }
],
spinDuration: 4000,
onSpinEnd: (winner) => console.log('Won:', winner)
})Features:
- Weighted random selection
- Configurable spin duration
- Rotation calculation for CSS transforms
- Start/reset controls
useQuiz
Quiz/trivia game logic:
ts
import { useQuiz } from '@fortune-kit/headless'
const { currentQuestion, answer, score, progress, isComplete } = useQuiz({
questions: [...],
shuffleQuestions: true,
onAnswer: (answer) => console.log('Answered:', answer),
onComplete: (answers, score) => console.log('Final:', score)
})Features:
- Question navigation (next/prev/goTo)
- Answer tracking with timing
- Score calculation
- Shuffle support
- Progress tracking
Building Custom UI
Example: Canvas Wheel
vue
<script setup>
import { useWheel } from '@fortune-kit/headless'
import { onMounted, watch } from 'vue'
const { state, spin, segmentAngle } = useWheel({ segments })
const canvas = ref<HTMLCanvasElement>()
watch(() => state.value.currentRotation, (rotation) => {
// Redraw wheel at new rotation
drawWheel(canvas.value, rotation)
})
</script>
<template>
<canvas ref="canvas" @click="spin" />
</template>Example: 3D Quiz Cards
vue
<script setup>
import { useQuiz } from '@fortune-kit/headless'
const { currentQuestion, answer } = useQuiz({ questions })
</script>
<template>
<div class="perspective-1000">
<div
v-for="option in currentQuestion.options"
class="transform-3d hover:rotate-y-10"
@click="answer(option.id)"
>
{{ option.text }}
</div>
</div>
</template>Testing
Headless engines are easy to unit test:
ts
import { useWheel } from '@fortune-kit/headless'
test('wheel selects weighted random segment', async () => {
const { spin, result } = useWheel({
segments: [
{ id: 'a', label: 'A', value: 1, weight: 100 },
{ id: 'b', label: 'B', value: 2, weight: 0 }
]
})
await spin()
// With weight 0, 'b' should never win
expect(result.value?.id).toBe('a')
})