diff --git a/internal/controller/command.go b/internal/controller/command.go index 8d82073..d4a48d2 100644 --- a/internal/controller/command.go +++ b/internal/controller/command.go @@ -3,12 +3,21 @@ package controller import ( "strings" + "github.com/google/uuid" e "github.com/iliadenisov/galaxy/internal/error" "github.com/iliadenisov/galaxy/internal/model/game" ) +func (c Controller) RaceID(actor string) (uuid.UUID, error) { + ri, err := c.Cache.validRace(actor) + if err != nil { + return uuid.Nil, err + } + return c.Cache.g.Race[ri].ID, nil +} + func (c Controller) QuitGame(actor string) error { - ri, err := c.Cache.validActor(actor) + ri, err := c.Cache.validRace(actor) if err != nil { return err } diff --git a/internal/controller/controller.go b/internal/controller/controller.go index b7f9a8f..2ee96d0 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/google/uuid" "github.com/iliadenisov/galaxy/internal/model/game" "github.com/iliadenisov/galaxy/internal/model/report" "github.com/iliadenisov/galaxy/internal/repo" @@ -36,6 +37,9 @@ type Repo interface { // SaveReport stores latest report for a race SaveReport(uint, *report.Report) error + + // LoadReport loads report for specific turn and player id + LoadReport(uint, uuid.UUID) (*report.Report, error) } type Controller struct { @@ -47,9 +51,9 @@ type Param struct { StoragePath string } -type Config func(*Param) +type Configurer func(*Param) -func NewController(config Config) (*Controller, error) { +func NewController(config Configurer) (*Controller, error) { c := &Param{ StoragePath: ".", } diff --git a/internal/controller/controller_export_test.go b/internal/controller/controller_export_test.go index c022e2a..6191e58 100644 --- a/internal/controller/controller_export_test.go +++ b/internal/controller/controller_export_test.go @@ -31,9 +31,9 @@ func (c *Cache) AddRace(n string) (int, uuid.UUID) { return len(c.g.Race) - 1, id } -func (c *Cache) Race(i int) game.Race { +func (c *Cache) Race(i int) *game.Race { c.validateRaceIndex(i) - return c.g.Race[i] + return &c.g.Race[i] } func (c *Cache) RaceShipGroups(ri int) iter.Seq[*game.ShipGroup] { diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index 972f1d3..a9ea0bb 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -2,13 +2,10 @@ package controller_test import ( "fmt" - "slices" - "testing" "github.com/google/uuid" "github.com/iliadenisov/galaxy/internal/controller" "github.com/iliadenisov/galaxy/internal/model/game" - "github.com/stretchr/testify/assert" ) var ( @@ -103,14 +100,6 @@ var ( InSpace = game.InSpace{Origin: 2, X: floatRef(1.23), Y: floatRef(1.23)} ) -// [ ] Delete this fake test -func TestSlicesDelete(t *testing.T) { - sl := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - assert.Len(t, sl, 10) - sl = slices.DeleteFunc(sl, func(v int) bool { return v%2 == 0 }) - assert.Len(t, sl, 5) -} - func assertNoError(err error) { if err != nil { panic(fmt.Sprintf("init assertion failed: %v", err)) diff --git a/internal/controller/race_test.go b/internal/controller/race_test.go index 16b2804..2f21dd9 100644 --- a/internal/controller/race_test.go +++ b/internal/controller/race_test.go @@ -69,3 +69,19 @@ func TestQuitGame(t *testing.T) { assert.NoError(t, g.QuitGame(Race_0.Name)) assert.Equal(t, 3, int(c.Race(Race_0_idx).TTL)) } + +func TestRaceID(t *testing.T) { + c, g := newCache() + + c.Race(Race_0_idx).TTL = 9 + + _, err := g.RaceID(UnknownRace) + assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace)) + + _, err = g.RaceID(Race_Extinct.Name) + assert.ErrorContains(t, err, e.GenericErrorText(e.ErrRaceExinct)) + + id, err := g.RaceID(Race_0.Name) + assert.NoError(t, err) + assert.Equal(t, Race_0_ID, id) +} diff --git a/internal/error/generic.go b/internal/error/generic.go index 17fd8a1..fd0ae62 100644 --- a/internal/error/generic.go +++ b/internal/error/generic.go @@ -8,6 +8,7 @@ const ( ErrStorageFailure int = 1000 + iota ErrGameNotInitialized ErrGameStateInvalid + ErrReportNotFound ) const ( diff --git a/internal/error/state.go b/internal/error/state.go index dba44d8..f5d39c1 100644 --- a/internal/error/state.go +++ b/internal/error/state.go @@ -8,6 +8,10 @@ func NewGameNotInitializedError(arg ...any) error { return newGenericError(ErrGameNotInitialized, arg...) } +func NewReportNotFoundError(arg ...any) error { + return newGenericError(ErrReportNotFound, arg...) +} + func NewGameStateError(arg ...any) error { return newGenericError(ErrGameStateInvalid, arg...) } diff --git a/internal/game/controller.go b/internal/game/controller.go index 84e1ac2..697863e 100644 --- a/internal/game/controller.go +++ b/internal/game/controller.go @@ -4,6 +4,7 @@ import ( "github.com/google/uuid" "github.com/iliadenisov/galaxy/internal/controller" "github.com/iliadenisov/galaxy/internal/model/game" + "github.com/iliadenisov/galaxy/internal/model/report" ) func GenerateGame(configure func(*controller.Param), races []string) (gameID uuid.UUID, err error) { @@ -25,13 +26,19 @@ func LoadState(configure func(*controller.Param)) (g *game.Game, err error) { return } -// TODO: command for loading report by players (MUST be limited by router) -func LoadReport(configure func(*controller.Param)) (g *game.Game, err error) { +func LoadReport(configure func(*controller.Param), t uint, actor string) (g *report.Report, err error) { err = control(configure, func(c *controller.Controller) error { - return c.ExecuteState(func(r controller.Repo) error { - g, err = c.Repo.LoadState() + game, err := c.Repo.LoadStateSafe() + if err != nil { return err - }) + } + c.Cache = controller.NewCache(game) + id, err := c.RaceID(actor) + if err != nil { + return err + } + g, err = c.Repo.LoadReport(t, id) + return err }) return } diff --git a/internal/model/game/production.go b/internal/model/game/production.go index 4721e63..a5b84c0 100644 --- a/internal/model/game/production.go +++ b/internal/model/game/production.go @@ -22,7 +22,7 @@ const ( type Production struct { Type ProductionType `json:"type"` - SubjectID *uuid.UUID `json:"subjectId,omitempty"` // TODO: get rid of Nils? + SubjectID *uuid.UUID `json:"subjectId,omitempty"` Progress *Float `json:"progress,omitempty"` ProdUsed *Float `json:"prodUsed,omitempty"` } diff --git a/internal/repo/game.go b/internal/repo/game.go index 7357008..f55cabe 100644 --- a/internal/repo/game.go +++ b/internal/repo/game.go @@ -1,7 +1,6 @@ package repo /* - TODO: only state will be saved once (current, turn); meta and bombings are saved at turn generation and saved twice /state.json /0001/state.json /0001/meta.json @@ -13,6 +12,7 @@ package repo import ( "fmt" + "github.com/google/uuid" "github.com/iliadenisov/galaxy/internal/model/game" "github.com/iliadenisov/galaxy/internal/model/report" ) @@ -27,13 +27,33 @@ func (r *repo) SaveReport(t uint, rep *report.Report) error { } func saveReport(s Storage, t uint, v *report.Report) error { - path := fmt.Sprintf("%s/report/%s.json", turnDir(t), v.RaceID.String()) + path := repDir(t, v.RaceID) if err := s.Write(path, v); err != nil { return NewStorageError(err) } return nil } +func (r *repo) LoadReport(t uint, id uuid.UUID) (*report.Report, error) { + return loadReport(r.s, t, id) +} + +func loadReport(s Storage, t uint, id uuid.UUID) (*report.Report, error) { + path := repDir(t, id) + result := new(report.Report) + exist, err := s.Exists(path) + if err != nil { + return nil, NewStorageError(err) + } + if !exist { + return nil, NewReportNotFoundError() + } + if err := s.ReadSafe(path, result); err != nil { + return nil, NewStorageError(err) + } + return result, nil +} + func (r *repo) SaveNewTurn(t uint, g *game.Game) error { return saveNewTurn(r.s, t, g) } @@ -104,7 +124,6 @@ func loadMeta(s Storage) (*game.GameMeta, error) { if !exist { return result, nil } - // TODO: create separate Read func for meta ops if err := s.ReadSafe(path, result); err != nil { return nil, NewStorageError(err) } @@ -164,6 +183,10 @@ func (r *repo) SaveBombings(t uint, b []*game.Bombing) error { return saveMeta(r.s, t, meta) } +func repDir(t uint, id uuid.UUID) string { + return fmt.Sprintf("%s/report/%s.json", turnDir(t), id.String()) +} + func turnDir(t uint) string { return fmt.Sprintf("%04d", t) } diff --git a/internal/repo/repo.go b/internal/repo/repo.go index 58319a8..86737ed 100644 --- a/internal/repo/repo.go +++ b/internal/repo/repo.go @@ -16,6 +16,10 @@ func NewGameNotInitializedError() error { return e.NewGameNotInitializedError() } +func NewReportNotFoundError() error { + return e.NewReportNotFoundError() +} + func NewStateError(msg string) error { return e.NewGameStateError(msg) } diff --git a/internal/router/handler/command.go b/internal/router/handler/command.go index 3839a99..365dac9 100644 --- a/internal/router/handler/command.go +++ b/internal/router/handler/command.go @@ -11,19 +11,19 @@ import ( "github.com/iliadenisov/galaxy/internal/model/rest" ) -type CommandExecutor func(controller.Config, rest.Command) error +type CommandExecutor func(controller.Configurer, rest.Command) error var ( ErrCommandNotProcessed = errors.New("command was not processed by executor") ) -func CommandHandler(c *gin.Context, config controller.Config, executor CommandExecutor) { +func CommandHandler(c *gin.Context, configurer controller.Configurer, executor CommandExecutor) { var cmd rest.Command if err := c.ShouldBindJSON(&cmd); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - err := executor(config, cmd) + err := executor(configurer, cmd) switch { case err == nil: c.Status(http.StatusOK) @@ -35,7 +35,7 @@ func CommandHandler(c *gin.Context, config controller.Config, executor CommandEx } } -func ExecuteCommand(config controller.Config, cmd rest.Command) error { +func ExecuteCommand(config controller.Configurer, cmd rest.Command) error { switch { case cmd.DeclareWar != nil: return game.DeclareWar(config, cmd.Race, cmd.DeclareWar.Opponent) diff --git a/internal/router/handler/init.go b/internal/router/handler/init.go index 12ce507..4f0c97e 100644 --- a/internal/router/handler/init.go +++ b/internal/router/handler/init.go @@ -9,7 +9,7 @@ import ( "github.com/iliadenisov/galaxy/internal/model/rest" ) -func InitHandler(c *gin.Context, config controller.Config) { +func InitHandler(c *gin.Context, config controller.Configurer) { var init rest.Init if err := c.ShouldBindJSON(&init); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) diff --git a/internal/router/handler/status.go b/internal/router/handler/status.go index eafd3f0..fab5ddb 100644 --- a/internal/router/handler/status.go +++ b/internal/router/handler/status.go @@ -9,7 +9,7 @@ import ( "github.com/iliadenisov/galaxy/internal/model/rest" ) -func StatusHandler(c *gin.Context, config controller.Config) { +func StatusHandler(c *gin.Context, config controller.Configurer) { g, err := game.LoadState(config) if transformError(c, err) { diff --git a/internal/router/router.go b/internal/router/router.go index 1b04ecb..4a7e9b7 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -18,7 +18,6 @@ const ( func initConfig() func(*controller.Param) { return func(p *controller.Param) { - // TODO: initialize base controller settings p.StoragePath = os.Getenv("STORAGE_PATH") } } @@ -40,7 +39,7 @@ func NewRouterExecutor(executor handler.CommandExecutor) Router { return Router{r: setupRouter(initConfig(), executor)} } -func setupRouter(config controller.Config, executor handler.CommandExecutor) *gin.Engine { +func setupRouter(config controller.Configurer, executor handler.CommandExecutor) *gin.Engine { gin.SetMode(gin.ReleaseMode) r := gin.New() diff --git a/internal/router/router_export_test.go b/internal/router/router_export_test.go index 540f469..460cd30 100644 --- a/internal/router/router_export_test.go +++ b/internal/router/router_export_test.go @@ -10,6 +10,6 @@ func SetupRouter() *gin.Engine { return SetupRouterConfig(nil) } -func SetupRouterConfig(config controller.Config) *gin.Engine { - return setupRouter(config, func(controller.Config, rest.Command) error { return nil }) +func SetupRouterConfig(config controller.Configurer) *gin.Engine { + return setupRouter(config, func(controller.Configurer, rest.Command) error { return nil }) }