refactor: executors and routers
* refactor: executors and routers
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user