added MinMoves to lvl files, added score system in game observer
This commit is contained in:
parent
ebaeb51f68
commit
1c0f3d19d0
12
TODO.md
12
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
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
Description: test obstacles
|
||||
MinMoves: 2
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
Description: test obstacles
|
||||
MinMoves: 14
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
Description: find the fruit
|
||||
MinMoves: 7
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
Description: win
|
||||
MinMoves: 5
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
|
||||
########
|
||||
#v o #
|
||||
# S<# #
|
||||
# o <o#
|
||||
#v S # #
|
||||
# #### #
|
||||
|
||||
|
||||
# #### #
|
||||
# #
|
||||
#> ^#
|
||||
# ^#
|
||||
########
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
Description: use multiple players
|
||||
MinMoves: 4
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
Description: space
|
||||
MinMoves: 3
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
BIN
assets/sprites/background-popup-wide.png
Normal file
BIN
assets/sprites/background-popup-wide.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@ -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
|
||||
}
|
||||
|
||||
|
||||
34
game/game.go
34
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()
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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),
|
||||
)
|
||||
|
||||
|
||||
@ -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"
|
||||
@ -20,29 +19,29 @@ type Score struct {
|
||||
// initialization in grid/grid.go
|
||||
type GameObserver struct {
|
||||
World *ecs.World
|
||||
CurrentLevel, Width int
|
||||
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 []Score // one score per level
|
||||
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) {
|
||||
// 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.
@ -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())
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user