Last active
January 25, 2017 16:05
-
-
Save lieuwex/b567df99e29011b67d21 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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