Feliz 2025, fogos de artifício no terminal com Golang
Um pequeno programa comemora a chegada de 2025 com fogos de artifício no terminal. Com vários truques interessantes para você iniciar o ano com muito código.
Usamos os.Stdout.WriteString
para escrever diretamente no descritor do stdout, ou seja, na saída padrão do terminal. Essa é a forma mais rápida de escrever no terminal, mas não é sincronizada. Isso significa que não há garantia de que a saída será escrita na ordem esperada. Mas no nosso caso isso é desejável porque permite que os fogos de artifício se sobreponham, dando a impressão de que estão explodindo simultaneamente.
func drawExplosion(x, y int, maxRadius int) {
color := randomColor()
for radius := 1; radius <= maxRadius; radius++ {
for angle := 0; angle < 360; angle += 10 {
theta := float64(angle) * (math.Pi / 180)
px := x + int(float64(radius)*2.0*math.Cos(theta))
py := y + int(float64(radius)*math.Sin(theta))
sy := strconv.Itoa(py)
sx := strconv.Itoa(px)
os.Stdout.WriteString(
"[" + sy + ";" + sx + "H" + color + "*",
)
}
time.Sleep(100 * time.Millisecond)
}
for radius := 1; radius <= maxRadius; radius++ {
for angle := 0; angle < 360; angle += 10 {
theta := float64(angle) * (math.Pi / 180)
px := x + int(float64(radius)*2.0*math.Cos(theta))
py := y + int(float64(radius)*math.Sin(theta))
sy := strconv.Itoa(py)
sx := strconv.Itoa(px)
os.Stdout.WriteString(
"[" + sy + ";" + sx + "H" + ANSI_RESET + " ",
)
}
time.Sleep(50 * time.Millisecond)
}
}
O truque mais importante ao escrever diretamente no terminal sem controlar a sincronização é enviar todas as informações de uma vez ao descritor, ou seja, escrever o máximo possível de uma vez só. Isso evita que a escrita seja intercalada com a de outros processos, na maioria das vezes. Por isso, o texto está todo concatenado antes de chamarmos os.Stdout.WriteString
. Com isso, pequenos artefatos podem aparecer durante os desenhos, mas é um preço pequeno a pagar para obter uma animação mais fluida.
As explosões são simuladas com círculos de diferentes tamanhos e cores aleatórias. A rotina de desenho usa uma técnica antiga para criar círculos, mas foi ligeiramente modificada porque as letras no terminal são mais altas que largas. Por isso, precisamos de um fator de escala de 2.0 no eixo x. Dessa forma, obtemos uma elipse que parece um círculo no terminal.
func drawBanner() {
np := 0
for {
np++
x := 1
mx.Lock()
y := rows / 2
mx.Unlock()
sy := strconv.Itoa(y)
sx := strconv.Itoa(x)
os.Stdout.WriteString(ANSI_SAVE_CURSOR +
"[" + sy + ";" + sx + "H" +
ANSI_WHITE + string(bannerBuff) +
ANSI_RESTORE_CURSOR)
if np%4 == 0 {
copy(bannerBuff, bannerBuff[1:])
bannerBuff[len(bannerBuff)-1] = bannerBuff[0]
}
time.Sleep(40 * time.Millisecond)
}
}
Também desenhamos um banner com nossa mensagem de feliz ano novo. O banner é uma string que é rotacionada a cada 4 frames para dar a impressão de movimento, mas é escrito na tela a cada frame. Dessa forma, está sempre sobre os fogos de artifício. Dependendo do tamanho do terminal, pode ser necessário ajustar os tempos para o efeito ficar mais suave.
Usamos uma condição muito simples para garantir um desenho a cada quadro frames: np%4 == 0
. Ou seja, pegamos o resto da divisão de np
por 4 e, se for zero, desenhamos o banner.
Também usamos a mesma ideia de obter o resto da divição para preencher o buffer do banner com a mesma mensagem repetidas vezes, de forma a cobrir toda a tela.
bannerBuff = make([]rune, 0, cols)
runes := []rune(BANNER)
for i := 0; i < cols; i++ {
bannerBuff = append(bannerBuff, runes[i%len(runes)])
}
Na inicialização do programa, começamos a monitorar alguns sinais do sistema: SIGWINCH
, SIGTERM
e SIGINT
. Assim, podemos redimensionar a tela se o terminal for redimensionado e sair do programa de forma limpa se o usuário pressionar Ctrl+C
.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH, os.Interrupt, syscall.SIGTERM)
go func() {
for caux := range ch {
switch caux {
case syscall.SIGWINCH:
updateTerminalSize()
case os.Interrupt, syscall.SIGTERM:
os.Stdout.WriteString(
ANSI_SHOW_CURSOR +
ANSI_RESET +
ANSI_CLEAR +
"[1;1H\r\n",
)
os.Exit(0)
}
}
}()
ch <- syscall.SIGWINCH
O código fonte completo está disponível no repositório do Grupo de Estudos de Go.
Vejá também o vídeo com a execução do programa:
Feliz 2025!