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) RaceQuit(actor string) error RaceVote(actor, acceptor string) error RaceRelation(actor, acceptor string, rel string) error ShipClassCreate(actor, typeName string, drive float64, ammo int, weapons, shileds, cargo float64) error ShipClassMerge(actor, name, targetName string) error ShipClassRemove(actor, typeName string) error ShipGroupLoad(actor string, groupIndex uint, cargoType string, ships uint, quantity float64) error ShipGroupUnload(actor string, groupIndex uint, ships uint, quantity float64) error ShipGroupSend(actor string, groupIndex, planetNumber, quantity uint) error ShipGroupUpgrade(actor string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error ShipGroupBreak(actor string, groupIndex, quantity uint) error ShipGroupMerge(actor string) error ShipGroupDismantle(actor string, groupIndex, quantity uint) error ShipGroupTransfer(actor, acceptor string, groupIndex, quantity uint) error ShipGroupJoinFleet(actor, fleetName string, group, count uint) error FleetMerge(actor, fleetSourceName, fleetTargetName string) error FleetSend(actor, fleetName string, planetNumber uint) error ScienceCreate(actor, typeName string, drive, weapons, shields, cargo float64) error ScienceRemove(actor, typeName string) error PlanetRename(actor string, planetNumber int, typeName string) error PlanetProduce(actor string, planetNumber int, prodType, subject string) error PlanetRouteSet(actor, loadType string, origin, destination uint) error PlanetRouteRemove(actor, loadType string, origin 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 }