diff --git a/TODO.md b/TODO.md index dee2d07..e9c4202 100644 --- a/TODO.md +++ b/TODO.md @@ -20,6 +20,18 @@ - reset counters when new level starts - display level score in level select +- for finding caller: + pc := make([]uintptr, 10) + n := runtime.Callers(0, pc) + pc = pc[:n] + fs := runtime.CallersFrames(pc) + source, _ := fs.Next() + source, _ = fs.Next() + source, _ = fs.Next() + + slog.Debug("get observer", "minmoves", observer.LevelScore, + "file", source.File, "line", source.Line) + ## Collider Rework - do not use the map anymore for collision detection diff --git a/assets/levels/00-deadly-obstacles.lvl b/assets/levels/00-deadly-obstacles.lvl index 41dafbc..44cdc5f 100644 --- a/assets/levels/00-deadly-obstacles.lvl +++ b/assets/levels/00-deadly-obstacles.lvl @@ -1,4 +1,5 @@ Description: test obstacles +MinMoves: 2 Background: background-lila diff --git a/assets/levels/01-friendly-obstacles.lvl b/assets/levels/01-friendly-obstacles.lvl index 3eee36a..3e0322e 100644 --- a/assets/levels/01-friendly-obstacles.lvl +++ b/assets/levels/01-friendly-obstacles.lvl @@ -1,4 +1,5 @@ Description: test obstacles +MinMoves: 14 Background: background-lila diff --git a/assets/levels/02-start.lvl b/assets/levels/02-start.lvl index ec64d47..2a0f073 100644 --- a/assets/levels/02-start.lvl +++ b/assets/levels/02-start.lvl @@ -1,4 +1,5 @@ Description: find the fruit +MinMoves: 7 Background: background-lila diff --git a/assets/levels/03-own.lvl b/assets/levels/03-own.lvl index 5d50ad3..489b08e 100644 --- a/assets/levels/03-own.lvl +++ b/assets/levels/03-own.lvl @@ -1,17 +1,18 @@ Description: win +MinMoves: 5 Background: background-lila ######## - #v o # - # S<# # + # o ^# + # ^# ######## diff --git a/assets/levels/04-mayhem.lvl b/assets/levels/04-mayhem.lvl index ebd53b6..6d27a36 100644 --- a/assets/levels/04-mayhem.lvl +++ b/assets/levels/04-mayhem.lvl @@ -1,4 +1,5 @@ Description: use multiple players +MinMoves: 4 Background: background-lila diff --git a/assets/levels/05-space.lvl b/assets/levels/05-space.lvl index 42a4c26..2078884 100644 --- a/assets/levels/05-space.lvl +++ b/assets/levels/05-space.lvl @@ -1,4 +1,5 @@ Description: space +MinMoves: 3 Background: background-lila diff --git a/assets/loader-levels.go b/assets/loader-levels.go index 5defb2a..7e962c1 100644 --- a/assets/loader-levels.go +++ b/assets/loader-levels.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" "sort" + "strconv" "strings" "github.com/hajimehoshi/ebiten/v2" @@ -204,6 +205,7 @@ type RawLevel struct { Description string Background *ebiten.Image Data []byte + MinMoves int } func InitTiles() TileRegistry { @@ -272,6 +274,7 @@ func ParseRawLevel(dir string, levelfile fs.DirEntry) RawLevel { des := "" background := &ebiten.Image{} data := []byte{} + minmoves := 0 scanner := bufio.NewScanner(fd) for scanner.Scan() { @@ -292,6 +295,12 @@ func ParseRawLevel(dir string, levelfile fs.DirEntry) RawLevel { case strings.Contains(line, "Description:"): haveit := strings.Split(line, ": ") des = haveit[1] + case strings.Contains(line, "MinMoves:"): + haveit := strings.Split(line, ": ") + minmoves, err = strconv.Atoi(haveit[1]) + if err != nil { + log.Fatal("Failed to convert MinMoves to int: %w", err) + } default: // all other non-empty and non-equalsign lines are // level definition matrix data, merge thes into data @@ -304,5 +313,6 @@ func ParseRawLevel(dir string, levelfile fs.DirEntry) RawLevel { Data: data, Background: background, Description: des, + MinMoves: minmoves, } } diff --git a/assets/sprites/background-popup-wide.png b/assets/sprites/background-popup-wide.png new file mode 100644 index 0000000..77d4612 Binary files /dev/null and b/assets/sprites/background-popup-wide.png differ diff --git a/components/position.go b/components/position.go index f8c549f..f9b5d2f 100644 --- a/components/position.go +++ b/components/position.go @@ -4,8 +4,6 @@ import ( "fmt" "image" . "openquell/config" - - "log/slog" ) // physical location on screen @@ -105,13 +103,13 @@ func (tile *Position) Intersects(moving *Position, velocity *Velocity) (bool, *P object.Update(tile.X, tile.Rect.Max.Y) } - slog.Debug("Intersect", - "velocity", velocity.Data, - "player", moving, - "moved player", object, - "collision at", is, - "tilepos", tile, - ) + // slog.Debug("Intersect", + // "velocity", velocity.Data, + // "player", moving, + // "moved player", object, + // "collision at", is, + // "tilepos", tile, + // ) return true, object } diff --git a/game/game.go b/game/game.go index f721bc9..2d187d6 100644 --- a/game/game.go +++ b/game/game.go @@ -18,7 +18,7 @@ type Game struct { Scenes map[SceneName]Scene CurrentScene SceneName Observer *observers.GameObserver - //Levels []*Level // fed in LevelScene.GenerateLevels() + Levels []*Level // fed in LevelScene.GenerateLevels() Config *config.Config } @@ -35,6 +35,9 @@ func NewGame(width, height, cellsize int, cfg *config.Config, startscene SceneNa Config: cfg, } + game.Observer = observers.NewGameObserver( + &world, cfg.Startlevel, width, height, cellsize) + game.Scenes[Welcome] = NewWelcomeScene(game) game.Scenes[Menu] = NewMenuScene(game) game.Scenes[About] = NewAboutScene(game) @@ -44,9 +47,6 @@ func NewGame(width, height, cellsize int, cfg *config.Config, startscene SceneNa game.CurrentScene = startscene - game.Observer = observers.NewGameObserver( - &world, cfg.Startlevel, len(game.Levels), width, height, cellsize) - fmt.Println(game.World.Stats().String()) return game @@ -57,24 +57,18 @@ func (game *Game) GetCurrentScene() Scene { } func (game *Game) Update() error { - gameobserver := observers.GetGameObserver(game.World) - // handle level ends - timer := gameobserver.StopTimer + timer := game.Observer.StopTimer if timer.IsReady() { // a level is either lost or won, we display a small popup // asking the user how to continue from here timer.Reset() - slog.Debug("timer ready", "lost", gameobserver.Lost, "retry", gameobserver.Retry) - if !gameobserver.Lost { - level := // current level? - score := ((level.MinMoves * 100) / gameobserver.Moves) / 30 - gameobserver.AddScore() - } + slog.Debug("timer ready", "lost", game.Observer.Lost, "retry", game.Observer.Retry) + game.Observer.AddScore() - game.Scenes[Nextlevel] = NewNextlevelScene(game, gameobserver.Lost) + game.Scenes[Nextlevel] = NewNextlevelScene(game) game.CurrentScene = Nextlevel } @@ -91,15 +85,15 @@ func (game *Game) Update() error { if next != game.CurrentScene { if next == Play && game.CurrentScene == Nextlevel { // switched from nextlevel (lost or won) popup to play (either retry or next level) - if !gameobserver.Retry { - gameobserver.CurrentLevel++ + if !game.Observer.Retry { + game.Observer.CurrentLevel++ } - gameobserver.Retry = false + game.Observer.Retry = false } if next == Play { // fresh setup of actual level every time we enter the play scene - game.Scenes[Play].SetLevel(gameobserver.CurrentLevel) + game.Scenes[Play].SetLevel(game.Observer.CurrentLevel) } // make sure we stay on the selected scene @@ -108,8 +102,8 @@ func (game *Game) Update() error { // finally switch game.CurrentScene = next - // FIXME: add some reset function to gameobserver for these kinds of things - gameobserver.Lost = false + // FIXME: add some reset function to game.Observer for these kinds of things + game.Observer.Lost = false } timer.Update() diff --git a/game/level_scene.go b/game/level_scene.go index 1c8b7d8..3ebd8b4 100644 --- a/game/level_scene.go +++ b/game/level_scene.go @@ -3,7 +3,6 @@ package game import ( "log/slog" "openquell/assets" - "openquell/observers" "github.com/hajimehoshi/ebiten/v2" ) @@ -34,9 +33,8 @@ func (scene *LevelScene) GenerateLevels(game *Game) { min = append(min, level.MinMoves) } - observer := observers.GetGameObserver(scene.Game.World) - observer.SetupLevelScore(min) - //scene.Game.Levels = scene.Levels + scene.Game.Observer.SetupLevelScore(min) + scene.Game.Levels = scene.Levels } func (scene *LevelScene) SetLevel(level int) { diff --git a/game/nextlevel_scene.go b/game/nextlevel_scene.go index 96fb04e..7e47fea 100644 --- a/game/nextlevel_scene.go +++ b/game/nextlevel_scene.go @@ -1,6 +1,7 @@ package game import ( + "fmt" "image/color" "log/slog" "openquell/assets" @@ -22,12 +23,11 @@ type NextlevelScene struct { Lost bool } -func NewNextlevelScene(game *Game, lost bool) Scene { +func NewNextlevelScene(game *Game) Scene { scene := &NextlevelScene{ Whoami: Nextlevel, Game: game, Next: Nextlevel, - Lost: lost, } scene.SetupUI() @@ -61,7 +61,7 @@ func (scene *NextlevelScene) Update() error { func (scene *NextlevelScene) SetLevel(level int) {} func (scene *NextlevelScene) Draw(screen *ebiten.Image) { - background := assets.Assets["background-popup"] + background := assets.Assets["background-popup-wide"] op := &ebiten.DrawImageOptions{} op.GeoM.Translate( @@ -69,33 +69,33 @@ func (scene *NextlevelScene) Draw(screen *ebiten.Image) { float64((scene.Game.ScreenHeight/2)-(background.Bounds().Dy()/2)), ) - screen.DrawImage(assets.Assets["background-popup"], op) + screen.DrawImage(background, op) scene.Ui.Draw(screen) } func (scene *NextlevelScene) SetupUI() { blue := color.RGBA{0, 255, 128, 255} - gameobserver := observers.GetGameObserver(scene.Game.World) + observer := observers.GetGameObserver(scene.Game.World) rowContainer := gameui.NewRowContainer(false) - labeltext := "Success" + labeltext := fmt.Sprintf("Great! (Score: %d)", observer.GetLevelScore()) - switch scene.Lost { + switch observer.Lost { case true: - labeltext = "Failure" + labeltext = "Too bad!" } buttonRetry := gameui.NewMenuButton("Retry", *assets.FontRenderer.FontNormal, func(args *widget.ButtonClickedEventArgs) { scene.SetNext(Play) - gameobserver.Retry = true + observer.Retry = true }) buttonNext := gameui.NewMenuButton("Next Level", *assets.FontRenderer.FontNormal, func(args *widget.ButtonClickedEventArgs) { scene.SetNext(Play) - gameobserver.Retry = false + observer.Retry = false }) buttonAbort := gameui.NewMenuButton("Abort", *assets.FontRenderer.FontNormal, @@ -104,7 +104,7 @@ func (scene *NextlevelScene) SetupUI() { }) label := widget.NewText( - widget.TextOpts.Text(labeltext, *assets.FontRenderer.FontBig, blue), + widget.TextOpts.Text(labeltext, *assets.FontRenderer.FontNormal, blue), widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter), ) diff --git a/observers/game_observer.go b/observers/game_observer.go index 7fbc8fa..cb4790a 100644 --- a/observers/game_observer.go +++ b/observers/game_observer.go @@ -1,10 +1,9 @@ package observers import ( + "math/rand" "openquell/components" - "log/slog" - "github.com/mlange-42/arche/ecs" "github.com/mlange-42/arche/ecs/event" "github.com/mlange-42/arche/generic" @@ -19,30 +18,30 @@ type Score struct { // current level. The Entities map will be reset on each level // initialization in grid/grid.go type GameObserver struct { - World *ecs.World - CurrentLevel, Width int - Height, Cellsize, Score int - StopTimer *components.Timer - Lost bool // set to true if player is struck or something, by default: win! - Retry bool - NextlevelText string - Entities map[ecs.ID]map[ecs.Entity]int - LevelScore []Score // one score per level + World *ecs.World + CurrentLevel, Width, Moves int + Height, Cellsize, Score int + StopTimer *components.Timer + Lost bool // set to true if player is struck or something, by default: win! + Retry bool + NextlevelText string + Entities map[ecs.ID]map[ecs.Entity]int + LevelScore map[int]*Score // one score per level + Id int } func (observer *GameObserver) GetListenerCallback(comp ecs.ID) listener.Callback { return listener.NewCallback( func(world *ecs.World, event ecs.EntityEvent) { observer.RemoveEntity(event.Entity, comp) - slog.Debug("player removed") }, event.EntityRemoved, comp, ) } -func NewGameObserver(world *ecs.World, startlevel, levelcount, - width, height, cellsize int) *GameObserver { +func NewGameObserver( + world *ecs.World, startlevel, width, height, cellsize int) *GameObserver { observer := &GameObserver{ CurrentLevel: startlevel, StopTimer: &components.Timer{}, @@ -50,6 +49,7 @@ func NewGameObserver(world *ecs.World, startlevel, levelcount, Height: height, Cellsize: cellsize, World: world, + Id: rand.Intn(1000), } playerID := ecs.ComponentID[components.Player](world) @@ -73,8 +73,6 @@ func NewGameObserver(world *ecs.World, startlevel, levelcount, observer.Entities[obstacleID] = make(map[ecs.Entity]int) observer.Entities[particleID] = make(map[ecs.Entity]int) - observer.LevelScore = make([]Score, levelcount) - resmanger := generic.NewResource[GameObserver](world) resmanger.Add(observer) @@ -82,8 +80,9 @@ func NewGameObserver(world *ecs.World, startlevel, levelcount, } func GetGameObserver(world *ecs.World) *GameObserver { - observerID := ecs.ResourceID[GameObserver](world) - observer := world.Resources().Get(observerID).(*GameObserver) + resmanger := generic.NewResource[GameObserver](world) + observer := resmanger.Get() + return observer } @@ -134,14 +133,29 @@ func (observer *GameObserver) RemoveEntities() { } func (observer *GameObserver) SetupLevelScore(min []int) { + observer.LevelScore = make(map[int]*Score, len(min)) + for level, minmoves := range min { - observer.LevelScore[level].Min = minmoves + observer.LevelScore[level] = &Score{Min: minmoves} } } -// to be called from scenes where player wins or looses -func (observer *GameObserver) AddScore(level, moves int) { - observer.LevelScore[level].Score = ((observer.LevelScore[level].Min * 100) / moves) / 30 +// set current level stats and reset counters +func (observer *GameObserver) AddScore() { + level := observer.CurrentLevel + moves := observer.Moves + + if observer.Lost { + observer.LevelScore[level].Score = 0 + } else { + observer.LevelScore[level].Score = ((observer.LevelScore[level].Min * 100) / moves) / 30 + } + + observer.Moves = 0 +} + +func (observer *GameObserver) AddMove() { + observer.Moves++ } func (observer *GameObserver) GetScore() int { @@ -153,6 +167,6 @@ func (observer *GameObserver) GetScore() int { return sum } -func (observer *GameObserver) GetLevelScore(level int) int { - return observer.LevelScore[level].Score +func (observer *GameObserver) GetLevelScore() int { + return observer.LevelScore[observer.CurrentLevel].Score } diff --git a/src/background-popup.xcf b/src/background-popup.xcf index 3ee6821..cdc909b 100644 Binary files a/src/background-popup.xcf and b/src/background-popup.xcf differ diff --git a/systems/collectible_system.go b/systems/collectible_system.go index 7ad50cd..43b570d 100644 --- a/systems/collectible_system.go +++ b/systems/collectible_system.go @@ -1,7 +1,6 @@ package systems import ( - "log/slog" "openquell/assets" "openquell/components" . "openquell/components" @@ -46,7 +45,7 @@ func (system *CollectibleSystem) Update() error { } for query.Next() { - colposition, collectible, _ := query.Get() + colposition, _, _ := query.Get() for _, player := range observer.GetPlayers() { if !system.World.Alive(player) { @@ -58,7 +57,7 @@ func (system *CollectibleSystem) Update() error { ok, _ := colposition.Intersects(playerposition, playervelocity) if ok { - slog.Debug("bumped into collectible", "collectible", collectible) + //slog.Debug("bumped into collectible", "collectible", collectible) particlepositions = append(particlepositions, colposition) EntitiesToRemove = append(EntitiesToRemove, query.Entity()) } diff --git a/systems/hud_system.go b/systems/hud_system.go index 5306de4..8585ac1 100644 --- a/systems/hud_system.go +++ b/systems/hud_system.go @@ -41,4 +41,6 @@ func (system *HudSystem) Draw(screen *ebiten.Image) { system.Plan.Name, system.Plan.Description, ), 10, 10) + + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Score: %d", system.Observer.GetScore()), 10, 455) } diff --git a/systems/player_system.go b/systems/player_system.go index f724152..59b3ec8 100644 --- a/systems/player_system.go +++ b/systems/player_system.go @@ -148,6 +148,7 @@ func (system *PlayerSystem) CheckMovement( player *components.Player) { moved := false + observer := observers.GetGameObserver(system.World) if !velocity.Moving() { switch { @@ -169,6 +170,7 @@ func (system *PlayerSystem) CheckMovement( // will be reset every time the user initiates player movement player.LoopPos.Set(position) player.LoopCount = 0 + observer.AddMove() } } } @@ -185,8 +187,8 @@ func (system *PlayerSystem) CheckGridCollision( } else { ok, tilepos := system.GridContainer.Grid.GetSolidNeighborPosition(playerposition, velocity, true) if ok { - slog.Debug("HaveSolidNeighbor", "ok", ok, "tilepos", - tilepos.Point(), "playerpos", playerposition) + // slog.Debug("HaveSolidNeighbor", "ok", ok, "tilepos", + // tilepos.Point(), "playerpos", playerposition) intersects, newpos := tilepos.Intersects(playerposition, velocity) if intersects { playerposition.Set(newpos) diff --git a/systems/transient_system.go b/systems/transient_system.go index 006409c..9342ec1 100644 --- a/systems/transient_system.go +++ b/systems/transient_system.go @@ -1,7 +1,6 @@ package systems import ( - "log/slog" "openquell/assets" "openquell/components" . "openquell/components" @@ -79,7 +78,7 @@ func (system *TransientSystem) Update() error { Position: *transientposition, NewSprite: transient.GetNext(), }) - slog.Debug("done transient", "transient", transientposition) + //slog.Debug("done transient", "transient", transientposition) } } }