diff --git a/confetti/confetti.go b/confetti/confetti.go index b3aee21..2b45e11 100644 --- a/confetti/confetti.go +++ b/confetti/confetti.go @@ -4,7 +4,6 @@ import ( "math/rand" "time" - "github.com/charmbracelet/harmonica" "github.com/maaslalani/confetty/array" "github.com/maaslalani/confetty/simulation" @@ -43,11 +42,11 @@ func Spawn(width, height int) []simulation.Particle { y := float64(0) p := simulation.Particle{ - 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), + Physics: simulation.NewProjectile( + simulation.FPS(framesPerSecond), + simulation.Point{X: x + (float64(width/4) * (rand.Float64() - 0.5)), Y: y, Z: 0}, + simulation.Vector{X: (rand.Float64() - 0.5) * 100, Y: rand.Float64() * 50, Z: 0}, + simulation.Vector(simulation.TerminalGravity), ), Char: lipgloss.NewStyle(). Foreground(lipgloss.Color(array.Sample(colors))). diff --git a/fireworks/fireworks.go b/fireworks/fireworks.go index 715731c..2b34630 100644 --- a/fireworks/fireworks.go +++ b/fireworks/fireworks.go @@ -5,7 +5,6 @@ import ( "math/rand" "time" - "github.com/charmbracelet/harmonica" "github.com/maaslalani/confetty/array" "github.com/maaslalani/confetty/simulation" @@ -22,6 +21,8 @@ const ( var ( colors = []string{"#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"} characters = []string{"+", "*", "•"} + head_char = "▄" + tail_char = "│" ) type frameMsg time.Time @@ -36,24 +37,43 @@ type model struct { system *simulation.System } -func Spawn(width, height int) []simulation.Particle { +func SpawnShoot(width, height int) simulation.Particle { + color := lipgloss.Color(array.Sample(colors)) + v := float64(rand.Intn(15) + 15.0) + + x := rand.Float64() * float64(width) + + p := simulation.Particle{ + Physics: simulation.NewProjectile( + simulation.FPS(framesPerSecond), + simulation.Point{X: x, Y: float64(height)}, + simulation.Vector{X: 0, Y: -v}, + simulation.Vector(simulation.TerminalGravity), + ), + Char: lipgloss.NewStyle().Foreground(color).Render(head_char), + TailChar: lipgloss.NewStyle().Foreground(color).Render(tail_char), + Shooting: true, + ExplosionCall: SpawnExplosion, + } + return p +} + +func SpawnExplosion(x, y float64, width, height int) []simulation.Particle { color := lipgloss.Color(array.Sample(colors)) v := float64(rand.Intn(10) + 20.0) particles := []simulation.Particle{} - x := rand.Float64() * float64(width) - y := rand.Float64() * float64(height) - for i := 0; i < numParticles; i++ { p := simulation.Particle{ - 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), + Physics: simulation.NewProjectile( + simulation.FPS(framesPerSecond), + simulation.Point{X: x, Y: y}, + simulation.Vector{X: math.Cos(float64(i)) * v, Y: math.Sin(float64(i)) * v / 2}, + simulation.Vector(simulation.TerminalGravity), ), - Char: lipgloss.NewStyle().Foreground(color).Render(array.Sample(characters)), + Char: lipgloss.NewStyle().Foreground(color).Render(array.Sample(characters)), + Shooting: false, } particles = append(particles, p) } @@ -67,7 +87,7 @@ func InitialModel() model { } return model{system: &simulation.System{ - Particles: Spawn(width, height), + Particles: []simulation.Particle{SpawnShoot(width, height)}, Frame: simulation.Frame{ Width: width, Height: height, @@ -89,7 +109,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "ctrl+c", "q": return m, tea.Quit } - m.system.Particles = append(m.system.Particles, Spawn(m.system.Frame.Width, m.system.Frame.Height)...) + m.system.Particles = append(m.system.Particles, SpawnShoot(m.system.Frame.Width, m.system.Frame.Height)) return m, nil case frameMsg: diff --git a/go.mod b/go.mod index 524d60a..75fcb1f 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,10 @@ module github.com/maaslalani/confetty go 1.16 require ( - github.com/charmbracelet/bubbletea v0.17.0 - github.com/charmbracelet/harmonica v0.1.1-0.20211027165653-ff7be1c0cd31 + github.com/charmbracelet/bubbletea v0.19.0 github.com/charmbracelet/lipgloss v0.4.0 - github.com/containerd/console v1.0.3 // indirect github.com/mattn/go-isatty v0.0.14 // indirect - github.com/muesli/reflow v0.3.0 // indirect github.com/spf13/cobra v1.2.1 - golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 // indirect + golang.org/x/sys v0.0.0-20211031064116-611d5d643895 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 ) diff --git a/go.sum b/go.sum index 0639e58..4ae761e 100644 --- a/go.sum +++ b/go.sum @@ -46,10 +46,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/charmbracelet/bubbletea v0.17.0 h1:ABmd01l6K7Q8lSQxF7yG8yWayz5gXWSt3JfB/X1eYP4= -github.com/charmbracelet/bubbletea v0.17.0/go.mod h1:YTZSs2p3odhwYZdhqJheYHVUjU37c9OLgS85kw6NGQY= -github.com/charmbracelet/harmonica v0.1.1-0.20211027165653-ff7be1c0cd31 h1:SDIjOIg+gsaRt3vmfQ3hIP6KT4PCC2/ARbTU5hiy/Gg= -github.com/charmbracelet/harmonica v0.1.1-0.20211027165653-ff7be1c0cd31/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/bubbletea v0.19.0 h1:1gz4rbxl3qZik/oP8QW2vUtul2gO8RDDzmoLGERpTQc= +github.com/charmbracelet/bubbletea v0.19.0/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA= github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g= github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -59,9 +57,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -184,7 +181,6 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -204,15 +200,17 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0= github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8= github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -409,8 +407,8 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211031064116-611d5d643895 h1:iaNpwpnrgL5jzWS0vCNnfa8HqzxveCFpFx3uC/X4Tps= +golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= diff --git a/simulation/projectile.go b/simulation/projectile.go new file mode 100644 index 0000000..b5421c0 --- /dev/null +++ b/simulation/projectile.go @@ -0,0 +1,33 @@ +package simulation + +type Projectile struct { + Position Point + Velocity Vector + Acceleration Vector + DeltaTime float64 +} + +type Point struct { + X, Y, Z float64 +} + +type Vector struct { + X, Y, Z float64 +} + +var Gravity = Vector{0, -9.81, 0} +var TerminalGravity = Vector{0, 9.81, 0} + +func NewProjectile(del float64, pos Point, vel, acc Vector) *Projectile { + return &Projectile{pos, vel, acc, del} +} + +func (p *Projectile) Update() { + p.Position.X += (p.Velocity.X * p.DeltaTime) + p.Position.Y += (p.Velocity.Y * p.DeltaTime) + p.Position.Z += (p.Velocity.Z * p.DeltaTime) + + p.Velocity.X += (p.Acceleration.X * p.DeltaTime) + p.Velocity.Y += (p.Acceleration.Y * p.DeltaTime) + p.Velocity.Z += (p.Acceleration.Z * p.DeltaTime) +} diff --git a/simulation/simulation.go b/simulation/simulation.go index 5ec862b..fba581c 100644 --- a/simulation/simulation.go +++ b/simulation/simulation.go @@ -3,8 +3,7 @@ package simulation import ( "fmt" "strings" - - "github.com/charmbracelet/harmonica" + "time" ) type System struct { @@ -13,9 +12,12 @@ type System struct { } type Particle struct { - Char string - Physics *harmonica.Projectile - Hidden bool + Char string + TailChar string + Physics *Projectile + Hidden bool + Shooting bool + ExplosionCall func(x, y float64, width, height int) []Particle } type Frame struct { @@ -23,6 +25,10 @@ type Frame struct { Height int } +func FPS(n int) float64 { + return (time.Second / time.Duration(n)).Seconds() +} + func RemoveParticleFromArray(s []Particle, i int) []Particle { s[i] = s[len(s)-1] return s[:len(s)-1] @@ -30,8 +36,19 @@ func RemoveParticleFromArray(s []Particle, i int) []Particle { func (s *System) Update() { for i := len(s.Particles) - 1; i >= 0; i-- { - p := s.Particles[i].Physics.Position() - if p.X > float64(s.Frame.Width) || p.X < 0 || p.Y > float64(s.Frame.Height) { + p := s.Particles[i] + pos := p.Physics.Position + + // if the shooting particle is slow enough then hide it and call the explosion function + if !p.Hidden && p.Shooting && p.Physics.Velocity.Y > -3 { + p.Hidden = true + if p.ExplosionCall != nil { + s.Particles = append(s.Particles, p.ExplosionCall(p.Physics.Position.X, p.Physics.Position.Y, s.Frame.Width, s.Frame.Height)...) + } + } + + // remove particles that are hidden or out of the side/bottom of the frame + if p.Hidden || pos.X > float64(s.Frame.Width) || pos.X < 0 || pos.Y > float64(s.Frame.Height) { s.Particles = RemoveParticleFromArray(s.Particles, i) } else { s.Particles[i].Physics.Update() @@ -40,9 +57,9 @@ func (s *System) Update() { } func (s *System) Visible(p Particle) bool { - 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 + y := int(p.Physics.Position.Y) + x := int(p.Physics.Position.X) + return !p.Hidden && y >= 0 && y < s.Frame.Height-1 && x >= 0 && x < s.Frame.Width-1 } func (s *System) Render() string { @@ -53,7 +70,16 @@ func (s *System) Render() string { } for _, p := range s.Particles { if s.Visible(p) { - plane[int(p.Physics.Position().Y)][int(p.Physics.Position().X)] = p.Char + plane[int(p.Physics.Position.Y)][int(p.Physics.Position.X)] = p.Char + if p.Shooting { + l := -int(p.Physics.Velocity.Y) + for i := 1; i < l; i++ { + y := int(p.Physics.Position.Y) + i + if y > 0 && y < s.Frame.Height-1 { + plane[y][int(p.Physics.Position.X)] = p.TailChar + } + } + } } } for i := range plane {