added MinMoves to lvl files, added score system in game observer

This commit is contained in:
Thomas von Dein 2024-02-28 19:58:05 +01:00
parent ebaeb51f68
commit 1c0f3d19d0
19 changed files with 112 additions and 78 deletions

12
TODO.md
View File

@ -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

View File

@ -1,4 +1,5 @@
Description: test obstacles
MinMoves: 2
Background: background-lila

View File

@ -1,4 +1,5 @@
Description: test obstacles
MinMoves: 14
Background: background-lila

View File

@ -1,4 +1,5 @@
Description: find the fruit
MinMoves: 7
Background: background-lila

View File

@ -1,17 +1,18 @@
Description: win
MinMoves: 5
Background: background-lila
########
#v o #
# S<# #
# o <o#
#v S # #
# #### #
# #### #
# #
#> ^#
# ^#
########

View File

@ -1,4 +1,5 @@
Description: use multiple players
MinMoves: 4
Background: background-lila

View File

@ -1,4 +1,5 @@
Description: space
MinMoves: 3
Background: background-lila

View File

@ -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,
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -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
}

View File

@ -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()

View File

@ -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) {

View File

@ -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),
)

View File

@ -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
}

Binary file not shown.

View File

@ -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())
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}
}
}