refactor: executors and routers

* refactor: executors and routers
This commit is contained in:
Ilia Denisov
2026-02-09 15:53:34 +03:00
committed by GitHub
parent e48a0c8b96
commit d9c8de27e5
38 changed files with 508 additions and 838 deletions
+4 -4
View File
@@ -134,11 +134,11 @@ func TestProduceBattles(t *testing.T) {
race_C_idx, _ := c.AddRace(race_C_name)
race_D_idx, _ := c.AddRace(race_D_name)
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar))
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar))
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar.String()))
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar.String()))
assert.NoError(t, g.UpdateRelation(race_C_name, race_D_name, game.RelationWar))
assert.NoError(t, g.UpdateRelation(race_D_name, race_C_name, game.RelationWar))
assert.NoError(t, g.UpdateRelation(race_C_name, race_D_name, game.RelationWar.String()))
assert.NoError(t, g.UpdateRelation(race_D_name, race_C_name, game.RelationWar.String()))
assert.Equal(t, game.RelationPeace, c.Relation(Race_0_idx, race_C_idx))
assert.Equal(t, game.RelationPeace, c.Relation(Race_1_idx, race_C_idx))
+5 -5
View File
@@ -37,8 +37,8 @@ func TestBombPlanet(t *testing.T) {
func TestCollectBombingGroups(t *testing.T) {
c, g := newCache()
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar))
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar))
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar.String()))
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar.String()))
// 1: idx = 0 / Ready to bomb: Race_1/Planet_1
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2)) // bombs
@@ -82,7 +82,7 @@ func TestCollectBombingGroups(t *testing.T) {
assert.Equal(t, 1, bg[R0_Planet_2_num][Race_1_idx][0])
// remove bombings from Race_1
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationPeace))
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationPeace.String()))
bg = c.CollectBombingGroups()
assert.Len(t, bg, 1)
assert.Contains(t, bg, R1_Planet_1_num)
@@ -95,8 +95,8 @@ func TestCollectBombingGroups(t *testing.T) {
func TestProduceBombings(t *testing.T) {
c, g := newCache()
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar))
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar))
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar.String()))
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar.String()))
// 1: idx = 0 / Bombs on: Race_1/Planet_1
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3))
+5 -1
View File
@@ -38,7 +38,11 @@ func (c Controller) GiveVotes(actor, acceptor string) error {
return nil
}
func (c Controller) UpdateRelation(actor, acceptor string, rel game.Relation) error {
func (c Controller) UpdateRelation(actor, acceptor string, v string) error {
rel, ok := game.ParseRelation(v)
if !ok {
return e.NewUnknownRelationError(v)
}
ri, err := c.Cache.validActor(actor)
if err != nil {
return err
+108 -38
View File
@@ -2,7 +2,6 @@ package controller
import (
"errors"
"fmt"
"github.com/google/uuid"
"github.com/iliadenisov/galaxy/internal/model/game"
@@ -10,6 +9,8 @@ import (
"github.com/iliadenisov/galaxy/internal/repo"
)
type Configurer func(*Param)
type Repo interface {
// Lock must be called before any repository operations
Lock() error
@@ -42,18 +43,109 @@ type Repo interface {
LoadReport(uint, uuid.UUID) (*report.Report, error)
}
type Controller struct {
Repo Repo
Cache *Cache
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
}
type Param struct {
StoragePath string
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
}
type Configurer func(*Param)
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 NewController(config Configurer) (*Controller, error) {
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: ".",
}
@@ -64,44 +156,22 @@ func NewController(config Configurer) (*Controller, error) {
if err != nil {
return nil, err
}
return &Controller{
return &RepoController{
Repo: r,
}, nil
}
func NewRepoController(r Repo) *Controller {
func NewGameController(g *game.Game) *Controller {
return &Controller{
Repo: r,
Cache: NewCache(g),
}
}
func (c *Controller) ExecuteState(consumer func(Repo) error) (err error) {
if err := c.Repo.Lock(); err != nil {
return fmt.Errorf("execute: lock failed: %s", err)
}
defer func() {
err = errors.Join(err, c.Repo.Release())
}()
err = consumer(c.Repo)
return
type Controller struct {
Repo Repo
Cache *Cache
}
func (c *Controller) ExecuteCommand(consumer func(Repo, *game.Game) error) (err error) {
if err := c.Repo.Lock(); err != nil {
return fmt.Errorf("execute: lock failed: %s", err)
}
g, err := c.Repo.LoadState()
if err != nil {
return err
}
defer func() {
err = errors.Join(err, c.Repo.Release())
}()
c.Cache = NewCache(g)
err = consumer(c.Repo, g)
if err == nil {
g.Stage += 1
c.Repo.SaveLastState(g)
}
return
type Param struct {
StoragePath string
}
+6 -4
View File
@@ -129,8 +129,10 @@ func newGame() *game.Game {
}
func newCache() (*controller.Cache, *controller.Controller) {
g := newGame()
c := controller.NewCache(g)
ctl := controller.NewGameController(newGame())
// g := newGame()
// c := controller.NewCache(g)
c := ctl.Cache
assertNoError(c.CreateShipType(Race_0_idx, Race_0_Gunship, 60, 3, 30, 100, 0))
assertNoError(c.CreateShipType(Race_0_idx, Race_0_Freighter, 8, 0, 0, 2, 10))
assertNoError(c.CreateShipType(Race_0_idx, ShipType_Cruiser, Cruiser.Drive.F(), int(Cruiser.Armament), Cruiser.Weapons.F(), Cruiser.Shields.F(), Cruiser.Cargo.F()))
@@ -139,8 +141,8 @@ func newCache() (*controller.Cache, *controller.Controller) {
assertNoError(c.CreateShipType(Race_1_idx, Race_1_Freighter, 8, 0, 0, 2, 10))
assertNoError(c.CreateShipType(Race_1_idx, ShipType_Cruiser, 15, 2, 15, 15, 0)) // same name - different type (why.)
ctl := controller.NewRepoController(nil)
ctl.Cache = c
// ctl := controller.NewRepoController(nil)
// ctl.Cache = c
return c, ctl
}
+9 -6
View File
@@ -35,23 +35,26 @@ func TestGiveVotes(t *testing.T) {
func TestRelation(t *testing.T) {
c, g := newCache()
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar))
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationPeace))
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, "war"))
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, "PEACE"))
assert.Equal(t, game.RelationWar, c.Relation(Race_0_idx, Race_1_idx))
assert.Equal(t, game.RelationPeace, c.Relation(Race_1_idx, Race_0_idx))
assert.ErrorContains(t,
g.UpdateRelation(Race_0.Name, UnknownRace, game.RelationWar),
g.UpdateRelation(Race_0.Name, Race_1.Name, "Wojna"),
e.GenericErrorText(e.ErrInputUnknownRelation))
assert.ErrorContains(t,
g.UpdateRelation(Race_0.Name, UnknownRace, "War"),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.UpdateRelation(UnknownRace, Race_0.Name, game.RelationWar),
g.UpdateRelation(UnknownRace, Race_0.Name, "Peace"),
e.GenericErrorText(e.ErrInputUnknownRace))
assert.ErrorContains(t,
g.UpdateRelation(Race_0.Name, Race_Extinct.Name, game.RelationWar),
g.UpdateRelation(Race_0.Name, Race_Extinct.Name, "War"),
e.GenericErrorText(e.ErrRaceExinct))
assert.ErrorContains(t,
g.UpdateRelation(Race_Extinct.Name, Race_0.Name, game.RelationWar),
g.UpdateRelation(Race_Extinct.Name, Race_0.Name, "War"),
e.GenericErrorText(e.ErrRaceExinct))
}
+53
View File
@@ -2,6 +2,7 @@ package controller_test
import (
"slices"
"strconv"
"testing"
e "github.com/iliadenisov/galaxy/internal/error"
@@ -35,6 +36,58 @@ func TestCreateShipClass(t *testing.T) {
e.GenericErrorText(e.ErrInputEntityTypeNameInvalid))
}
func TestCreateShipTypeValidation(t *testing.T) {
race := Race_0.Name
typeName := "Drone"
type tc struct {
name string
d, w, s, c float64
a int
err string
}
table := []tc{
// correct values
{typeName, 1, 0, 0, 0, 0, ""},
{typeName, 1.1, 0, 0, 0, 0, ""},
{typeName, 1, 1.2, 0, 0, 1, ""},
{typeName, 1, 1.2, 2.5, 0, 1, ""},
{typeName, 1, 0, 2.5, 7.7, 0, ""},
// incorrect values...
{"", 1, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)},
{" ", 1, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)},
{typeName, 0, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputShipTypeZeroValues)},
// drive
{typeName, -1, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputDriveValue)},
{typeName, 0.5, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputDriveValue)},
// weapons
{typeName, 0, -1, 0, 0, 0, e.GenericErrorText(e.ErrInputWeaponsValue)},
{typeName, 0, 0.5, 0, 0, 0, e.GenericErrorText(e.ErrInputWeaponsValue)},
// shields
{typeName, 0, 0, -1, 0, 0, e.GenericErrorText(e.ErrInputShieldsValue)},
{typeName, 0, 0, 0.5, 0, 0, e.GenericErrorText(e.ErrInputShieldsValue)},
// cargo
{typeName, 0, 0, 0, -1, 0, e.GenericErrorText(e.ErrInputCargoValue)},
{typeName, 0, 0, 0, 0.5, 0, e.GenericErrorText(e.ErrInputCargoValue)},
// armament (and weapons)
{typeName, 0, 0, 0, 0, -1, e.GenericErrorText(e.ErrInputShipTypeArmamentValue)},
{typeName, 0, 1, 0, 0, 0, e.GenericErrorText(e.ErrInputShipTypeWeaponsAndArmamentValue)},
{typeName, 0, 0, 0, 0, 1, e.GenericErrorText(e.ErrInputShipTypeWeaponsAndArmamentValue)},
}
for i, tc := range table {
_, g := newCache()
if tc.err == "" {
err := g.CreateShipType(race, tc.name+strconv.Itoa(i), tc.d, tc.a, tc.w, tc.s, tc.c)
assert.NoError(t, err)
err = g.CreateShipType(race, tc.name+strconv.Itoa(i), tc.d, tc.a, tc.w, tc.s, tc.c)
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityTypeNameDuplicate))
} else {
err := g.CreateShipType(race, tc.name, tc.d, tc.a, tc.w, tc.s, tc.c)
assert.ErrorContains(t, err, tc.err)
}
}
}
func TestMergeShipClass(t *testing.T) {
c, g := newCache()