270 lines
7.0 KiB
Go
270 lines
7.0 KiB
Go
package controller
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"galaxy/game/internal/model/game"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"galaxy/model/order"
|
|
"galaxy/model/report"
|
|
|
|
"galaxy/game/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 LoadReport(configure func(*Param), actor string, turn uint) (*report.Report, error) {
|
|
ec, err := NewRepoController(configure)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ec.loadReport(actor, turn)
|
|
}
|
|
|
|
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) loadReport(actor string, turn uint) (r *report.Report, err error) {
|
|
execErr := ec.executeSafe(func(t uint, c *Controller) (exErr error) {
|
|
id, exErr := c.RaceID(actor)
|
|
if exErr == nil {
|
|
r, exErr = ec.Repo.LoadReport(turn, id)
|
|
}
|
|
return
|
|
})
|
|
err = errors.Join(err, execErr)
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|