123 lines
3.5 KiB
Go
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
|
|
}
|