package game import ( "fmt" "image" "log/slog" "openquell/config" "openquell/observers" "github.com/hajimehoshi/ebiten/v2" "github.com/mlange-42/arche/ecs" input "github.com/quasilyte/ebitengine-input" ) type Game struct { World *ecs.World Bounds image.Rectangle ScreenWidth, ScreenHeight, Cellsize int Scenes map[SceneName]Scene CurrentScene SceneName Observer *observers.GameObserver Levels []*Level // fed in PlayScene.GenerateLevels() Config *config.Config InputSystem input.System Input *input.Handler } func NewGame(width, height, cellsize int, cfg *config.Config, startscene SceneName) *Game { world := ecs.NewWorld() game := &Game{ Bounds: image.Rectangle{}, World: &world, ScreenWidth: width, ScreenHeight: height, Scenes: map[SceneName]Scene{}, Cellsize: cellsize, Config: cfg, } game.Observer = observers.NewGameObserver( &world, cfg.Startlevel, width, height, cellsize) game.InputSystem.Init(input.SystemConfig{ DevicesEnabled: input.AnyDevice, }) game.Input = game.InputSystem.NewHandler(0, game.Config.Keymap) game.Scenes[Welcome] = NewWelcomeScene(game) game.Scenes[Menu] = NewMenuScene(game) game.Scenes[About] = NewAboutScene(game) game.Scenes[Popup] = NewPopupScene(game) game.Scenes[Play] = NewPlayScene(game, cfg.Startlevel) game.Scenes[Select] = NewSelectScene(game) game.CurrentScene = startscene fmt.Println(game.World.Stats().String()) return game } func (game *Game) GetCurrentScene() Scene { return game.Scenes[game.CurrentScene] } func (game *Game) Update() error { // update ebitengine-input game.InputSystem.Update() // handle level ends 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", game.Observer.Lost, "retry", game.Observer.Retry) game.Observer.AddScore() game.Scenes[Nextlevel] = NewNextlevelScene(game) game.CurrentScene = Nextlevel } scene := game.GetCurrentScene() scene.Update() if scene.Clearscreen() { ebiten.SetScreenClearedEveryFrame(true) } else { ebiten.SetScreenClearedEveryFrame(false) } next := scene.GetNext() 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 !game.Observer.Retry { game.Observer.CurrentLevel++ } game.Observer.Retry = false } if next == Play { // fresh setup of actual level every time we enter the play scene game.Scenes[Play].SetLevel(game.Observer.CurrentLevel) } // make sure we stay on the selected scene scene.ResetNext() // finally switch game.CurrentScene = next // FIXME: add some reset function to game.Observer for these kinds of things game.Observer.Lost = false } timer.Update() return nil } func (game *Game) Draw(screen *ebiten.Image) { game.GetCurrentScene().Draw(screen) } func (g *Game) Layout(newWidth, newHeight int) (int, int) { return g.ScreenWidth, g.ScreenHeight }