feat: turn stage increment after player's command

This commit is contained in:
Ilia Denisov
2026-02-08 16:35:58 +02:00
parent bf34843568
commit ba5d4e1ba8
14 changed files with 102 additions and 81 deletions
+4
View File
@@ -28,6 +28,10 @@ func NewCache(g *game.Game) *Cache {
return c return c
} }
func (c Cache) Stage() uint {
return c.g.Stage
}
func (c *Cache) ShipGroupShipClass(groupIndex int) *game.ShipType { func (c *Cache) ShipGroupShipClass(groupIndex int) *game.ShipType {
if len(c.cacheShipClassByShipGroupIndex) == 0 { if len(c.cacheShipClassByShipGroupIndex) == 0 {
c.cacheShipsAndGroups() c.cacheShipsAndGroups()
+17 -6
View File
@@ -1,6 +1,7 @@
package controller package controller
import ( import (
"errors"
"fmt" "fmt"
"github.com/iliadenisov/galaxy/internal/model/game" "github.com/iliadenisov/galaxy/internal/model/game"
@@ -70,15 +71,18 @@ func NewRepoController(r Repo) *Controller {
} }
} }
func (c *Controller) ExecuteState(consumer func(Repo)) error { func (c *Controller) ExecuteState(consumer func(Repo) error) (err error) {
if err := c.Repo.Lock(); err != nil { if err := c.Repo.Lock(); err != nil {
return fmt.Errorf("execute: lock failed: %s", err) return fmt.Errorf("execute: lock failed: %s", err)
} }
consumer(c.Repo) defer func() {
return c.Repo.Release() err = errors.Join(err, c.Repo.Release())
}()
err = consumer(c.Repo)
return
} }
func (c *Controller) ExecuteGame(consumer func(Repo, *game.Game)) error { func (c *Controller) ExecuteCommand(consumer func(Repo, *game.Game) error) (err error) {
if err := c.Repo.Lock(); err != nil { if err := c.Repo.Lock(); err != nil {
return fmt.Errorf("execute: lock failed: %s", err) return fmt.Errorf("execute: lock failed: %s", err)
} }
@@ -86,7 +90,14 @@ func (c *Controller) ExecuteGame(consumer func(Repo, *game.Game)) error {
if err != nil { if err != nil {
return err return err
} }
defer func() {
err = errors.Join(err, c.Repo.Release())
}()
c.Cache = NewCache(g) c.Cache = NewCache(g)
consumer(c.Repo, g) err = consumer(c.Repo, g)
return c.Repo.Release() if err == nil {
g.Stage += 1
c.Repo.SaveLastState(g)
}
return
} }
+1
View File
@@ -12,6 +12,7 @@ import (
func MakeTurn(c *Controller, r Repo) error { func MakeTurn(c *Controller, r Repo) error {
// Next turn // Next turn
c.Cache.g.Turn += 1 c.Cache.g.Turn += 1
c.Cache.g.Stage = 0
// 00. Вышедшие расы удаляются из списка участвующих рас перед началом просчета очередного хода // 00. Вышедшие расы удаляются из списка участвующих рас перед началом просчета очередного хода
c.Cache.TurnWipeExtinctRaces() c.Cache.TurnWipeExtinctRaces()
+5 -8
View File
@@ -6,17 +6,14 @@ import (
) )
func JoinEqualGroups(configure func(*controller.Param), race string) (err error) { func JoinEqualGroups(configure func(*controller.Param), race string) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
err = joinEqualGroups(c, r, g, race) return joinEqualGroups(c, race)
}) })
}) })
return return
} }
func joinEqualGroups(c *controller.Controller, r controller.Repo, g *game.Game, race string) error { func joinEqualGroups(c *controller.Controller, race string) error {
if err := c.JoinEqualGroups(race); err != nil { return c.JoinEqualGroups(race)
return err
}
return r.SaveLastState(g)
} }
+5 -8
View File
@@ -6,17 +6,14 @@ import (
) )
func RenamePlanet(configure func(*controller.Param), race string, number int, name string) (err error) { func RenamePlanet(configure func(*controller.Param), race string, number int, name string) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
err = renamePlanet(c, r, g, race, number, name) return renamePlanet(c, race, number, name)
}) })
}) })
return return
} }
func renamePlanet(c *controller.Controller, r controller.Repo, g *game.Game, race string, number int, name string) error { func renamePlanet(c *controller.Controller, race string, number int, name string) error {
if err := c.RenamePlanet(race, number, name); err != nil { return c.RenamePlanet(race, number, name)
return err
}
return r.SaveLastState(g)
} }
+5 -8
View File
@@ -6,17 +6,14 @@ import (
) )
func PlanetProduction(configure func(*controller.Param), race string, planetNumber int, prodType, subject string) (err error) { func PlanetProduction(configure func(*controller.Param), race string, planetNumber int, prodType, subject string) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
err = planetProduction(c, r, g, race, planetNumber, prodType, subject) return planetProduction(c, race, planetNumber, prodType, subject)
}) })
}) })
return return
} }
func planetProduction(c *controller.Controller, r controller.Repo, g *game.Game, race string, planetNumber int, prodType, subject string) error { func planetProduction(c *controller.Controller, race string, planetNumber int, prodType, subject string) error {
if err := c.PlanetProduction(race, planetNumber, prodType, subject); err != nil { return c.PlanetProduction(race, planetNumber, prodType, subject)
return err
}
return r.SaveLastState(g)
} }
+10 -16
View File
@@ -6,33 +6,27 @@ import (
) )
func CreateScience(configure func(*controller.Param), race, typeName string, drive, weapons, shields, cargo float64) (err error) { func CreateScience(configure func(*controller.Param), race, typeName string, drive, weapons, shields, cargo float64) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
err = createScience(c, r, g, race, typeName, drive, weapons, shields, cargo) return createScience(c, race, typeName, drive, weapons, shields, cargo)
}) })
}) })
return return
} }
func createScience(c *controller.Controller, r controller.Repo, g *game.Game, race, typeName string, drive, weapons, shields, cargo float64) error { func createScience(c *controller.Controller, race, typeName string, drive, weapons, shields, cargo float64) error {
if err := c.CreateScience(race, typeName, drive, weapons, shields, cargo); err != nil { return c.CreateScience(race, typeName, drive, weapons, shields, cargo)
return err
}
return r.SaveLastState(g)
} }
func DeleteScience(configure func(*controller.Param), race, typeName string) (err error) { func DeleteScience(configure func(*controller.Param), race, typeName string) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
err = deleteScience(c, r, g, race, typeName) return deleteScience(c, race, typeName)
}) })
}) })
return return
} }
func deleteScience(c *controller.Controller, r controller.Repo, g *game.Game, race, typeName string) error { func deleteScience(c *controller.Controller, race, typeName string) error {
if err := c.DeleteScience(race, typeName); err != nil { return c.DeleteScience(race, typeName)
return err
}
return r.SaveLastState(g)
} }
+1 -1
View File
@@ -53,7 +53,7 @@ func TestCreateScienceValidation(t *testing.T) {
{typeName, 0, 0, 0, -1, e.GenericErrorText(e.ErrInputCargoValue)}, {typeName, 0, 0, 0, -1, e.GenericErrorText(e.ErrInputCargoValue)},
{typeName, 0, 1, 1, -1, e.GenericErrorText(e.ErrInputCargoValue)}, {typeName, 0, 1, 1, -1, e.GenericErrorText(e.ErrInputCargoValue)},
} }
c(t, func(p func(*controller.Param), g func() *controller.Controller) { c(t, func(p func(*controller.Param), ctrl func() *controller.Controller) {
for i, tc := range table { for i, tc := range table {
if tc.err == "" { if tc.err == "" {
n := tc.name + strconv.Itoa(i) n := tc.name + strconv.Itoa(i)
+15 -15
View File
@@ -6,49 +6,49 @@ import (
) )
func CreateShipType(configure func(*controller.Param), race, typeName string, drive float64, ammo int, weapons, shields, cargo float64) (err error) { func CreateShipType(configure func(*controller.Param), race, typeName string, drive float64, ammo int, weapons, shields, cargo float64) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
err = createShipType(c, r, g, race, typeName, drive, ammo, weapons, shields, cargo) return createShipType(c, race, typeName, drive, ammo, weapons, shields, cargo)
}) })
}) })
return return
} }
func createShipType(c *controller.Controller, r controller.Repo, g *game.Game, race, typeName string, drive float64, ammo int, weapons, shields, cargo float64) error { func createShipType(c *controller.Controller, race, typeName string, drive float64, ammo int, weapons, shields, cargo float64) error {
if err := c.CreateShipType(race, typeName, drive, ammo, weapons, shields, cargo); err != nil { if err := c.CreateShipType(race, typeName, drive, ammo, weapons, shields, cargo); err != nil {
return err return err
} }
return r.SaveLastState(g) return nil // r.SaveLastState(g)
} }
func MergeShipType(configure func(*controller.Param), race, source, target string) (err error) { func MergeShipType(configure func(*controller.Param), race, source, target string) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
err = mergeShipType(c, r, g, race, source, target) return mergeShipType(c, race, source, target)
}) })
}) })
return return
} }
func mergeShipType(c *controller.Controller, r controller.Repo, g *game.Game, race, source, target string) error { func mergeShipType(c *controller.Controller, race, source, target string) error {
if err := c.MergeShipType(race, source, target); err != nil { if err := c.MergeShipType(race, source, target); err != nil {
return err return err
} }
return r.SaveLastState(g) return nil // r.SaveLastState(g)
} }
func DeleteShipType(configure func(*controller.Param), race, typeName string) (err error) { func DeleteShipType(configure func(*controller.Param), race, typeName string) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
err = deleteShipType(c, r, g, race, typeName) return deleteShipType(c, race, typeName)
}) })
}) })
return return
} }
func deleteShipType(c *controller.Controller, r controller.Repo, g *game.Game, race, typeName string) error { func deleteShipType(c *controller.Controller, race, typeName string) error {
if err := c.DeleteShipType(race, typeName); err != nil { if err := c.DeleteShipType(race, typeName); err != nil {
return err return err
} }
return r.SaveLastState(g) return nil // r.SaveLastState(g)
} }
+10 -4
View File
@@ -2,12 +2,18 @@ package game
import ( import (
"github.com/iliadenisov/galaxy/internal/controller" "github.com/iliadenisov/galaxy/internal/controller"
"github.com/iliadenisov/galaxy/internal/model/game"
) )
func MakeTurn(configure func(*controller.Param), race string, number int, name string) (err error) { func GenerateTurn(configure func(*controller.Param)) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { controller.MakeTurn(c, r) }) return c.ExecuteState(func(r controller.Repo) error {
g, err := r.LoadState()
if err != nil {
return err
}
c.Cache = controller.NewCache(g)
return controller.MakeTurn(c, r)
})
}) })
return return
} }
+10 -6
View File
@@ -6,22 +6,26 @@ import (
) )
func DeclareWar(configure func(*controller.Param), from, to string) (err error) { func DeclareWar(configure func(*controller.Param), from, to string) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { err = updateRelation(c, r, g, from, to, game.RelationWar) }) return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
return updateRelation(c, from, to, game.RelationWar)
})
}) })
return return
} }
func DeclarePeace(configure func(*controller.Param), from, to string) (err error) { func DeclarePeace(configure func(*controller.Param), from, to string) (err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteGame(func(r controller.Repo, g *game.Game) { err = updateRelation(c, r, g, from, to, game.RelationPeace) }) return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
return updateRelation(c, from, to, game.RelationPeace)
})
}) })
return return
} }
func updateRelation(c *controller.Controller, r controller.Repo, g *game.Game, hostRace, opponentRace string, rel game.Relation) error { func updateRelation(c *controller.Controller, hostRace, opponentRace string, rel game.Relation) error {
if err := c.UpdateRelation(hostRace, opponentRace, rel); err != nil { if err := c.UpdateRelation(hostRace, opponentRace, rel); err != nil {
return err return err
} }
return r.SaveLastState(g) return nil // r.SaveLastState(g)
} }
+2
View File
@@ -37,6 +37,7 @@ func TestDeclarePeaceAndWarAll(t *testing.T) {
} }
assert.NoError(t, game.DeclarePeace(f, raceNum(hostRace), raceNum(hostRace))) assert.NoError(t, game.DeclarePeace(f, raceNum(hostRace), raceNum(hostRace)))
assert.Equal(t, 1, int(ctrl().Cache.Stage()))
for i := range testRaceCount { for i := range testRaceCount {
if i == hostRace { if i == hostRace {
@@ -46,6 +47,7 @@ func TestDeclarePeaceAndWarAll(t *testing.T) {
} }
assert.NoError(t, game.DeclareWar(f, raceNum(hostRace), raceNum(hostRace))) assert.NoError(t, game.DeclareWar(f, raceNum(hostRace), raceNum(hostRace)))
assert.Equal(t, 2, int(ctrl().Cache.Stage()))
for i := range testRaceCount { for i := range testRaceCount {
if i == hostRace { if i == hostRace {
+16 -8
View File
@@ -7,31 +7,39 @@ import (
) )
func GenerateGame(configure func(*controller.Param), races []string) (gameID uuid.UUID, err error) { func GenerateGame(configure func(*controller.Param), races []string) (gameID uuid.UUID, err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteState(func(r controller.Repo) { gameID, err = controller.NewGame(r, races) }) return c.ExecuteState(func(r controller.Repo) error {
gameID, err = controller.NewGame(r, races)
return err
})
}) })
return return
} }
// LoadState used for lock-safe loading game state and may be called concurrently. // LoadState used for lock-safe loading game state and may be called concurrently.
func LoadState(configure func(*controller.Param)) (g *game.Game, err error) { func LoadState(configure func(*controller.Param)) (g *game.Game, err error) {
control(configure, func(c *controller.Controller) { g, err = c.Repo.LoadStateSafe() }) err = control(configure, func(c *controller.Controller) error {
g, err = c.Repo.LoadStateSafe()
return err
})
return return
} }
// TODO: command for loading report by players (MUST be limited by router) // 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)) (g *game.Game, err error) {
control(configure, func(c *controller.Controller) { err = control(configure, func(c *controller.Controller) error {
c.ExecuteState(func(r controller.Repo) { g, err = c.Repo.LoadState() }) return c.ExecuteState(func(r controller.Repo) error {
g, err = c.Repo.LoadState()
return err
})
}) })
return return
} }
func control(configure func(*controller.Param), consumer func(*controller.Controller)) error { func control(configure func(*controller.Param), consumer func(*controller.Controller) error) (err error) {
c, err := controller.NewController(configure) c, err := controller.NewController(configure)
if err != nil { if err != nil {
return err return err
} }
consumer(c) return consumer(c)
return nil
} }
+1 -1
View File
@@ -48,10 +48,10 @@ func NewTechSet() TechSet {
} }
} }
// TODO: turn's incremental Version
type Game struct { type Game struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
Turn uint `json:"turn"` Turn uint `json:"turn"`
Stage uint `json:"stage"`
Map Map `json:"map"` Map Map `json:"map"`
Race []Race `json:"races"` Race []Race `json:"races"`
Votes Float `json:"votes"` Votes Float `json:"votes"`