@@ -13,19 +13,51 @@ import (
1313 "strings"
1414 "time"
1515
16+ tea "github.com/charmbracelet/bubbletea"
17+ "github.com/charmbracelet/lipgloss"
1618 "github.com/pierrec/lz4/v4"
1719)
1820
19- func nyago (width , height int ) {
21+ // nyanyaState holds the animation state
22+ type nyanyaState struct {
23+ frames [][]string
24+ currentFrame int
25+ loopCount int
26+ maxLoops int
27+ }
28+
29+ // nyanyaTickMsg is sent when it's time to advance the frame
30+ type nyanyaTickMsg time.Time
31+
32+ // nyanyaColors maps characters to 256-color codes
33+ var nyanyaColors = map [string ]string {
34+ "'" : "0" , // outline
35+ "." : "15" , // white
36+ "," : "234" , // bg
37+ ">" : "198" , // lightred (rainbow 1)
38+ "&" : "211" , // lightorange (rainbow 2)
39+ "+" : "222" , // lightyellow (rainbow 3)
40+ "#" : "86" , // lightgreen (rainbow 4)
41+ "=" : "45" , // lightblue (rainbow 5)
42+ ";" : "32" , // lightpurple (rainbow 6)
43+ "@" : "224" , // outer body
44+ "$" : "217" , // inner body
45+ "-" : "204" , // dots on the cat
46+ "%" : "210" , // cheeks
47+ "*" : "248" , // grey
48+ }
49+
50+ // initNyanya initializes the nyanya animation state
51+ func initNyanya () * nyanyaState {
2052 cdec , err := base64 .StdEncoding .DecodeString (c )
2153 if err != nil {
22- return
54+ return nil
2355 }
2456
2557 reader := lz4 .NewReader (bytes .NewReader (cdec ))
2658 all := make ([]byte , 50000 )
2759 if _ , err := reader .Read (all ); err != nil && err != io .EOF {
28- return
60+ return nil
2961 }
3062
3163 framesRaw := strings .Split (string (all ), "z" )
@@ -34,71 +66,101 @@ func nyago(width, height int) {
3466 frames [i ] = strings .Split (framesRaw [i ], "\n " )
3567 }
3668
37- fmt .Printf ("%+v\n " , frames )
38-
39- stop := make (chan struct {}, 1 )
40- captureSIGINTonce (stop )
41-
42- colors := map [string ]string {
43- "'" : "0" , // outline
44- "." : "15" , // white
45- "," : "234" , // bg
46- ">" : "198" , // lightred (rainbow 1)
47- "&" : "211" , // lightorange (rainbow 2)
48- "+" : "222" , // lightyellow (rainbow 3)
49- "#" : "86" , // lightgreen (rainbow 4)
50- "=" : "45" , // lightblue (rainbow 5)
51- ";" : "32" , // lightpurple (rainbow 6)
52- "@" : "224" , // outer body
53- "$" : "217" , // inner body
54- "-" : "204" , // dots on the cat
55- "%" : "210" , // cheeks
56- "*" : "248" , // grey
69+ return & nyanyaState {
70+ frames : frames ,
71+ maxLoops : 3 ,
72+ }
73+ }
74+
75+ // nyanyaTick returns a command that ticks the animation
76+ func nyanyaTick () tea.Cmd {
77+ return tea .Tick (90 * time .Millisecond , func (t time.Time ) tea.Msg {
78+ return nyanyaTickMsg (t )
79+ })
80+ }
81+
82+ // renderNyanya renders the current frame centered on screen
83+ func renderNyanya (state * nyanyaState , width , height int ) string {
84+ if state == nil || len (state .frames ) == 0 {
85+ return ""
5786 }
5887
59- fmt .Print ("\033 [H\033 [2J\033 [?25l" )
60- const outputChar = " "
88+ frame := state .frames [state .currentFrame ]
89+
90+ // Build the frame with colors, filtering empty lines
91+ var frameLines []string
92+ for _ , line := range frame {
93+ if len (line ) == 0 {
94+ continue // Skip empty lines
95+ }
96+ var lineBuilder strings.Builder
97+ for _ , char := range line {
98+ colorCode := nyanyaColors [string (char )]
99+ if colorCode == "" {
100+ colorCode = "234" // default bg
101+ }
102+ // Use ANSI 256-color background
103+ lineBuilder .WriteString (fmt .Sprintf ("\033 [48;5;%sm \033 [0m" , colorCode ))
104+ }
105+ frameLines = append (frameLines , lineBuilder .String ())
106+ }
61107
62- y0 := 0
63- y1 := len (frames [0 ])
108+ frameHeight := len (frameLines )
109+ frameWidth := 0
110+ if frameHeight > 0 && len (frame ) > 0 {
111+ // Find the first non-empty line to calculate width
112+ for _ , line := range frame {
113+ if len (line ) > 0 {
114+ frameWidth = len (line ) * 2 // Each char becomes 2 spaces wide
115+ break
116+ }
117+ }
118+ }
64119
65- x0 := 0
66- x1 := len (frames [0 ][0 ])
120+ // Add instruction at bottom
121+ instruction := lipgloss .NewStyle ().
122+ Foreground (lipgloss .Color ("241" )).
123+ Render ("Press any key to exit" )
67124
68- if y1 > height {
69- y0 = (y1 - height ) / 2
70- y1 = y0 + height
125+ // Calculate vertical positioning
126+ topPadding := (height - frameHeight - 2 ) / 2
127+ if topPadding < 0 {
128+ topPadding = 0
71129 }
72130
73- if x1 > width {
74- x0 = (x1 - width ) / 2
75- x1 = x0 + width
131+ // Center each frame line horizontally
132+ leftPadding := (width - frameWidth ) / 2
133+ if leftPadding < 0 {
134+ leftPadding = 0
76135 }
136+ padding := strings .Repeat (" " , leftPadding )
77137
78- ticker := time .NewTicker (90 * time .Millisecond )
79- defer func () { ticker .Stop () }()
80-
81- for i := 0 ; i < 3 ; i ++ {
82- for _ , frame := range frames {
83- // Print the next frame
84- for _ , line := range frame [y0 :y1 ] {
85- for _ , char := range line [x0 :x1 ] {
86- fmt .Printf ("\033 [48;5;%sm%s" , colors [string (char )], outputChar )
87- }
88- fmt .Println ("\033 [m" )
89- }
138+ var result strings.Builder
90139
91- // Reset the frame and sleep
92- fmt .Print ("\033 [H" )
93- time .Sleep (90 * time .Millisecond )
140+ // Add top padding (empty lines)
141+ for i := 0 ; i < topPadding ; i ++ {
142+ result .WriteString ("\n " )
143+ }
94144
95- select {
96- case <- stop :
97- return
98- case <- ticker . C :
99- }
145+ // Add frame lines (no extra newline after the last one)
146+ for i , line := range frameLines {
147+ result . WriteString ( padding + line )
148+ if i < len ( frameLines ) - 1 {
149+ result . WriteString ( " \n " )
100150 }
101151 }
152+
153+ // Add spacing before instruction
154+ result .WriteString ("\n \n " )
155+
156+ // Add centered instruction
157+ instructionPadding := (width - lipgloss .Width (instruction )) / 2
158+ if instructionPadding < 0 {
159+ instructionPadding = 0
160+ }
161+ result .WriteString (strings .Repeat (" " , instructionPadding ) + instruction )
162+
163+ return result .String ()
102164}
103165
104166const c = "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"
0 commit comments