Compare commits

...

4 Commits

Author SHA1 Message Date
3c99e5881b Saving lives left for resume as well. 2021-08-12 22:35:33 +02:00
e3527eb580 Added music & game sounds.
User can now use mouse to select controls.
2021-08-12 22:28:31 +02:00
cbd08cdc12 Added install.bat. 2021-08-11 19:56:50 +02:00
c47f9383c3 Added background generation. 2021-08-11 19:55:52 +02:00
13 changed files with 535 additions and 69 deletions

View File

@ -1,12 +1,14 @@
- [X] Increase difficulty.
- [ ] Add music & sounds.
- [X] Add music & sounds.
- [X] Keep score/difficulty level (resume & restart).
- [X] ~~Explain controls on info page~~ add settings for controls.
- [X] Fix usage of go/embed (and remove rice again).
- [X] Add monster animations (~~jumping on tile &~~ towards new tile).
- [ ] Scale icons (heart & star on right side) when playing.
- [X] ~~Scale icons (heart & star on right side) when playing.~~
- [ ] Change layout when playing in portrait mode.
- [X] Fix wobble animation.
- [ ] Add more unit tests?
- [ ] Fix z-fighting of monsters.
- [ ] Add exploding animation of monsters.
- [ ] Add exploding animation of monsters.
- [ ] Add audio settings (music & sound volume).
- [X] Hearts must be saved as well for resume.

View File

@ -61,6 +61,21 @@ func (a *app) Init(ctx ui.Context) error {
})
a.context.ShowMainMenu(ctx)
err := a.context.Audio.LoadSample(
"level_completed.mp3",
"level_game_over.mp3",
"level_new_high_score.mp3",
"menu_interaction.mp3",
"monster_jump.mp3",
"player_collect_heart.mp3",
"player_collect_star.mp3",
"player_hurt.mp3",
"player_move.mp3",
)
if err != nil {
return err
}
return nil
}

View File

