Skip to content

Instantly share code, notes, and snippets.

@lieuwex
Last active January 25, 2017 16:05
Show Gist options
  • Save lieuwex/b567df99e29011b67d21 to your computer and use it in GitHub Desktop.
Save lieuwex/b567df99e29011b67d21 to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"fmt"
"math"
"math/rand"
"os"
"os/exec"
"os/signal"
"strconv"
"time"
)
const (
maxAngle = 45 // in degrees
maxWind = 30 // in degrees
maxWindDelta = 7 // in degrees
defaultFramesPerSecond = 30
)
const (
maxAngleRad = maxAngle * math.Pi / 180
maxWindRad = maxWind * math.Pi / 180
maxWindDeltaRad = maxWindDelta * math.Pi / 180
)
// State contains the state of the current animation
type State struct {
particles []Particle
tick int
wind float64
}
func (state *State) step() int {
state.tick++
state.wind += float64(rand.Intn(200)-100) / 100 * maxWindDeltaRad
if state.wind < -maxWindRad {
state.wind = -maxWindRad
} else if state.wind > maxWindRad {
state.wind = maxWindRad
}
for i := range state.particles {
particle := &state.particles[i]
angle := particle.angle + state.wind
deltaX := particle.stepsPerTick * math.Sin(angle)
deltaY := particle.stepsPerTick * math.Cos(angle)
particle.x += deltaX
particle.y += deltaY
}
return state.tick
}
func (state *State) render(width, height int) bool {
clearScreen()
foundAny := false
locs := make([]bool, width*height)
for _, particle := range state.particles {
i := (width * int(particle.y)) + int(particle.x)
if i < 0 || i >= width*height {
continue
}
locs[i] = true
foundAny = true
}
var buffer bytes.Buffer
buffer.Grow(width * height)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
if locs[(width*y)+x] {
buffer.WriteString("❄")
} else {
buffer.WriteString(" ")
}
}
}
fmt.Print(buffer.String())
return foundAny
}
func (state *State) print() {
fmt.Println("particles:")
for _, particle := range state.particles {
fmt.Printf("- %+v\n", particle)
}
fmt.Printf("\ntick: %d\n", state.tick)
}
// Particle is a snowflake
type Particle struct {
x, y float64
angle float64
stepsPerTick float64
}
func getTerminalSize() (int, int) {
var h, w int
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
s, _ := cmd.Output()
fmt.Sscan(string(s), &h, &w)
return w, h
}
func clearScreen() {
print("\033[H\033[2J")
}
func runAfterClose(fn func()) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
fn()
os.Exit(1)
}()
}
func main() {
cols, rows := getTerminalSize()
state := State{
particles: make([]Particle, cols*rows/13),
tick: 0,
}
framesPerSecond := defaultFramesPerSecond
if len(os.Args) == 2 {
var err error
framesPerSecond, err = strconv.Atoi(os.Args[1])
if err != nil {
fmt.Printf("could not parse '%s' to int", os.Args[1])
os.Exit(1)
}
}
// give each particle a random angle.
for i := range state.particles {
particle := &state.particles[i]
particle.x = float64(rand.Intn(cols*2) - cols)
particle.y = float64(rand.Intn(40)) - 35
particle.angle = float64(rand.Intn(200)-100) / 100 * maxAngleRad
particle.stepsPerTick = (float64(rand.Intn(65)) + 35) / 100
}
runAfterClose(clearScreen)
defer clearScreen()
for {
if !state.render(cols, rows) {
break
}
state.step()
time.Sleep(time.Second / time.Duration(framesPerSecond))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment