diff --git a/pkg/game/command.go b/pkg/game/command.go index b42b679..91a7b73 100644 --- a/pkg/game/command.go +++ b/pkg/game/command.go @@ -3,29 +3,21 @@ package game import "github.com/iliadenisov/galaxy/pkg/model/game" func DeclareWar(configure func(*Param), from, to string) (err error) { - control(configure, func(c *ctrl) { c.execute(func(r Repo) { err = updateRelation(r, from, to, game.RelationWar) }) }) + control(configure, func(c *ctrl) { + c.execute(func(r Repo, g game.Game) { err = updateRelation(r, g, from, to, game.RelationWar) }) + }) return } func DeclarePeace(configure func(*Param), from, to string) (err error) { - control(configure, func(c *ctrl) { c.execute(func(r Repo) { err = updateRelation(r, from, to, game.RelationPeace) }) }) + control(configure, func(c *ctrl) { + c.execute(func(r Repo, g game.Game) { err = updateRelation(r, g, from, to, game.RelationPeace) }) + }) return } -func updateRelation(r Repo, hostRace, opponentRace string, rel game.Relation) error { - g, err := r.LoadState() - if err != nil { - return err - } - hostID, err := g.HostRaceID(hostRace) - if err != nil { - return err - } - opponentID, err := g.OpponentRaceID(opponentRace) - if err != nil { - return err - } - if err := g.UpdateRelation(hostID, opponentID, rel); err != nil { +func updateRelation(r Repo, g game.Game, hostRace, opponentRace string, rel game.Relation) error { + if err := g.UpdateRelation(hostRace, opponentRace, rel); err != nil { return err } return r.SaveState(g) diff --git a/pkg/game/command_test.go b/pkg/game/command_test.go index 819056c..bccfe8d 100644 --- a/pkg/game/command_test.go +++ b/pkg/game/command_test.go @@ -1,26 +1,70 @@ package game_test import ( - "fmt" "testing" "github.com/iliadenisov/galaxy/pkg/game" - "github.com/iliadenisov/galaxy/pkg/util" + mg "github.com/iliadenisov/galaxy/pkg/model/game" "github.com/stretchr/testify/assert" ) -func TestRelation(t *testing.T) { - root, cleanup := util.CreateWorkDir(t) - defer cleanup() - players := 20 - races := make([]string, players) - for i := range players { - races[i] = fmt.Sprintf("race_%02d", i) - } - _, err := game.ComposeGame(func(p *game.Param) { p.StoragePath = root }, races) - assert.NoError(t, err) +func TestDeclarePeaceAndWarSingle(t *testing.T) { + g(t, func(f func(*game.Param), g func() mg.Game) { + hostRace := "race_05" + opponentRace := "race_01" - err = game.DeclarePeace(func(p *game.Param) { p.StoragePath = root }, "race_05", "race_01") - assert.NoError(t, err) - // TODO: check relation state changed + r, err := g().Relation(hostRace, opponentRace) + assert.NoError(t, err) + assert.Equal(t, mg.RelationWar, r.Relation) + + assert.NoError(t, game.DeclarePeace(f, hostRace, opponentRace)) + r, err = g().Relation(hostRace, opponentRace) + assert.NoError(t, err) + assert.Equal(t, mg.RelationPeace, r.Relation) + + assert.NoError(t, game.DeclareWar(f, hostRace, opponentRace)) + r, err = g().Relation(hostRace, opponentRace) + assert.NoError(t, err) + assert.Equal(t, mg.RelationWar, r.Relation) + }) +} + +func TestDeclarePeaceAndWarAll(t *testing.T) { + g(t, func(f func(*game.Param), g func() mg.Game) { + hostRace := "race_07" + + for i := range testRaceCount { + opponentRace := raceNum(i) + if opponentRace == hostRace { + continue + } + r, err := g().Relation(hostRace, opponentRace) + assert.NoError(t, err) + assert.Equal(t, mg.RelationWar, r.Relation) + } + + assert.NoError(t, game.DeclarePeace(f, hostRace, hostRace)) + + for i := range testRaceCount { + opponentRace := raceNum(i) + if opponentRace == hostRace { + continue + } + r, err := g().Relation(hostRace, opponentRace) + assert.NoError(t, err) + assert.Equal(t, mg.RelationPeace, r.Relation) + } + + assert.NoError(t, game.DeclareWar(f, hostRace, hostRace)) + + for i := range testRaceCount { + opponentRace := raceNum(i) + if opponentRace == hostRace { + continue + } + r, err := g().Relation(hostRace, opponentRace) + assert.NoError(t, err) + assert.Equal(t, mg.RelationWar, r.Relation) + } + }) } diff --git a/pkg/game/controller.go b/pkg/game/controller.go index 60bbe21..d56dfcd 100644 --- a/pkg/game/controller.go +++ b/pkg/game/controller.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/google/uuid" + "github.com/iliadenisov/galaxy/pkg/model/game" "github.com/iliadenisov/galaxy/pkg/repo" ) @@ -16,8 +17,13 @@ type Param struct { StoragePath string } -func ComposeGame(configure func(*Param), races []string) (gameID uuid.UUID, err error) { - control(configure, func(c *ctrl) { c.execute(func(r Repo) { gameID, err = newGame(r, races) }) }) +func LoadState(configure func(*Param)) (g game.Game, err error) { + control(configure, func(c *ctrl) { c.executeInit(func(r Repo) { g, err = c.repo.LoadState() }) }) + return +} + +func GenerateGame(configure func(*Param), races []string) (gameID uuid.UUID, err error) { + control(configure, func(c *ctrl) { c.executeInit(func(r Repo) { gameID, err = newGame(r, races) }) }) return } @@ -47,10 +53,22 @@ func control(configure func(*Param), consumer func(*ctrl)) error { return nil } -func (c *ctrl) execute(consumer func(Repo)) error { +func (c *ctrl) executeInit(consumer func(Repo)) error { if err := c.repo.Lock(); err != nil { return fmt.Errorf("execute: lock failed: %s", err) } consumer(c.repo) return c.repo.Release() } + +func (c *ctrl) execute(consumer func(Repo, game.Game)) 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 + } + consumer(c.repo, g) + return c.repo.Release() +} diff --git a/pkg/game/controller_test.go b/pkg/game/controller_test.go index f865ebf..26b1339 100644 --- a/pkg/game/controller_test.go +++ b/pkg/game/controller_test.go @@ -5,21 +5,47 @@ import ( "testing" "github.com/iliadenisov/galaxy/pkg/game" + mg "github.com/iliadenisov/galaxy/pkg/model/game" "github.com/iliadenisov/galaxy/pkg/util" "github.com/stretchr/testify/assert" ) +const ( + testRaceCount = 20 +) + +func raceNum(i int) string { + return fmt.Sprintf("race_%02d", i) +} + func TestComposeGame(t *testing.T) { + g(t, func(p func(*game.Param), g func() mg.Game) { + _, err := game.GenerateGame(p, []string{"r1", "r2"}) + assert.Error(t, err) + assert.ErrorContains(t, err, "state for turn 0 already saved") + }) +} + +func g(t *testing.T, f func(func(*game.Param), func() mg.Game)) { root, cleanup := util.CreateWorkDir(t) defer cleanup() - players := 20 - races := make([]string, players) - for i := range players { - races[i] = fmt.Sprintf("race_%02d", i) + races := make([]string, testRaceCount) + for i := range testRaceCount { + races[i] = raceNum(i) } - _, err := game.ComposeGame(func(p *game.Param) { p.StoragePath = root }, races) - assert.NoError(t, err) - _, err = game.ComposeGame(func(p *game.Param) { p.StoragePath = root }, races) - assert.Error(t, err) - assert.ErrorContains(t, err, "state for turn 0 already saved") + p := func(p *game.Param) { p.StoragePath = root } + _, err := game.GenerateGame(p, races) + if err != nil { + assert.FailNow(t, "g: ComposeGame", err) + return + } + g := func() mg.Game { + g, err := game.LoadState(p) + if err != nil { + assert.FailNow(t, "g: LoadState", err) + return mg.Game{} + } + return g + } + f(p, g) } diff --git a/pkg/model/game/game.go b/pkg/model/game/game.go index 51c0b35..5bec478 100644 --- a/pkg/model/game/game.go +++ b/pkg/model/game/game.go @@ -25,14 +25,14 @@ func (g Game) Votes(raceID uuid.UUID) float64 { return pop / 1000. } -func (g Game) HostRaceID(name string) (uuid.UUID, error) { +func (g Game) hostRaceID(name string) (uuid.UUID, error) { if v, ok := g.raceID(name); ok { return v, nil } return uuid.Nil, e.NewHostRaceUnknownError(name) } -func (g Game) OpponentRaceID(name string) (uuid.UUID, error) { +func (g Game) opponentRaceID(name string) (uuid.UUID, error) { if v, ok := g.raceID(name); ok { return v, nil } @@ -48,19 +48,67 @@ func (g Game) raceID(raceName string) (uuid.UUID, bool) { return uuid.Nil, false } -func (g Game) UpdateRelation(hostID, opponentID uuid.UUID, rel Relation) error { +func (g Game) UpdateRelation(hostRace, opponentRace string, rel Relation) error { + hostID, err := g.hostRaceID(hostRace) + if err != nil { + return err + } + var opponentID uuid.UUID + if hostRace == opponentRace { + opponentID = hostID + } else if opponentID, err = g.opponentRaceID(opponentRace); err != nil { + return err + } + return g.updateRelationInternal(hostID, opponentID, rel) +} + +func (g Game) updateRelationInternal(hostID, opponentID uuid.UUID, rel Relation) error { for r := range g.Race { if g.Race[r].ID == hostID { for o := range g.Race[r].Relations { - if g.Race[r].Relations[o].RaceID == opponentID { + switch { + case hostID == opponentID: + g.Race[r].Relations[o].Relation = rel + case g.Race[r].Relations[o].RaceID == opponentID: g.Race[r].Relations[o].Relation = rel return nil } } - return e.NewGameStateError("UpdateRelation: opponent not found") + if hostID != opponentID { + return e.NewGameStateError("UpdateRelation: opponent not found") + } } } - return e.NewGameStateError("UpdateRelation: host %v not found", hostID) + if hostID != opponentID { + return e.NewGameStateError("UpdateRelation: host %v not found", hostID) + } + return nil +} + +func (g Game) Relation(hostRace, opponentRace string) (RaceRelation, error) { + hostID, err := g.hostRaceID(hostRace) + if err != nil { + return RaceRelation{}, err + } + opponentID, err := g.opponentRaceID(opponentRace) + if err != nil { + return RaceRelation{}, err + } + return g.relationInternal(hostID, opponentID) +} + +func (g Game) relationInternal(hostID, opponentID uuid.UUID) (RaceRelation, error) { + for r := range g.Race { + if g.Race[r].ID == hostID { + for o := range g.Race[r].Relations { + if g.Race[r].Relations[o].RaceID == opponentID { + return g.Race[r].Relations[o], nil + } + } + return RaceRelation{}, e.NewGameStateError("Relation: opponent not found") + } + } + return RaceRelation{}, e.NewGameStateError("Relation: host %v not found", hostID) } func (g Game) MarshalBinary() (data []byte, err error) { diff --git a/pkg/model/game/race.go b/pkg/model/game/race.go index 5067d7d..eed52c6 100644 --- a/pkg/model/game/race.go +++ b/pkg/model/game/race.go @@ -25,8 +25,8 @@ type Race struct { type Relation string const ( - RelationWar = "War" - RelationPeace = "Peace" + RelationWar Relation = "War" + RelationPeace Relation = "Peace" ) type RaceRelation struct {