@ -1,6 +1,9 @@
package main
import (
"fmt"
"math/rand"
"opslag.de/schobers/tins2021"
"opslag.de/schobers/zntg/ui"
)
@ -12,10 +15,14 @@ type appContext struct {
Score *tins2021.ScoreState
Debug bool
StarTexture tins2021.AnimatedTexture
HeartTexture tins2021.AnimatedTexture
StarTexture tins2021.AnimatedTexture
HeartTexture tins2021.AnimatedTexture
MonsterTextures map[tins2021.MonsterType]tins2021.AnimatedTexture
Audio *AudioPlayer
MenuMusic *Music
GameMusic *Music
GameMusicSong int
}
func newAppContext(ctx ui.Context, settings *tins2021.Settings, score *tins2021.ScoreState, setView func(ui.Control)) *appContext {
@ -23,6 +30,7 @@ func newAppContext(ctx ui.Context, settings *tins2021.Settings, score *tins2021.
app := &appContext{
setView: setView,
Settings: settings,
Audio: NewAudioPlayer(ctx.Resources(), "resources/sounds/"),
Score: score,
StarTexture: newAnimatedTexture(ctx, "star", defaultAnimationFrames, textures.Star),
HeartTexture: newAnimatedTexture(ctx, "heart", defaultAnimationFrames, textures.Heart),
@ -38,12 +46,16 @@ func newAppContext(ctx ui.Context, settings *tins2021.Settings, score *tins2021.
const numberOfStars = 5
func (app *appContext) MenuInteraction() {
app.Audio.PlaySample("menu_interaction.mp3")
}
func (app *appContext) Play(ctx ui.Context) {
level := tins2021.NewLevel()
level.Randomize(0, numberOfStars)
app.Score.Current = tins2021.Score{}
app.show(newLevelControl(app, ctx, level))
app.ResetCurrentScore()
app.show(ctx, newLevelControl(app, ctx, level))
}
func (app *appContext) PlayNext(ctx ui.Context, controller *levelController) {
@ -62,31 +74,95 @@ func (app *appContext) PlayNext(ctx ui.Context, controller *levelController) {
func (app *appContext) PlayResume(ctx ui.Context) {
level := tins2021.NewLevel()
level.Score = app.Score.Current.Score
level.Lives = app.Score.CurrentLives
level.Randomize(app.Score.Current.Difficulty, numberOfStars)
app.show(newLevelControl(app, ctx, level))
app.show(ctx, newLevelControl(app, ctx, level))
}
func (app *appContext) show(control ui.Control) {
func (app *appContext) ResetCurrentScore() {
app.Score.Current = tins2021.Score{}
app.Score.CurrentLives = 0
}
func (app *appContext) SetCurrentScore(level *tins2021.Level) {
app.Score.Current = tins2021.NewScore(level.Score, level.Difficulty+1)
app.Score.CurrentLives = level.Lives
}
func (app *appContext) show(ctx ui.Context, control ui.Control) {
app.setView(control)
if _, ok := control.(*levelController); ok {
app.switchToPlayMusic(ctx)
} else {
app.switchToMenuMusic(ctx)
}
}
func (app *appContext) ShowCredits(ctx ui.Context) {
app.setView(newCredits(app, ctx))
app.show(ctx, newCredits(app, ctx))
}
func (app *appContext) ShowSettings(ctx ui.Context) {
app.setView(newSettings(app, ctx))
app.show(ctx, newSettings(app, ctx))
}
func (app *appContext) ShowHighscores(ctx ui.Context) {
app.setView(newHighscores(app, ctx))
app.show(ctx, newHighscores(app, ctx))
}
func (app *appContext) ShowInfo(ctx ui.Context) {
app.setView(newInfo(app, ctx))
app.show(ctx, newInfo(app, ctx))
}
func (app *appContext) ShowMainMenu(ctx ui.Context) {
app.show(newMainMenu(app, ctx))
app.show(ctx, newMainMenu(app, ctx))
}
func (app *appContext) playNextGameMusic(ctx ui.Context) {
if app.GameMusic != nil {
return
}
const songs = 4
pick := func() int {
for {
s := rand.Intn(songs) + 1
if s == app.GameMusicSong {
continue
}
app.GameMusicSong = s
return s
}
}
song := fmt.Sprintf("song_game_%d.mp3", pick())
app.GameMusic, _ = app.Audio.PlayMusic(song, func(m *Music) {
m.OnFinished = func() {
if app.GameMusic == nil {
return
}
app.GameMusic = nil
app.playNextGameMusic(ctx)
}
})
}
func (app *appContext) switchToPlayMusic(ctx ui.Context) {
app.playNextGameMusic(ctx)
menuMusic := app.MenuMusic
app.MenuMusic = nil
if menuMusic != nil {
menuMusic.Stop()
}
}
func (app *appContext) switchToMenuMusic(ctx ui.Context) {
if app.MenuMusic != nil {
return
}
app.MenuMusic, _ = app.Audio.PlayMusic("song_menu.mp3", func(m *Music) {})
gameMusic := app.GameMusic
app.GameMusic = nil
if gameMusic != nil {
gameMusic.Stop()
}
}

181
cmd/tins2021/audio.go Normal file
View File

@ -0,0 +1,181 @@
package main
import (
"fmt"
"io"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/effects"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
"opslag.de/schobers/zntg/ui"
)
type AudioPlayer struct {
resources ui.Resources
prefix string
SampleRate beep.SampleRate
Samples map[string]Sample
SampleVolume float64
MusicVolume float64
}
func NewAudioPlayer(resources ui.Resources, prefix string) *AudioPlayer {
var rate = beep.SampleRate(48000)
speaker.Init(rate, rate.N(time.Second/20))
return &AudioPlayer{
resources: resources,
prefix: prefix,
SampleRate: rate,
Samples: map[string]Sample{},
SampleVolume: 1,
MusicVolume: 1,
}
}
func (p *AudioPlayer) LoadSample(name ...string) error {
for _, name := range name {
if _, err := p.openSample(name); err != nil {
return err
}
}
return nil
}
func (p *AudioPlayer) openSample(name string) (Sample, error) {
sample, ok := p.Samples[name]
if ok {
return sample, nil
}
stream, format, err := p.openResource(name)
if err != nil {
return Sample{}, err
}
defer stream.Close()
buffer := beep.NewBuffer(format)
buffer.Append(stream)
sample = Sample{Buffer: buffer, SampleRate: format.SampleRate}
p.Samples[name] = sample
return sample, nil
}
func (p *AudioPlayer) PlaySample(name string) error {
sample, err := p.openSample(name)
if err != nil {
return err
}
speaker.Play(p.resample(sample.Stream(), sample.SampleRate))
return nil
}
func (p *AudioPlayer) resample(stream beep.Streamer, sampleRate beep.SampleRate) beep.Streamer {
return Resample(stream, p.SampleRate, sampleRate)
}
func (p *AudioPlayer) PlayMusic(name string, init func(*Music)) (*Music, error) {
stream, format, err := p.openResource(name)
if err != nil {
return nil, err
}
closer := &streamCloser{stream}
music := &Music{
Name: name,
AutoRepeat: false,
Stream: closer,
Volume: &effects.Volume{
Streamer: p.resample(closer, format.SampleRate),
Base: 2,
Volume: 1,
Silent: false,
},
}
if init != nil {
init(music)
}
speaker.Play(beep.Seq(music.Volume, beep.Callback(func() { go music.Finished(p) })))
return music, nil
}
func (p *AudioPlayer) openResource(name string) (beep.StreamSeekCloser, beep.Format, error) {
path := fmt.Sprintf("%s%s", p.prefix, name)
audio, err := p.resources.OpenResource(path)
if err != nil {
return nil, beep.Format{}, err
}
stream, format, err := mp3.Decode(audio)
if err != nil {
return nil, beep.Format{}, err
}
return stream, format, nil
}
type Music struct {
Name string
AutoRepeat bool
Stream beep.StreamCloser
Volume *effects.Volume
OnFinished func()
}
func (m *Music) Stop() {
m.AutoRepeat = false
m.Stream.Close()
}
func (m Music) Finished(player *AudioPlayer) {
m.Stream.Close()
if m.AutoRepeat {
player.PlayMusic(m.Name, func(m *Music) { m.AutoRepeat = true })
}
onFinished := m.OnFinished
if onFinished != nil {
onFinished()
}
}
type Sample struct {
*beep.Buffer
SampleRate beep.SampleRate
}
func (s Sample) Stream() beep.Streamer {
return s.Buffer.Streamer(0, s.Buffer.Len())
}
type streamCloser struct {
beep.StreamCloser
}
func (c *streamCloser) Err() error {
if c.StreamCloser == nil {
return io.EOF
}
return c.StreamCloser.Err()
}
func (c *streamCloser) Stream(samples [][2]float64) (n int, ok bool) {
if c.StreamCloser == nil {
return 0, false
}
return c.StreamCloser.Stream(samples)
}
func (c *streamCloser) Close() error {
c.StreamCloser = nil
return nil
}
func Resample(stream beep.Streamer, expected, actual beep.SampleRate) beep.Streamer {
if expected == actual {
return stream
}
return beep.Resample(3, actual, expected, stream)
}

View File

@ -0,0 +1,64 @@
package main
import (
"image"
"image/color"
"image/png"
"math/rand"
"os"
"github.com/llgcode/draw2d/draw2dimg"
"github.com/nfnt/resize"
"golang.org/x/image/draw"
"opslag.de/schobers/geom"
"opslag.de/schobers/tins2021"
"opslag.de/schobers/zntg/ui"
)
func GenerateBackground(resources ui.Resources, path string) error {
const cubeTextureWidth = 100
cube := resize.Resize(cubeTextureWidth, 0, tins2021.GenerateCube(tins2021.Blue), resize.Bilinear)
inverted := resize.Resize(cubeTextureWidth, 0, tins2021.GenerateHole(tins2021.Blue), resize.Bilinear)
const twelfth = (1. / 6) * geom.Pi
centerTopSquare := geom.PtF(.5, .5*geom.Sin(twelfth))
delta := geom.PtF(geom.Cos(twelfth), .5+centerTopSquare.Y).Mul(cubeTextureWidth).Mul(.5).ToInt().Mul(2)
bounds := image.Rect(2560, 1440, 0, 0)
im := image.NewRGBA(bounds)
var odd bool
for y := -cubeTextureWidth; y < bounds.Max.Y; y += delta.Y {
left := -cubeTextureWidth
if odd {
left += delta.X / 2
}
for x := left; x < bounds.Max.X; x += delta.X {
currentCube := cube
n := rand.Intn(2)
if n == 0 {
currentCube = inverted
}
draw.Copy(im, image.Pt(x, y), currentCube, image.Rect(0, 0, cubeTextureWidth, cubeTextureWidth), draw.Over, nil)
}
odd = !odd
}
ctx := draw2dimg.NewGraphicContext(im)
font, err := parseTitleFont(resources)
if err != nil {
return err
}
setDraw2DFont(ctx, font)
ctx.SetFontSize(224)
const text = "QBITTER"
center := draw2DCenterString(ctx, text)
ctx.SetFillColor(color.White)
ctx.FillStringAt(text, .5*float64(bounds.Dx())+center.X, .5*float64(bounds.Dy())+center.Y)
out, err := os.Create(path)
if err != nil {
return err
}
return png.Encode(out, im)
}

View File

@ -0,0 +1,47 @@
package main
import (
"io/ioutil"
"github.com/golang/freetype/truetype"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/ui"
)
func draw2DCenterString(ctx *draw2dimg.GraphicContext, s string) geom.PointF {
left, top, right, bottom := ctx.GetStringBounds(s)
return geom.PtF(-.5*(right-left), .5*(bottom-top))
}
type draw2DFontCache struct{ *truetype.Font }
func (f draw2DFontCache) Load(draw2d.FontData) (*truetype.Font, error) { return f.Font, nil }
func (draw2DFontCache) Store(draw2d.FontData, *truetype.Font) {}
func parseTrueTypeFont(resources ui.Resources, name string) (*truetype.Font, error) {
ttf, err := resources.OpenResource(name)
if err != nil {
return nil, err
}
defer ttf.Close()
data, err := ioutil.ReadAll(ttf)
if err != nil {
return nil, err
}
return truetype.Parse(data)
}
func parseScoreFont(resources ui.Resources) (*truetype.Font, error) {
return parseTrueTypeFont(resources, "resources/fonts/FiraMono-Regular.ttf")
}
func parseTitleFont(resources ui.Resources) (*truetype.Font, error) {
return parseTrueTypeFont(resources, "resources/fonts/escher.ttf")
}
func setDraw2DFont(ctx *draw2dimg.GraphicContext, font *truetype.Font) {
ctx.FontCache = draw2DFontCache{font}
// ctx.SetFont(font) // is ignored anyway
}

View File

@ -91,7 +91,7 @@ func (r *levelController) updateHighscore() bool {
if highscore {
r.app.Score.Highscores = highscores
}
r.app.Score.Current = tins2021.Score{} // reset score
r.app.ResetCurrentScore()
return highscore
}
@ -101,7 +101,7 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
switch e.Key {
case ui.KeyEscape:
if r.Level.StarsCollected == r.Level.Stars {
r.app.Score.Current = tins2021.NewScore(r.Level.Score, r.Level.Difficulty+1)
r.app.SetCurrentScore(r.Level)
}
r.app.ShowMainMenu(ctx)
}
@ -116,20 +116,42 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
case *ui.KeyDownEvent:
switch e.Key {
case ui.KeyEnter:
r.app.Score.Current = tins2021.NewScore(r.Level.Score, r.Level.Difficulty+1)
r.app.SetCurrentScore(r.Level)
r.app.PlayNext(ctx, r)
}
}
return false
}
checkGameOver := func() {
if r.Level.GameOver {
r.Highscore = r.updateHighscore()
if r.Highscore {
r.app.Audio.PlaySample("level_new_high_score.mp3")
} else {
r.app.Audio.PlaySample("level_game_over.mp3")
}
}
}
switch e := e.(type) {
case *ui.KeyDownEvent:
dir, ok := r.Controls[e.Key]
if ok {
stars, lives := r.Level.StarsCollected, r.Level.Lives
r.Level.MovePlayer(dir)
if r.Level.GameOver {
r.Highscore = r.updateHighscore()
switch {
case r.Level.StarsCollected > stars:
r.app.Audio.PlaySample("player_collect_star.mp3")
case r.Level.Lives < lives:
r.app.Audio.PlaySample("player_hurt.mp3")
case r.Level.Lives > lives:
r.app.Audio.PlaySample("player_collect_heart.mp3")
}
r.app.Audio.PlaySample("player_move.mp3")
checkGameOver()
if r.Level.StarsCollected == r.Level.Stars {
r.app.Audio.PlaySample("level_completed.mp3")
}
}
}
@ -149,9 +171,8 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
r.Level.DestroyMonster(pos)
jumped = append(jumped, pos)
r.Level.DecrementLive()
if r.Level.GameOver {
r.Highscore = r.updateHighscore()
}
r.app.Audio.PlaySample("player_hurt.mp3")
checkGameOver()
continue
}
if animation.Frame < 50 { // after 50 frames the animation has finished
@ -177,6 +198,7 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
r.Level.MonsterTargets[pos] = target
r.MovingMonsters.Frame(pos)
jumping = append(jumping, pos)
r.app.Audio.PlaySample("monster_jump.mp3")
break
}
}

View File

@ -29,6 +29,8 @@ func (c *center) Arrange(ctx ui.Context, bounds geom.RectangleF32, offset geom.P
type mainMenu struct {
ui.StackPanel
music *Music
}
func newMainMenu(app *appContext, ctx ui.Context) ui.Control {
@ -37,7 +39,8 @@ func newMainMenu(app *appContext, ctx ui.Context) ui.Control {
menu.Add("Play", func(ctx ui.Context) {
app.Play(ctx)
})
if app.Score.Current.Difficulty > 0 {
resume := app.Score.Current.Difficulty > 0
if resume {
menu.Add("Resume", func(ctx ui.Context) { app.PlayResume(ctx) })
}
menu.Add("Highscores", func(ctx ui.Context) { app.ShowHighscores(ctx) })
@ -45,7 +48,14 @@ func newMainMenu(app *appContext, ctx ui.Context) ui.Control {
menu.Add("Credits", func(ctx ui.Context) { app.ShowCredits(ctx) })
menu.Add("Quit", func(ctx ui.Context) { ctx.Quit() })
menu.Activate(1) // play
if resume {
menu.Activate(2) // resume
} else {
menu.Activate(1) // play
}
menu.ActiveChanged.AddHandlerEmpty((func(ui.Context) {
app.MenuInteraction()
}))
ctx.Animate()
return Center(&mainMenu{

View File

@ -11,6 +11,8 @@ type Menu struct {
active int
buttons []*MenuButton
ActiveChanged ui.Events
}
func NewMenu() *Menu {
@ -25,7 +27,7 @@ func (m *Menu) Activate(i int) {
if len(m.buttons) == 0 || i < 0 {
return
}
m.updateActiveButton(i % len(m.buttons))
m.updateActiveButton(nil, i%len(m.buttons))
}
func (m *Menu) Add(text string, click func(ui.Context)) {
@ -54,29 +56,33 @@ func (m *Menu) Handle(ctx ui.Context, e ui.Event) bool {
onEscape(ctx)
}
case ui.KeyDown:
m.updateActiveButton((m.active + 1) % len(m.buttons))
m.updateActiveButton(ctx, (m.active+1)%len(m.buttons))
case ui.KeyUp:
m.updateActiveButton((m.active + len(m.buttons) - 1) % len(m.buttons))
m.updateActiveButton(ctx, (m.active+len(m.buttons)-1)%len(m.buttons))
case ui.KeyEnter:
m.buttons[m.active].InvokeClick(ctx)
}
case *ui.MouseMoveEvent:
for i, button := range m.buttons {
if button.IsOver() {
m.updateActiveButton(i)
m.updateActiveButton(ctx, i)
break
}
}
m.updateActiveButton(m.active)
m.updateActiveButton(ctx, m.active)
}
return false
}
func (m *Menu) updateActiveButton(active int) {
func (m *Menu) updateActiveButton(ctx ui.Context, active int) {
change := m.active != active
m.active = active
for i, btn := range m.buttons {
btn.Over = i == m.active
}
if change && ctx != nil {
m.ActiveChanged.Notify(ctx, nil)
}
}
type MenuButton struct {

View File

@ -4,10 +4,8 @@ import (
"fmt"
"image"
"image/color"
"io/ioutil"
"github.com/golang/freetype/truetype"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
"opslag.de/schobers/geom"
"opslag.de/schobers/tins2021"
@ -53,21 +51,15 @@ func drawKey(ctx *draw2dimg.GraphicContext, font *truetype.Font, center geom.Poi
ctx.Close()
ctx.Stroke()
ctx.FontCache = fontCache{font}
ctx.SetFont(font)
setDraw2DFont(ctx, font)
ctx.SetFontSize(keyHeight_5)
text := fmt.Sprintf("%c", key)
textLeft, textTop, textRight, textBottom := ctx.GetStringBounds(text)
textX, textY := skewed(-.5*(textRight-textLeft), .5*(textBottom-textTop))
textCenter := draw2DCenterString(ctx, text)
textX, textY := skewed(textCenter.X, textCenter.Y)
ctx.FillStringAt(text, textX, textY)
}
type fontCache struct{ *truetype.Font }
func (f fontCache) Load(draw2d.FontData) (*truetype.Font, error) { return f.Font, nil }
func (fontCache) Store(draw2d.FontData, *truetype.Font) {}
func generateArrowKeys(resources ui.Resources) image.Image {
return generateKeys(resources,
keyboardLayoutKey{Position: geom.PtF(.53, .25), Key: '↑'},
@ -106,7 +98,7 @@ func generateKeys(resources ui.Resources, keys ...keyboardLayoutKey) image.Image
im := image.NewRGBA(image.Rect(0, 0, keyboardLayoutTextureWidth, keyboardLayoutTextureHeight))
ctx := draw2dimg.NewGraphicContext(im)
font, err := parseFont(resources)
font, err := parseScoreFont(resources)
if err != nil {
panic(err)
}
@ -138,19 +130,6 @@ type keyboardLayoutKey struct {
Highlight bool
}
func parseFont(resources ui.Resources) (*truetype.Font, error) {
ttf, err := resources.OpenResource("resources/fonts/FiraMono-Regular.ttf")
if err != nil {
return nil, err
}
defer ttf.Close()
data, err := ioutil.ReadAll(ttf)
if err != nil {
return nil, err
}
return truetype.Parse(data)
}
type settings struct {
ui.StackPanel
@ -228,6 +207,7 @@ func (s *settings) Handle(ctx ui.Context, e ui.Event) bool {
switch e.Key {
case ui.KeyEscape:
s.SelectingCustom = 0
s.app.MenuInteraction()
return true
}
key, ok := supportedCustomKeys[e.Key]
@ -250,33 +230,88 @@ func (s *settings) Handle(ctx ui.Context, e ui.Event) bool {
s.SelectedLayout = 2
s.app.Settings.Controls.Type = controlsTypeCustom
}
s.app.MenuInteraction()
}
return true
}
switch e.Key {
case ui.KeyEscape:
s.app.ShowMainMenu(ctx)
s.app.MenuInteraction()
return true
case ui.KeyLeft:
s.ActiveLayout = (s.ActiveLayout + 2) % 3
s.setActiveLayout(s.ActiveLayout - 1)
case ui.KeyRight:
s.ActiveLayout = (s.ActiveLayout + 1) % 3
s.setActiveLayout(s.ActiveLayout + 1)
case ui.KeyEnter:
switch s.ActiveLayout {
case 0:
s.SelectedLayout = 0
s.app.Settings.Controls.Type = controlsTypeWASD
case 1:
s.SelectedLayout = 1
s.app.Settings.Controls.Type = controlsTypeArrows
case 2:
s.SelectingCustom = 1
s.selectLayout()
}
case *ui.MouseMoveEvent:
if s.SelectingCustom == 0 {
layout := s.isOverLayout(ctx, e.Pos())
if layout > -1 {
s.setActiveLayout(layout)
}
}
case *ui.MouseButtonDownEvent:
if s.SelectingCustom == 0 {
if e.Button == ui.MouseButtonLeft {
layout := s.isOverLayout(ctx, e.Pos())
if layout > -1 {
s.setActiveLayout(layout) // to be sure, mouse move should've set this already
s.selectLayout()
}
}
}
}
return false
}
func (s *settings) selectLayout() {
s.app.MenuInteraction()
switch s.ActiveLayout {
case 0:
s.SelectedLayout = 0
s.app.Settings.Controls.Type = controlsTypeWASD
case 1:
s.SelectedLayout = 1
s.app.Settings.Controls.Type = controlsTypeArrows
case 2:
s.SelectingCustom = 1
}
}
func (s *settings) isOverLayout(ctx ui.Context, mouse geom.PointF32) int {
bounds := s.Bounds()
center := bounds.Center()
width := bounds.Dx()
scale := tins2021.FindScaleRound(keyboardLayoutTextureWidth, .28*width)
font := ctx.Fonts().Font("default")
for i := 0; i < 3; i++ {
left := (.04 + .32*float32(i)) * width
right := left + .28*width
top := center.Y - 2*font.Height()
bottom := center.Y + scale*keyboardLayoutTextureWidth
if mouse.In(geom.RectF32(left, top, right, bottom)) {
return i
}
}
return -1
}
func (s *settings) setActiveLayout(layout int) {
layout = (layout + 3) % 3
change := layout != s.ActiveLayout
s.ActiveLayout = (layout + 3) % 3
if change {
s.app.MenuInteraction()
}
}
func (s *settings) Render(ctx ui.Context) {
bounds := s.Bounds()
center := bounds.Center()

View File

@ -29,14 +29,20 @@ func openResources() ui.Resources {
}
func run() error {
var background string
var extract bool
flag.StringVar(&background, "background", "", "generates a background")
flag.BoolVar(&extract, "extract", false, "extracts all resources to the current working directory")
flag.Parse()
if extract {
return copyBoxToDisk(resources)
}
res := openResources()
if background != "" {
return GenerateBackground(res, background)
}
ptPtr := func(x, y int) *geom.Point {
p := geom.Pt(x, y)

View File

@ -57,8 +57,9 @@ func (s *Score) Validate() bool {
}
type ScoreState struct {
Current Score
Highscores Highscores
Current Score
CurrentLives int
Highscores Highscores
}
func LoadScores() (ScoreState, error) {

1
scripts/install.bat Normal file
View File

@ -0,0 +1 @@
go build -tags static -ldflags "-s -w" -o "%GOPATH%/bin/qbitter.exe" opslag.de/schobers/tins2021/cmd/tins2021