package controller import ( "errors" "github.com/google/uuid" "github.com/iliadenisov/galaxy/internal/model/game" "github.com/iliadenisov/galaxy/internal/model/report" "github.com/iliadenisov/galaxy/internal/repo" ) type Configurer func(*Param) type Repo interface { // Lock must be called before any repository operations Lock() error // Release must be called after first and only repository operation Release() error // SaveTurn stores just generated new turn SaveNewTurn(uint, *game.Game) error // SaveState stores current game state updated between turns SaveLastState(*game.Game) error // LoadState retrieves game current state with required lock acquisition LoadState() (*game.Game, error) // LoadStateSafe retrieves game current state without preliminary locking LoadStateSafe() (*game.Game, error) // SaveBattle stores a new battle protocol and battle meta data for turn t SaveBattle(uint, *report.BattleReport, *game.BattleMeta) error // SaveBombing stores all prodused bombings for turn t SaveBombings(uint, []*game.Bombing) error // 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 Ctrl interface { RaceID(actor string) (uuid.UUID, error) QuitGame(actor string) error GiveVotes(actor, acceptor string) error UpdateRelation(actor, acceptor string, rel string) error JoinShipGroupToFleet(actor, fleetName string, group, count uint) error JoinFleets(actor, fleetSourceName, fleetTargetName string) error SendFleet(actor, fleetName string, planetNumber uint) error RenamePlanet(actor string, planetNumber int, typeName string) error PlanetProduction(actor string, planetNumber int, prodType, subject string) error SetRoute(actor, loadType string, origin, destination uint) error RemoveRoute(actor, loadType string, origin uint) error CreateScience(actor, typeName string, drive, weapons, shields, cargo float64) error DeleteScience(actor, typeName string) error CreateShipType(actor, typeName string, drive float64, ammo int, weapons, shileds, cargo float64) error MergeShipType(actor, name, targetName string) error DeleteShipType(actor, typeName string) error SendGroup(actor string, groupIndex, planetNumber, quantity uint) error UpgradeGroup(actor string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error JoinEqualGroups(actor string) error BreakGroup(actor string, groupIndex, quantity uint) error DisassembleGroup(actor string, groupIndex, quantity uint) error LoadCargo(actor string, groupIndex uint, cargoType string, ships uint, quantity float64) error UnloadCargo(actor string, groupIndex uint, ships uint, quantity float64) error TransferGroup(actor, acceptor string, groupIndex, quantity uint) error } func GenerateGame(configure func(*Param), races []string) (ID uuid.UUID, err error) { ec, err := NewRepoController(configure) if err != nil { return uuid.Nil, err } if err = ec.Repo.Lock(); err != nil { return } defer func() { err = errors.Join(err, ec.Repo.Release()) }() ID, err = NewGame(ec.Repo, races) return } func ExecuteCommand(configure func(*Param), consumer func(c Ctrl) error) (err error) { ec, err := NewRepoController(configure) if err != nil { return err } return ec.ExecuteCommand(func(c *Controller) error { return consumer(c) }) } func GameState(configure func(*Param)) (s game.State, err error) { ec, err := NewRepoController(configure) g, err := ec.Repo.LoadStateSafe() if err != nil { return game.State{}, err } result := &game.State{ ID: g.ID, Turn: g.Turn, Stage: g.Stage, Players: make([]game.PlayerState, len(g.Race)), } for i := range g.Race { r := &g.Race[i] result.Players[i].ID = r.ID result.Players[i].Name = r.Name result.Players[i].Extinct = r.Extinct } return *result, nil } type RepoController struct { Repo Repo } func (ec *RepoController) ExecuteCommand(consumer func(c *Controller) error) (err error) { if err := ec.Repo.Lock(); err != nil { return err } defer func() { err = errors.Join(err, ec.Repo.Release()) }() g, err := ec.Repo.LoadState() if err != nil { return err } err = consumer(NewGameController(g)) if err == nil { g.Stage += 1 ec.Repo.SaveLastState(g) } return } func NewRepoController(config Configurer) (*RepoController, error) { c := &Param{ StoragePath: ".", } if config != nil { config(c) } r, err := repo.NewFileRepo(c.StoragePath) if err != nil { return nil, err } return &RepoController{ Repo: r, }, nil } func NewGameController(g *game.Game) *Controller { return &Controller{ Cache: NewCache(g), } } type Controller struct { Repo Repo Cache *Cache } type Param struct { StoragePath string }