97 lines
2.5 KiB
Go
97 lines
2.5 KiB
Go
package ui
|
|
|
|
import "strings"
|
|
|
|
// Half-width katakana + digits for the iconic Matrix digital rain.
|
|
var matrixChars = []rune{
|
|
'ヲ', 'ァ', 'ィ', 'ゥ', 'ェ', 'ォ', 'ャ', 'ュ', 'ョ', 'ッ',
|
|
'ー', 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ',
|
|
'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト',
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
}
|
|
|
|
// renderMatrix draws falling character streams reminiscent of the Matrix
|
|
// digital rain. Each column has a fixed fall speed derived from its position.
|
|
// Band energy controls how many columns are active (rain density).
|
|
func (v *Visualizer) renderMatrix(bands []float64) string {
|
|
height := v.Rows
|
|
lines := make([]string, height)
|
|
bandCount := len(bands)
|
|
|
|
for row := range height {
|
|
var sb, run strings.Builder
|
|
tag := -1
|
|
col := 0
|
|
for b := range bandCount {
|
|
w := visBandWidth(bandCount, b)
|
|
for range w {
|
|
energy := bands[b]
|
|
seed := uint64(col)*7919 + 104729
|
|
|
|
// Column activity: stable gate, changes every ~20 frames.
|
|
// Higher energy activates more columns.
|
|
if scatterHash(b, 0, col, v.frame/20) > energy*1.5+0.1 {
|
|
if tag != -1 {
|
|
flushStyleRun(&sb, &run, tag)
|
|
tag = -1
|
|
}
|
|
run.WriteByte(' ')
|
|
col++
|
|
continue
|
|
}
|
|
|
|
// Fixed speed per column (2-4 frames per row step), derived
|
|
// from column position so each drop falls steadily.
|
|
speed := 2 + int(seed%3)
|
|
|
|
// Trail length: 3-5 characters.
|
|
trailLen := 3 + int((seed/7)%3)
|
|
|
|
// Cycle length large enough for a visible gap between drops.
|
|
cycleLen := height + trailLen + 4
|
|
offset := int((seed / 13) % uint64(cycleLen))
|
|
pos := (int(v.frame)/speed + offset) % cycleLen
|
|
|
|
dist := pos - row
|
|
if dist < 0 || dist > trailLen {
|
|
if tag != -1 {
|
|
flushStyleRun(&sb, &run, tag)
|
|
tag = -1
|
|
}
|
|
run.WriteByte(' ')
|
|
} else {
|
|
// Character mutates slowly (~every 4 frames).
|
|
charSeed := seed ^ (uint64(row)*31 + (v.frame/4)*17)
|
|
ch := matrixChars[charSeed%uint64(len(matrixChars))]
|
|
var newTag int
|
|
switch {
|
|
case dist == 0:
|
|
newTag = 2
|
|
case dist <= 2:
|
|
newTag = 1
|
|
default:
|
|
newTag = 0
|
|
}
|
|
if newTag != tag {
|
|
flushStyleRun(&sb, &run, tag)
|
|
tag = newTag
|
|
}
|
|
run.WriteRune(ch)
|
|
}
|
|
col++
|
|
}
|
|
if b < bandCount-1 {
|
|
if tag != -1 {
|
|
flushStyleRun(&sb, &run, tag)
|
|
tag = -1
|
|
}
|
|
run.WriteByte(' ')
|
|
col++
|
|
}
|
|
}
|
|
flushStyleRun(&sb, &run, tag)
|
|
lines[row] = sb.String()
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|