package game import ( "context" "fmt" "log" "math/rand" "os" "strconv" "sync" "time" "git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation" "git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command" "git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/types" "git.wisellama.rocks/Project-Ely/project-ely/internal/game/player" "git.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite" "git.wisellama.rocks/Project-Ely/project-ely/internal/game/window" "git.wisellama.rocks/Project-Ely/project-ely/internal/vector" "git.wisellama.rocks/Wisellama/gosimpleconf" "github.com/faiface/beep" "github.com/faiface/beep/effects" "github.com/faiface/beep/flac" "github.com/faiface/beep/speaker" "github.com/veandco/go-sdl2/sdl" ) type EntityCmdHandler interface { Run() CloseRequests() CommandRequest() chan command.Command DrawRequest() chan bool DrawResponse() chan bool UpdateRequest() chan bool UpdateResponse() chan bool } // Run is the main function to start the game. func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error { var err error ctx, cancel := context.WithCancel(ctx) defer cancel() go musicTest() framerate, err := strconv.ParseFloat(configMap["game.framerate"], 64) if err != nil { err = fmt.Errorf("error parsing framerate: %w", err) return err } framerateDelay := uint32(1000 / framerate) err = window.SdlInit() if err != nil { return err } defer window.SdlQuit() gameWindow, err := window.NewWindow(configMap["game.title"]) if err != nil { err = fmt.Errorf("failed creating GameWindow: %w", err) return err } defer gameWindow.Cleanup() var renderer *sdl.Renderer sdl.Do(func() { renderer, err = sdl.CreateRenderer(gameWindow.SdlWindow, -1, sdl.RENDERER_ACCELERATED) }) if err != nil { err = fmt.Errorf("failed creating SDL renderer: %w", err) return err } defer func() { sdl.Do(func() { err = renderer.Destroy() if err != nil { log.Printf("error destroying renderer: %v\n", err) } }) }() err = sprite.InitSpriteCache(renderer) if err != nil { err = fmt.Errorf("failed in InitSpriteCache: %w", err) return err } animation.DefineAnimations() inputHandler := window.NewInputHandler(ctx) // Done with main setup, now moving on to creating specific entities entityList := make([]EntityCmdHandler, 0) wg := sync.WaitGroup{} // Setup Player 1 // Let them control a penguin to start with player1 := player.NewPlayer(ctx, inputHandler) penguinEntity := types.NewPenguin(renderer) penguinCmdHandler := command.NewCommandHandler(ctx, &penguinEntity) penguinEntity.SetSpeed(2.0) entityList = append(entityList, penguinCmdHandler) player1.SetEntityChan(penguinCmdHandler.CommandRequest()) wg.Add(1) go func() { defer wg.Done() penguinCmdHandler.Run() }() for i := 0; i < 10; i++ { entity := types.NewPenguin(renderer) entityCmd := command.NewCommandHandler(ctx, &entity) randomPos := vector.Vec2F{X: rand.Float64() * 500, Y: rand.Float64() * -500} entity.SetPosition(randomPos) entity.SetAnimation(rand.Intn(animation.PENGUIN_NUM_ANIMS)) entityList = append(entityList, entityCmd) wg.Add(1) go func() { defer wg.Done() entityCmd.Run() }() } // Start the inputHandler wg.Add(1) go func() { defer wg.Done() inputHandler.Run() }() // And now starting the main loop running := true for running { // Poll for SDL events var event sdl.Event sdl.Do(func() { event = sdl.PollEvent() for event != nil { switch e := event.(type) { case *sdl.QuitEvent: log.Println("QuitEvent quitting") cancel() case *sdl.KeyboardEvent: if e.Keysym.Sym == sdl.K_ESCAPE { log.Println("Esc quitting") cancel() } else { // Publish the event to the inputHandler inputHandler.KeyboardChan() <- *e } } event = sdl.PollEvent() } }) // Allow us to exit early if the context is done select { case <-ctx.Done(): running = false default: // Keep running } if !running { break } // Players player1.Update() // Background sdl.Do(func() { err = renderer.SetDrawColor(0, 120, 0, 255) if err != nil { log.Printf("error in renderer.SetDrawColor: %v\n", err) } err = renderer.Clear() if err != nil { log.Printf("error in renderer.Clear: %v\n", err) } }) // Tell everything to Update and Draw for _, e := range entityList { e.UpdateRequest() <- true e.DrawRequest() <- true } // Wait for each entity to finish their Draw and Update commands before proceeding for _, e := range entityList { <-e.UpdateResponse() <-e.DrawResponse() } // Draw sdl.Do(func() { renderer.Present() sdl.Delay(framerateDelay) }) } inputHandler.CloseChannels() for _, e := range entityList { e.CloseRequests() } sprite.CleanupSpriteCache() wg.Wait() return nil } func musicTest() { f, err := os.Open("assets/audio/test.flac") if err != nil { log.Printf("%v", err) } s, format, err := flac.Decode(f) if err != nil { log.Printf("%v", err) } speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) done := make(chan struct{}) callbackFunc := func() { close(done) } notify := beep.Callback(callbackFunc) v := &effects.Volume{ Streamer: s, Base: 2, Volume: -5, Silent: false, } v = beep.Loop(-1, s) speaker.Play(beep.Seq(v, notify)) select { case <-done: break } }