Files
latchd/legacy/go/tui/bg/automata.go

122 lines
2.0 KiB
Go

// The renderer packs two cell rows into each Unicode half-block row.
// Two buffers let Tick update the grid without an allocation per frame.
package bg
import (
"math/rand"
"strings"
)
const (
fgAlive = "\x1b[38;2;255;127;0m"
fgDead = "\x1b[38;2;30;30;30m"
bgAlive = "\x1b[48;2;255;127;0m"
bgDead = "\x1b[48;2;30;30;30m"
reset = "\x1b[0m"
block = "▀"
)
type Automata struct {
w, h int
a, b []uint8
useA bool
buf strings.Builder
}
func NewAutomata(w, h int) *Automata {
a := &Automata{useA: true}
a.Resize(w, h)
return a
}
func (a *Automata) Resize(w, h int) {
logH := h * 2
if a.w == w && a.h == logH {
return
}
a.w = w
a.h = logH
sz := w * logH
a.a = make([]uint8, sz)
a.b = make([]uint8, sz)
for i := 0; i < sz; i++ {
if rand.Float32() > 0.85 {
a.a[i] = 1
}
}
a.buf.Grow(w * h * 40)
}
func (a *Automata) Tick() {
src, dst := a.a, a.b
if !a.useA {
src, dst = a.b, a.a
}
w, h := a.w, a.h
for y := range h {
for x := range w {
i := y*w + x
alive := src[i]
n := a.count(src, x, y, w, h)
if alive == 1 && (n == 2 || n == 3) {
dst[i] = 1
} else if alive == 0 && n == 3 {
dst[i] = 1
} else {
dst[i] = 0
}
}
}
a.useA = !a.useA
}
func (a *Automata) count(g []uint8, x, y, w, h int) uint8 {
var c uint8
for dy := -1; dy <= 1; dy++ {
for dx := -1; dx <= 1; dx++ {
if dx == 0 && dy == 0 {
continue
}
nx, ny := x+dx, y+dy
if nx >= 0 && nx < w && ny >= 0 && ny < h {
c += g[ny*w+nx]
}
}
}
return c
}
func (a *Automata) Frame() string {
a.buf.Reset()
src := a.a
if !a.useA {
src = a.b
}
w, h := a.w, a.h
for y := 0; y < h; y += 2 {
for x := range w {
top := src[y*w+x]
bot := uint8(0)
if y+1 < h {
bot = src[(y+1)*w+x]
}
if top == 1 {
a.buf.WriteString(fgAlive)
} else {
a.buf.WriteString(fgDead)
}
if bot == 1 {
a.buf.WriteString(bgAlive)
} else {
a.buf.WriteString(bgDead)
}
a.buf.WriteString(block)
}
a.buf.WriteString(reset)
if y < h-2 {
a.buf.WriteByte('\n')
}
}
return a.buf.String()
}