package controller import ( "errors" "github.com/google/uuid" "github.com/iliadenisov/galaxy/server/internal/model/game" "galaxy/model/order" "galaxy/model/report" "github.com/iliadenisov/galaxy/server/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) // SaveOrder stores order for given turn SaveOrder(uint, uuid.UUID, *order.Order) error // LoadOrder loads order for specific turn and player id LoadOrder(uint, uuid.UUID) (*order.Order, bool, error) } type Ctrl interface { ValidateOrder(actor string, cmd ...order.DecodableCommand) error // remove below funcs if /command api will be deleted 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, groupID uuid.UUID, cargoType string, quantity float64) error ShipGroupUnload(actor string, groupID uuid.UUID, quantity float64) error ShipGroupSend(actor string, groupID uuid.UUID, planetNumber uint) error ShipGroupUpgrade(actor string, groupID uuid.UUID, techInput string, limitLevel float64) error ShipGroupBreak(actor string, groupID, newID uuid.UUID, quantity uint) error ShipGroupMerge(actor string) error ShipGroupDismantle(actor string, groupID uuid.UUID) error ShipGroupTransfer(actor, acceptor string, groupID uuid.UUID) error ShipGroupJoinFleet(actor, fleetName string, groupID uuid.UUID) 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) (s game.State, err error) { ec, err := NewRepoController(configure) if err != nil { return game.State{}, err } if err = ec.Repo.Lock(); err != nil { return } defer func() { err = errors.Join(err, ec.Repo.Release()) if err == nil { s, err = GameState(configure) } }() _, err = NewGame(ec.Repo, races) return } func GenerateTurn(configure func(*Param)) (err error) { ec, err := NewRepoController(configure) if err != nil { return err } err = ec.executeLocked(func(c *Controller) error { return c.MakeTurn() }) 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 ValidateOrder(configure func(*Param), actor string, cmd ...order.DecodableCommand) (err error) { ec, err := NewRepoController(configure) if err != nil { return err } return ec.validateOrder(actor, cmd...) } func GameState(configure func(*Param)) (s game.State, err error) { ec, err := NewRepoController(configure) if err != nil { return game.State{}, err } 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 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 (ec *RepoController) NewGameController(g *game.Game) *Controller { return &Controller{ RepoController: ec, Cache: NewCache(g), } } func (ec *RepoController) validateOrder(actor string, cmd ...order.DecodableCommand) (err error) { return ec.executeSafe(func(t uint, c *Controller) error { id, err := c.RaceID(actor) if err != nil { return err } err = c.ValidateOrder(actor, cmd...) if err != nil { return err } o := &order.Order{Commands: make([]order.DecodableCommand, len(cmd))} copy(o.Commands, cmd) return ec.Repo.SaveOrder(t, id, o) }) } func (ec *RepoController) executeCommand(consumer func(*Controller) error) (err error) { return ec.executeLocked(func(c *Controller) error { err = consumer(c) if err == nil { c.Cache.StageCommand() err = c.saveState() } return err }) } func (ec *RepoController) executeSafe(consumer func(uint, *Controller) error) (err error) { g, err := ec.Repo.LoadStateSafe() if err != nil { return err } err = consumer(g.Turn, ec.NewGameController(g)) return } func (ec *RepoController) executeLocked(consumer func(*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(ec.NewGameController(g)) return } func (c *Controller) saveState() error { return c.Repo.SaveLastState(c.Cache.g) } type Controller struct { *RepoController Cache *Cache } type Param struct { StoragePath string }