diff --git a/confetti/confetti.go b/confetti/confetti.go index a62a906..89fb57c 100644 --- a/confetti/confetti.go +++ b/confetti/confetti.go @@ -4,8 +4,8 @@ import ( "math/rand" "time" + "github.com/charmbracelet/harmonica" "github.com/maaslalani/confetty/array" - "github.com/maaslalani/confetty/physics" "github.com/maaslalani/confetty/simulation" tea "github.com/charmbracelet/bubbletea" @@ -43,11 +43,11 @@ func Spawn(width, height int) []simulation.Particle { y := float64(0) p := simulation.Particle{ - Physics: physics.New( - physics.Point{X: x + (float64(width/4) * (rand.Float64() - 0.5)), Y: y}, - physics.Vector{X: (rand.Float64() - 0.5) * 100, Y: rand.Float64() * 50}, - physics.Vector(physics.Gravity), - framesPerSecond, + Physics: harmonica.NewProjectile( + harmonica.FPS(framesPerSecond), + harmonica.Point{X: x + (float64(width/4) * (rand.Float64() - 0.5)), Y: y, Z: 0}, + harmonica.Vector{X: (rand.Float64() - 0.5) * 100, Y: rand.Float64() * 50, Z: 0}, + harmonica.Vector(harmonica.TerminalGravity), ), Char: lipgloss.NewStyle(). Foreground(lipgloss.Color(array.Sample(colors))). diff --git a/fireworks/fireworks.go b/fireworks/fireworks.go index b65d54d..4389944 100644 --- a/fireworks/fireworks.go +++ b/fireworks/fireworks.go @@ -5,8 +5,8 @@ import ( "math/rand" "time" + "github.com/charmbracelet/harmonica" "github.com/maaslalani/confetty/array" - "github.com/maaslalani/confetty/physics" "github.com/maaslalani/confetty/simulation" tea "github.com/charmbracelet/bubbletea" @@ -48,11 +48,11 @@ func Spawn(width, height int) []simulation.Particle { for i := 0; i < numParticles; i++ { p := simulation.Particle{ - Physics: physics.New( - physics.Point{X: x, Y: y}, - physics.Vector{X: math.Cos(float64(i)) * v, Y: math.Sin(float64(i)) * v / 2}, - physics.Vector(physics.Gravity), - framesPerSecond, + Physics: harmonica.NewProjectile( + harmonica.FPS(framesPerSecond), + harmonica.Point{X: x, Y: y}, + harmonica.Vector{X: math.Cos(float64(i)) * v, Y: math.Sin(float64(i)) * v / 2}, + harmonica.Vector(harmonica.TerminalGravity), ), Char: lipgloss.NewStyle().Foreground(color).Render(array.Sample(characters)), } diff --git a/go.mod b/go.mod index c43cd8c..ab94cfc 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/charmbracelet/bubbles v0.8.0 github.com/charmbracelet/bubbletea v0.14.1 + github.com/charmbracelet/harmonica v0.1.1-0.20211007155451-1466d67a1d68 // indirect github.com/charmbracelet/lipgloss v0.3.0 github.com/spf13/cobra v1.2.1 golang.org/x/term v0.0.0-20210422114643-f5beecf764ed diff --git a/go.sum b/go.sum index 975dd38..826ca69 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/charmbracelet/bubbles v0.8.0/go.mod h1:5WX1sSSjNCgCrzvRMN/z23HxvWaa+A github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg= github.com/charmbracelet/bubbletea v0.14.1 h1:pD/bM5LBEH/nDo7nKcgNUgi4uRHQhpWTIHZbG5vuSlc= github.com/charmbracelet/bubbletea v0.14.1/go.mod h1:b5lOf5mLjMg1tRn1HVla54guZB+jvsyV0yYAQja95zE= +github.com/charmbracelet/harmonica v0.1.1-0.20211007155451-1466d67a1d68 h1:Gsc/gIm3Fwp9P1hz0tY+7fJR7fASRsGacIErx5+B7uU= +github.com/charmbracelet/harmonica v0.1.1-0.20211007155451-1466d67a1d68/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v0.1.2/go.mod h1:5D8zradw52m7QmxRF6QgwbwJi9je84g8MkWiGN07uKg= github.com/charmbracelet/lipgloss v0.3.0 h1:5MysOD6sHr4RP4jkZNWGVIul5GKoOsP12NgbgXPvAlA= github.com/charmbracelet/lipgloss v0.3.0/go.mod h1:VkhdBS2eNAmRkTwRKLJCFhCOVkjntMusBDxv7TXahuk= diff --git a/physics/physics.go b/physics/physics.go deleted file mode 100644 index eaacdeb..0000000 --- a/physics/physics.go +++ /dev/null @@ -1,101 +0,0 @@ -package physics - -import ( - "math" -) - -// Position is the location of an object on a 2-dimensional plane -type Position Point - -// Velocity is the velocity vector of an object's motion -type Velocity Vector - -// Acceleration is the acceleration vector of an object's motion -type Acceleration Vector - -// Gravity is the acceleration of gravity -// Downward (+) by g m/s² -var Gravity = Acceleration{ - X: 0, - Y: 9.81, -} - -// Motion represents an objects motion -// it keeps track of the position, velocity, and acceleration -type Motion struct { - pos Position - vel Velocity - acc Acceleration -} - -// Physics tracks the current motion and initial motion of an object along with -// fps to account for the Update in frames rather than per second -type Physics struct { - current Motion - initial Motion - fps float64 -} - -// Vector represents a magnitude and a direction in the form of a Point -// from the origin (0, 0) -type Vector struct { - X float64 - Y float64 -} - -// Point is a coordinate on a 2-dimensional plane -type Point struct { - X float64 - Y float64 -} - -// Distance calculates the euclidean distance between two points -func (a Point) Distance(b Point) float64 { - return math.Sqrt(math.Pow(b.X-a.X, 2) + math.Pow(b.Y-a.Y, 2)) -} - -// New initialize a physics simulation with simple motion -func New(pos Point, vel, acc Vector, fps float64) *Physics { - motion := Motion{ - pos: Position(pos), - vel: Velocity(vel), - acc: Acceleration(acc), - } - return &Physics{ - initial: motion, - current: motion, - fps: fps, - } -} - -// Reset resets the current motion back to the initial -func (p *Physics) Reset() { - p.current = p.initial -} - -// Update increases the position of the motion by the velocity -// and increases the velocity by the acceleration -func (p *Physics) Update() { - p.current.pos.X += p.current.vel.X / p.fps - p.current.pos.Y += p.current.vel.Y / p.fps - - p.current.vel.X += p.current.acc.X / p.fps - p.current.vel.Y += p.current.acc.Y / p.fps -} - -// Displacement calculates the displacement between the current position and -// its initial position -func (p Physics) Displacement() float64 { - return Point(p.initial.pos).Distance(Point(p.current.pos)) -} - -// PosX returns the integer value of the current x coordinate for motion -// not to be confused with Posix :D -func (p Physics) PosX() int { - return int(math.Round(p.current.pos.X)) -} - -// PosY returns the integer value of the current y coordinate for motion -func (p Physics) PosY() int { - return int(math.Round(p.current.pos.Y)) -} diff --git a/physics/physics_test.go b/physics/physics_test.go deleted file mode 100644 index 72a4a72..0000000 --- a/physics/physics_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package physics_test - -import ( - "testing" - "time" - - . "github.com/maaslalani/confetty/physics" -) - -const fps = 60 - -func simulate(p *Physics, d time.Duration) { - frames := int(d.Seconds() * fps) - for i := 0; i < frames; i++ { - p.Update() - } -} - -func TestNew(t *testing.T) { - x := 8 - y := 20 - - physics := New(Point{float64(x), float64(y)}, Vector{1, 1}, Vector(Gravity), 60) - if x != physics.PosX() { - t.Fatal("x coordinate unexpected") - } - - if y != physics.PosY() { - t.Fatal("y coordinate unexpected") - } -} - -func TestUpdate(t *testing.T) { - physics := New(Point{0, 0}, Vector{5, 5}, Vector(Gravity), float64(fps)) - - // coordinates is the location at which the object should be - // after i+1 seconds of simulation. - coordinates := []Point{ - {5, 10}, - {10, 29}, - {15, 59}, - {20, 98}, - {25, 147}, - {30, 206}, - {35, 275}, - } - - for _, c := range coordinates { - simulate(physics, time.Second) - - x := physics.PosX() - y := physics.PosY() - - if x != int(c.X) { - t.Logf("Want: %d, Got: %d", x, int(c.X)) - t.Fatal("x coordinate unexpected") - } - if y != int(c.Y) { - t.Logf("Want: %d, Got: %d", y, int(c.Y)) - t.Fatal("y coordinate unexpected") - } - } -} - -func TestDisplacement(t *testing.T) { - tt := []struct { - x int - y int - vel Vector - d float64 - }{ - {x: 5, y: 5, vel: Vector{5, 10}, d: 11.180339887498933}, - {x: 0, y: 0, vel: Vector{1, 1}, d: 1.414213562373097}, - } - - for _, tc := range tt { - physics := New(Point{float64(tc.x), float64(tc.y)}, tc.vel, Vector{}, float64(fps)) - - simulate(physics, time.Second) - - if physics.Displacement() != tc.d { - t.Log(physics.Displacement()) - t.Fatal("expected displacement to be 15") - } - } -} - -func TestReset(t *testing.T) { - x := 5 - y := 10 - vel := Vector{20, 20} - - physics := New(Point{float64(x), float64(y)}, vel, Vector(Gravity), float64(fps)) - - simulate(physics, time.Second) - - // store the current position after 1 second of simulation we will simulate 1 - // second again and ensure that the object reaches the same position, we - // don't care what the position is but if the object reaches the same - // position again then we ensure that the velocity and acceleration was reset - cx := physics.PosX() - cy := physics.PosY() - - physics.Reset() - - if physics.PosX() != x { - t.Fatal("expected x to be reset") - } - - if physics.PosY() != y { - t.Fatal("expected y to be reset") - } - - // here we are simply checking that the object reaches the same position - // after being reset in the same amount of time. This ensures velocity and - // acceleration were reset without checking them explicitly - simulate(physics, time.Second) - - if physics.PosX() != cx { - t.Fatal("expected simulation to be repeatable") - } - - if physics.PosY() != cy { - t.Fatal("expected simulation to be repeatable") - } -} diff --git a/simulation/simulation.go b/simulation/simulation.go index af8d396..aced52c 100644 --- a/simulation/simulation.go +++ b/simulation/simulation.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/maaslalani/confetty/physics" + "github.com/charmbracelet/harmonica" ) type System struct { @@ -14,7 +14,7 @@ type System struct { type Particle struct { Char string - Physics *physics.Physics + Physics *harmonica.Projectile Hidden bool } @@ -39,8 +39,8 @@ func (s *System) Update() { } func (s *System) Visible(p Particle) bool { - y := p.Physics.PosY() - x := p.Physics.PosX() + y := int(p.Physics.Position().Y) + x := int(p.Physics.Position().X) return y >= 0 && y < s.Frame.Height-1 && x >= 0 && x < s.Frame.Width-1 } @@ -52,7 +52,7 @@ func (s *System) Render() string { } for _, p := range s.Particles { if s.Visible(p) { - plane[p.Physics.PosY()][p.Physics.PosX()] = p.Char + plane[int(p.Physics.Position().Y)][int(p.Physics.Position().X)] = p.Char } } for i := range plane {