Move login state, authentication, discovery, input, rendering, and tests into focused TUI modules. Add detailed CLI error reports with recovery commands, align the build and host-check documentation, and keep the legacy Go tree outside normal builds. Checks: make check; make integration-test; go test ./... (legacy/go).
122 lines
2.0 KiB
Go
122 lines
2.0 KiB
Go
// Pack two cell rows into each Unicode half-block row; two buffers keep frame
|
|
// updates allocation-free.
|
|
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()
|
|
}
|