feat(adventure): add renderer orchestrator and bootstrap game

Wires all UI modules together in renderer.ts with mountApp/render,
adds keyboard shortcuts (M, Escape, 1-9, A-C), and replaces the
placeholder main.ts with full bootstrap (load save, mount, subscribe,
enter room).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Brown
2026-03-26 18:34:26 -07:00
committed by DrJKL
parent 0d71e6757a
commit 997f4995dc
2 changed files with 105 additions and 3 deletions

View File

@@ -7,10 +7,20 @@ import './style/sidebar.css'
import './style/map.css'
import './style/animations.css'
import { isV1Save, loadSave } from '@/state/gameState'
import { enterRoom, initGameState, subscribe } from '@/engine/stateMachine'
import { mountApp, render } from '@/ui/renderer'
function main(): void {
const app = document.getElementById('app')
if (!app) throw new Error('Missing #app element')
app.textContent = 'Codebase Caverns v2 — Loading...'
if (isV1Save()) {
console.warn('Codebase Caverns v1 save detected. Starting fresh for v2.')
}
const save = loadSave()
mountApp()
initGameState(save)
subscribe(render)
enterRoom(save.currentRun.currentRoom)
}
main()

View File

@@ -0,0 +1,92 @@
import type { GameState } from '@/types'
import { challenges } from '@/data/challenges'
import { countResolvedChallenges } from '@/engine/navigation'
import { showEnding } from '@/engine/stateMachine'
import { clearSave } from '@/state/gameState'
import { createHud, renderHud } from '@/ui/hud'
import { renderChallenge } from '@/ui/challengeView'
import { renderEnding } from '@/ui/endingView'
import { createMapOverlay, renderMap, toggleMap } from '@/ui/nodeMap'
import { createRoomView, renderRoom } from '@/ui/roomView'
import { createSidebar, renderSidebar } from '@/ui/sidebar'
function mountApp(): void {
const app = document.getElementById('app')
if (!app) throw new Error('Missing #app element')
app.appendChild(createHud())
app.appendChild(createRoomView())
app.appendChild(createSidebar())
createMapOverlay()
const toggleBtn = document.getElementById('toggle-map')
toggleBtn?.addEventListener('click', toggleMap)
const restartBtn = document.getElementById('restart-btn')
restartBtn?.addEventListener('click', () => {
clearSave()
location.reload()
})
document.addEventListener('keydown', handleKeydown)
}
function handleKeydown(e: KeyboardEvent): void {
const tag = (e.target as HTMLElement).tagName
if (tag === 'INPUT' || tag === 'TEXTAREA') return
if (e.key === 'M' || e.key === 'm') {
toggleMap()
return
}
if (e.key === 'Escape') {
const dialog = document.getElementById(
'map-dialog'
) as HTMLDialogElement | null
if (dialog?.open) dialog.close()
return
}
const numMatch = e.key.match(/^[1-9]$/)
if (numMatch) {
const index = parseInt(e.key, 10) - 1
const choices = document.querySelectorAll<HTMLButtonElement>('.choice-btn')
choices[index]?.click()
return
}
const letterMatch = e.key.match(/^[A-Ca-c]$/)
if (letterMatch) {
const key = e.key.toUpperCase()
const choices = document.querySelectorAll<HTMLButtonElement>(
'.challenge-choice-btn'
)
const match = Array.from(choices).find(
(btn) => btn.querySelector('.choice-key')?.textContent === key
)
match?.click()
}
}
function render(state: GameState): void {
renderHud(state)
renderSidebar(state)
renderMap(state)
if (state.phase === 'ending') {
renderEnding(state)
return
}
renderRoom(state)
renderChallenge(state)
const totalChallenges = Object.keys(challenges).length
const resolved = countResolvedChallenges(state.save)
if (resolved >= totalChallenges) {
showEnding()
}
}
export { mountApp, render }