Files
symph/types/value.go
T
2026-07-15 01:28:56 -04:00

123 lines
3.5 KiB
Go

package types
import "fmt"
// Value identifies the content of a Note. The enum is taxonomy only: per-kind
// semantics live in valueSpecs, engine behavior in game.resolvers, appearance
// in render.valueVisuals. No switch keys on a Value
type Value int
const (
ValueNone Value = iota
// Blocks
ValueWall
ValueSpike
// Force direction
ValueLeft
ValueDown
ValueUp
ValueRight
// Power up
ValueEnergy
ValueBoost
ValueShield
ValueMagnet
// Enemies
ValueDrain
ValueFortifiedLeft
ValueFortifiedDown
ValueFortifiedUp
ValueFortifiedRight
ValueFortifiedAll
// Count
ValueCount
)
// Polarity classifies a Value's effect on the player. Drives the
// tile distance row (nearest help / nearest threat)
type Polarity uint8
const (
PolarityNeutral Polarity = iota
PolarityPositive
PolarityNegative
)
// ValueSpec is the static description of a Value — the single source of truth
// for kind identity. Adding a kind is a row in valueSpecs, never a switch edit.
// Glyph is both the pattern authoring rune and the fallback display glyph for
// frontends limited to the 32..126 atlas; ASCII range and uniqueness are asserted
type ValueSpec struct {
Name string
Glyph rune
Polarity Polarity
}
// valueSpecs is indexed by Value. Every kind below ValueCount needs a row
var valueSpecs = [ValueCount]ValueSpec{
ValueNone: {"none", '.', PolarityNeutral},
ValueWall: {"wall", '#', PolarityNegative},
ValueSpike: {"spike", 'x', PolarityNegative},
ValueLeft: {"left", '<', PolarityNeutral},
ValueDown: {"down", 'v', PolarityNeutral},
ValueUp: {"up", '^', PolarityNeutral},
ValueRight: {"right", '>', PolarityNeutral},
ValueEnergy: {"energy", '*', PolarityPositive},
ValueBoost: {"boost", 'B', PolarityPositive},
ValueShield: {"shield", 'S', PolarityPositive},
ValueMagnet: {"magnet", 'M', PolarityPositive},
ValueDrain: {"drain", '~', PolarityNegative},
ValueFortifiedLeft: {"fortified-left", 'L', PolarityNegative},
ValueFortifiedDown: {"fortified-down", 'J', PolarityNegative},
ValueFortifiedUp: {"fortified-up", 'K', PolarityNegative},
ValueFortifiedRight: {"fortified-right", 'R', PolarityNegative},
ValueFortifiedAll: {"fortified-all", 'O', PolarityNegative},
}
// glyphValue is the reverse index consumed by the pattern parser
var glyphValue map[rune]Value
// Author errors (missing row, non-ASCII or duplicate glyph) are caught at
// process start, matching parseChord
func init() {
glyphValue = make(map[rune]Value, ValueCount)
for v := ValueNone; v < ValueCount; v++ {
s := valueSpecs[v]
switch {
case s.Name == "":
panic(fmt.Sprintf("types: Value %d has no spec row", int(v)))
case s.Glyph < ' ' || s.Glyph > '~':
panic(fmt.Sprintf("types: Value %d glyph outside ASCII 32..126", int(v)))
}
if _, dup := glyphValue[s.Glyph]; dup {
panic(fmt.Sprintf("types: glyph %q assigned twice", string(s.Glyph)))
}
glyphValue[s.Glyph] = v
}
}
// Valid reports whether v is a defined kind
func (v Value) Valid() bool { return v >= ValueNone && v < ValueCount }
// Spec returns the description of v; the None row when v is out of range
func (v Value) Spec() ValueSpec {
if !v.Valid() {
return valueSpecs[ValueNone]
}
return valueSpecs[v]
}
func (v Value) Polarity() Polarity { return v.Spec().Polarity }
func (v Value) Glyph() rune { return v.Spec().Glyph }
func (v Value) String() string { return v.Spec().Name }
// ValueByGlyph resolves an authoring rune to its Value
func ValueByGlyph(r rune) (Value, bool) {
v, ok := glyphValue[r]
return v, ok
}