commands A, W

This commit is contained in:
Ilia Denisov
2025-09-30 20:44:10 +03:00
parent 128d6862a7
commit 04fc96dc8f
6 changed files with 179 additions and 51 deletions
+8 -16
View File
@@ -3,29 +3,21 @@ package game
import "github.com/iliadenisov/galaxy/pkg/model/game" import "github.com/iliadenisov/galaxy/pkg/model/game"
func DeclareWar(configure func(*Param), from, to string) (err error) { 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 return
} }
func DeclarePeace(configure func(*Param), from, to string) (err error) { 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 return
} }
func updateRelation(r Repo, hostRace, opponentRace string, rel game.Relation) error { func updateRelation(r Repo, g game.Game, hostRace, opponentRace string, rel game.Relation) error {
g, err := r.LoadState() if err := g.UpdateRelation(hostRace, opponentRace, rel); err != nil {
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 {
return err return err
} }
return r.SaveState(g) return r.SaveState(g)
+58 -14
View File
@@ -1,26 +1,70 @@
package game_test package game_test
import ( import (
"fmt"
"testing" "testing"
"github.com/iliadenisov/galaxy/pkg/game" "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" "github.com/stretchr/testify/assert"
) )
func TestRelation(t *testing.T) { func TestDeclarePeaceAndWarSingle(t *testing.T) {
root, cleanup := util.CreateWorkDir(t) g(t, func(f func(*game.Param), g func() mg.Game) {
defer cleanup() hostRace := "race_05"
players := 20 opponentRace := "race_01"
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)
err = game.DeclarePeace(func(p *game.Param) { p.StoragePath = root }, "race_05", "race_01") r, err := g().Relation(hostRace, opponentRace)
assert.NoError(t, err) assert.NoError(t, err)
// TODO: check relation state changed 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)
}
})
} }
+21 -3
View File
@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/iliadenisov/galaxy/pkg/model/game"
"github.com/iliadenisov/galaxy/pkg/repo" "github.com/iliadenisov/galaxy/pkg/repo"
) )
@@ -16,8 +17,13 @@ type Param struct {
StoragePath string StoragePath string
} }
func ComposeGame(configure func(*Param), races []string) (gameID uuid.UUID, err error) { func LoadState(configure func(*Param)) (g game.Game, err error) {
control(configure, func(c *ctrl) { c.execute(func(r Repo) { gameID, err = newGame(r, races) }) }) 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 return
} }
@@ -47,10 +53,22 @@ func control(configure func(*Param), consumer func(*ctrl)) error {
return nil 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 { 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) consumer(c.repo)
return c.repo.Release() 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()
}
+36 -10
View File
@@ -5,21 +5,47 @@ import (
"testing" "testing"
"github.com/iliadenisov/galaxy/pkg/game" "github.com/iliadenisov/galaxy/pkg/game"
mg "github.com/iliadenisov/galaxy/pkg/model/game"
"github.com/iliadenisov/galaxy/pkg/util" "github.com/iliadenisov/galaxy/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestComposeGame(t *testing.T) { const (
root, cleanup := util.CreateWorkDir(t) testRaceCount = 20
defer cleanup() )
players := 20
races := make([]string, players) func raceNum(i int) string {
for i := range players { return fmt.Sprintf("race_%02d", i)
races[i] = fmt.Sprintf("race_%02d", i)
} }
_, err := game.ComposeGame(func(p *game.Param) { p.StoragePath = root }, races)
assert.NoError(t, err) func TestComposeGame(t *testing.T) {
_, err = game.ComposeGame(func(p *game.Param) { p.StoragePath = root }, races) g(t, func(p func(*game.Param), g func() mg.Game) {
_, err := game.GenerateGame(p, []string{"r1", "r2"})
assert.Error(t, err) assert.Error(t, err)
assert.ErrorContains(t, err, "state for turn 0 already saved") 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()
races := make([]string, testRaceCount)
for i := range testRaceCount {
races[i] = raceNum(i)
}
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)
} }
+52 -4
View File
@@ -25,14 +25,14 @@ func (g Game) Votes(raceID uuid.UUID) float64 {
return pop / 1000. 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 { if v, ok := g.raceID(name); ok {
return v, nil return v, nil
} }
return uuid.Nil, e.NewHostRaceUnknownError(name) 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 { if v, ok := g.raceID(name); ok {
return v, nil return v, nil
} }
@@ -48,20 +48,68 @@ func (g Game) raceID(raceName string) (uuid.UUID, bool) {
return uuid.Nil, false 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 { for r := range g.Race {
if g.Race[r].ID == hostID { if g.Race[r].ID == hostID {
for o := range g.Race[r].Relations { 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 g.Race[r].Relations[o].Relation = rel
return nil return nil
} }
} }
if hostID != opponentID {
return e.NewGameStateError("UpdateRelation: opponent not found") return e.NewGameStateError("UpdateRelation: opponent not found")
} }
} }
}
if hostID != opponentID {
return e.NewGameStateError("UpdateRelation: host %v not found", hostID) 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) { func (g Game) MarshalBinary() (data []byte, err error) {
return json.Marshal(&g) return json.Marshal(&g)
+2 -2
View File
@@ -25,8 +25,8 @@ type Race struct {
type Relation string type Relation string
const ( const (
RelationWar = "War" RelationWar Relation = "War"
RelationPeace = "Peace" RelationPeace Relation = "Peace"
) )
type RaceRelation struct { type RaceRelation struct {