okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

dawnssh: say when a probe is copying the kernel to a host, in the picker and the chat8679812

main
5.2 KiB150 linesraw
1package tui
2
3import (
4	"fmt"
5	"time"
6)
7
8// A mood is a class of moment in a turn. Each class owns its own faces, so a
9// face always means something: what albedo is doing, or how a turn ended.
10type mood string
11
12const (
13	moodPreparing  mood = "preparing"
14	moodThinking   mood = "thinking"
15	moodResponding mood = "responding"
16	moodWorking    mood = "working"
17	moodCompacting mood = "compacting"
18	moodStopping   mood = "stopping"
19	moodConnecting mood = "connecting"
20
21	moodDone    mood = "done"
22	moodQuick   mood = "quick"
23	moodLong    mood = "long"
24	moodTired   mood = "tired"
25	moodFailed  mood = "failed"
26	moodStopped mood = "stopped"
27)
28
29// Faces avoid combining marks and right-to-left letters, which terminals
30// disagree on.
31//
32// A phase owns a pool of animations and each turn plays one of them. A face
33// keeps its parentheses in place across frames: only eyes, mouth, and what
34// follows the face move.
35var animations = map[mood][][]string{
36	moodPreparing: {{"(・・ )", "( ・・)"}},
37	moodThinking: {
38		{"( ˘ω˘ )", "( ˘ω˘ ).", "( ˘ω˘ )..", "( ˘ω˘ )..."},
39		{"(・ω・ )", "( ・ω・)", "(・ω・ )?", "( ・ω・)?"},
40		{"(´-ω-`)", "(´・ω・`)", "(´-ω-`)", "(´・ω・`)!"},
41	},
42	moodResponding: {
43		{"(ˊᗜˋ )", "(ˊ-ˋ )", "( ˊᗜˋ)", "( ˊ-ˋ)"},
44		{"(˶ᵔ ᗜ ᵔ˶)", "(˶ᵔ ᵕ ᵔ˶)"},
45		{"(・ᗜ・)", "(・-・)", "(・ᗜ・)ノ", "(・-・)ノ"},
46	},
47	moodWorking: {
48		{"(˶•ᴗ•)カタ", "(˶•ᴗ•)カタカタ", "(˶•ᴗ•)カタカタカタ"},
49		{"(・_・ )", "( ・_・)", "(・ᴗ・ )✧"},
50		{"(っ•ᴗ•)っ・", "(っ•ᴗ•)っ ・", "(っ•ᴗ•)っ  ・"},
51		{"ヽ(・ω・)ノ", "ヾ(・ω・)ノ"},
52		{"(๑•ᴗ•)", "(๑•ᴗ•)✧", "(๑•ᴗ•) ✧"},
53	},
54	moodCompacting: {{"(>_<)", "(>.<)"}},
55	moodStopping:   {{"(・_・;)"}},
56	moodConnecting: {
57		{"(・ω・)ノ", "(・ω・)ノ ・", "(・ω・)ノ ・・", "(・ω・)ノ ・・・"},
58		{"( ˘ω˘ )~", "( ˘ω˘ )~~", "( ˘ω˘ )~~~"},
59		{"(っ•ᴗ•)っ", "(っ•ᴗ•)っ ・", "(っ•ᴗ•)っ ・゚", "(っ•ᴗ•)っ ・゚✧"},
60	},
61}
62
63// An outcome owns a pool of faces and each turn shows one of them.
64// Additional faces come from https://wikileaks.org/ciav7p1/cms/page_17760284.html.
65var faces = map[mood][]string{
66	moodDone:    {"(˶ᵔ ᵕ ᵔ˶)", "(๑˃ᴗ˂)", "ヽ(・∀・)ノ", "(ᵔᴥᵔ)", "(っ˘ω˘ς)", "(◕‿◕)", "(✿◠‿◠)", "(o´ω`o)", "☻_☻", "ᶘ ᵒᴥᵒᶅ"},
67	moodQuick:   {"(・ω・)ノ", "(`・ω・´)", "(^_−)☆", "(^▽^)"},
68	moodLong:    {"(ง ˃ᴗ˂)ง", "(๑˃ᴗ˂)✧", "(ˊᗜˋ*)✧", "o(≧∀≦)o", "(ノ◕ヮ◕)ノ*:・゚✧"},
69	moodTired:   {"( ̄ー ̄;)ゞ", "(・ω・;)ゞ", "( ˘ᴗ˘ )フゥ", "(´-ω-`)zzZ", "_(:3 」∠)_", "(︶ω︶)", "(-_- )ノ", "(n˘v˘•)¬"},
70	moodFailed:  {"(╥﹏╥)", "(。•︿•。)", "(っ- ‸ – ς)", "(ಥ﹏ಥ)", "(╥_╥)", "☹_☹", "(ಠ~ಠ)", "(ಡ_ಡ)", "(ತಎತ)", "(ತ_ತ)", "(ಥдಥ)"},
71	moodStopped: {"(・_・;)", "(°ロ°)", "( ̄□ ̄;)", "(ಠ_ಠ)", "(°Д°)"},
72}
73
74// pick chooses from n by seed, salted by the mood so one turn's picks for
75// different moods do not move in step.
76func (m mood) pick(seed int64, n int) int {
77	return int(fnv1a(uint64(seed), string(m)) % uint64(n))
78}
79
80// face picks a stable face for seed, so a rebuilt transcript keeps its faces.
81func (m mood) face(seed int64) string {
82	set := faces[m]
83	return set[m.pick(seed, len(set))]
84}
85
86// faceInterval is how long each frame of an animation shows.
87const faceInterval = 500 * time.Millisecond
88
89// frame is the turn's animation at tick, one frame per faceInterval.
90func (m mood) frame(seed int64, tick int) string {
91	pool := animations[m]
92	set := pool[m.pick(seed, len(pool))]
93	return set[tick%len(set)]
94}
95
96// reachingText is what waiting on a host reads as, by its label: connecting,
97// or copying albedo's kernel over (step staging), which the first visit to a
98// host, and the first after albedo's python changed, waits for.
99func reachingText(label, step string) string {
100	if step == "staging" {
101		return "copying the kernel to " + label + "…"
102	}
103	return "connecting to " + label + "…"
104}
105
106// connectingFace is the animation for reaching a host, by its label, at
107// tick: the same one for a host wherever it shows.
108func connectingFace(host string, tick int) string {
109	return moodConnecting.frame(int64(fnv1a(fnvOffset64, host)), tick)
110}
111
112// outcome classes a finished turn by how it ended and how long it took.
113func outcome(failed, stopped bool, elapsedMs int64) mood {
114	switch {
115	case stopped:
116		return moodStopped
117	case failed:
118		return moodFailed
119	case elapsedMs > 10*60_000:
120		return moodTired
121	case elapsedMs >= 3*60_000:
122		return moodLong
123	case elapsedMs > 0 && elapsedMs < 4_000:
124		return moodQuick
125	}
126	return moodDone
127}
128
129func formatElapsed(ms int64) string {
130	s := ms / 1000
131	switch {
132	case s < 60:
133		return fmt.Sprintf("%ds", s)
134	case s < 3600:
135		return fmt.Sprintf("%dm %ds", s/60, s%60)
136	}
137	return fmt.Sprintf("%dh %dm", s/3600, s/60%60)
138}
139
140// formatGap is how long you were away, in the largest whole unit.
141func formatGap(ms int64) string {
142	m := ms / 60_000
143	switch {
144	case m < 60:
145		return fmt.Sprintf("%dm later", m)
146	case m < 48*60:
147		return fmt.Sprintf("%dh later", m/60)
148	}
149	return fmt.Sprintf("%dd later", m/(24*60))
150}