From 282150a253460a1d2b77904c1584700616c520a2 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Fri, 26 Sep 2025 01:38:49 +0300 Subject: [PATCH] saving new turn --- pkg/game/game.go | 6 +-- pkg/model/game/game.go | 18 +++++-- pkg/model/game/map.go | 6 +-- pkg/model/game/planet.go | 25 +++++----- pkg/model/game/production.go | 4 +- pkg/model/game/race.go | 29 +++++++---- pkg/model/game/science.go | 13 +++-- pkg/model/game/ship.go | 33 +++++++------ .../game_test.go => model/game/ship_test.go} | 6 +-- pkg/repo/fs/fs.go | 4 ++ pkg/repo/fs/fs_test.go | 21 ++++++++ pkg/repo/game.go | 48 +++++++++++++++++++ pkg/repo/repo.go | 14 ++++++ 13 files changed, 170 insertions(+), 57 deletions(-) rename pkg/{game/game_test.go => model/game/ship_test.go} (93%) create mode 100644 pkg/repo/game.go diff --git a/pkg/game/game.go b/pkg/game/game.go index 9277578..5de43da 100644 --- a/pkg/game/game.go +++ b/pkg/game/game.go @@ -10,7 +10,7 @@ import ( ) type Repo interface { - Persist(game.Game) error + SaveTurn(uint, game.Game) error } func NewGame(r Repo, races []string) (uuid.UUID, error) { @@ -46,7 +46,7 @@ func NewGame(r Repo, races []string) (uuid.UUID, error) { g.Race[i] = game.Race{ ID: raceID, Name: races[i], - Ally: raceID, + Vote: raceID, Drive: 1, Weapons: 1, Shields: 1, @@ -104,7 +104,7 @@ func NewGame(r Repo, races []string) (uuid.UUID, error) { g.Map = *gameMap - if err := r.Persist(*g); err != nil { + if err := r.SaveTurn(0, *g); err != nil { return uuid.Nil, fmt.Errorf("persist: %s", err) } return g.ID, nil diff --git a/pkg/model/game/game.go b/pkg/model/game/game.go index 2126e6d..44543f8 100644 --- a/pkg/model/game/game.go +++ b/pkg/model/game/game.go @@ -1,12 +1,16 @@ package game -import "github.com/google/uuid" +import ( + "encoding/json" + + "github.com/google/uuid" +) type Game struct { - ID uuid.UUID - Age uint // Game's turn number - Map Map - Race []Race + ID uuid.UUID `json:"id"` + Age uint `json:"turn"` // Game's turn number + Map Map `json:"map"` + Race []Race `json:"races"` } func (g Game) Votes(raceID uuid.UUID) float64 { @@ -19,3 +23,7 @@ func (g Game) Votes(raceID uuid.UUID) float64 { } return pop / 1000. } + +func (g Game) MarshalBinary() (data []byte, err error) { + return json.Marshal(&g) +} diff --git a/pkg/model/game/map.go b/pkg/model/game/map.go index 4a60d0f..867e842 100644 --- a/pkg/model/game/map.go +++ b/pkg/model/game/map.go @@ -1,7 +1,7 @@ package game type Map struct { - Width uint32 - Height uint32 - Planet []Planet + Width uint32 `json:"width"` + Height uint32 `json:"height"` + Planet []Planet `json:"planets"` } diff --git a/pkg/model/game/planet.go b/pkg/model/game/planet.go index 2ac05c6..58e3879 100644 --- a/pkg/model/game/planet.go +++ b/pkg/model/game/planet.go @@ -7,21 +7,22 @@ import ( ) type Planet struct { - X, Y float64 - Size float64 + X float64 `json:"x"` + Y float64 `json:"y"` + Size float64 `json:"size"` - Name string - Number uint - Owner uuid.UUID + Name string `json:"name"` + Number uint `json:"number"` + Owner uuid.UUID `json:"owner"` - Production ProductionType - Population float64 // P - Население - Industry float64 // I - Промышленность - Resources float64 // R - Ресурсы / сырьё + Production ProductionType `json:"production"` + Population float64 `json:"population"` // P - Население + Industry float64 `json:"industry"` // I - Промышленность + Resources float64 `json:"resources"` // R - Ресурсы / сырьё - Capital float64 // CAP $ - Запасы промышленности - Material float64 // MAT M - Запасы ресурсов / сырья - Colonists float64 // COL C - Количество колонистов + Capital float64 `json:"capital"` // CAP $ - Запасы промышленности + Material float64 `json:"material"` // MAT M - Запасы ресурсов / сырья + Colonists float64 `json:"colonists"` // COL C - Количество колонистов // Параметр "L" - Свободный производственный потенциал } diff --git a/pkg/model/game/production.go b/pkg/model/game/production.go index 7fbbbed..10bc38c 100644 --- a/pkg/model/game/production.go +++ b/pkg/model/game/production.go @@ -17,8 +17,8 @@ const ( ) type ProductionType struct { - Production PlanetProduction - SubjectName string // TODO: change to UUID + Production PlanetProduction `json:"type"` + SubjectName string `json:"subject"` // TODO: change to UUID } func (p PlanetProduction) AsType(subject string) ProductionType { diff --git a/pkg/model/game/race.go b/pkg/model/game/race.go index a4cc0ef..2321951 100644 --- a/pkg/model/game/race.go +++ b/pkg/model/game/race.go @@ -3,17 +3,28 @@ package game import "github.com/google/uuid" type Race struct { - ID uuid.UUID - Name string - Killed bool + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Extinct bool `json:"extinct"` - // Votes float64 - Ally uuid.UUID // Race's Votes receiver + Vote uuid.UUID `json:"vote"` + Relations []RaceRelation `json:"relations"` - Drive float64 - Weapons float64 - Shields float64 - Cargo float64 + Drive float64 `json:"drive"` + Weapons float64 `json:"weapons"` + Shields float64 `json:"shields"` + Cargo float64 `json:"cargo"` + + Sciences []Science `json:"science,omitempty"` + + ShipTypes []ShipType `json:"shipType,omitempty"` + ShipGroups []ShipGroup `json:"shipGroup,omitempty"` + Fleets []Fleet `json:"fleet,omitempty"` +} + +type RaceRelation struct { + RaceID uuid.UUID `json:"raceId"` + Peace bool `json:"peace"` } func (r Race) FlightDistance() float64 { diff --git a/pkg/model/game/science.go b/pkg/model/game/science.go index 1fc821c..1c7cec4 100644 --- a/pkg/model/game/science.go +++ b/pkg/model/game/science.go @@ -1,9 +1,12 @@ package game +import "github.com/google/uuid" + type Science struct { - Name string - Drive float64 - Weapons float64 - Shields float64 - Cargo float64 + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Drive float64 `json:"drive"` + Weapons float64 `json:"weapons"` + Shields float64 `json:"shields"` + Cargo float64 `json:"cargo"` } diff --git a/pkg/model/game/ship.go b/pkg/model/game/ship.go index e7db4de..4c6e228 100644 --- a/pkg/model/game/ship.go +++ b/pkg/model/game/ship.go @@ -3,31 +3,34 @@ package game import ( "math" + "github.com/google/uuid" "github.com/iliadenisov/galaxy/pkg/number" ) type ShipType struct { - Name string - Drive float64 // [0], [1...] - Armament uint - Weapons float64 // [0], [1...] - Shields float64 // [0], [1...] - Cargo float64 // [0], [1...] + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Drive float64 `json:"drive"` // [0], [1...] + Armament uint `json:"armament"` + Weapons float64 `json:"weapons"` // [0], [1...] + Shields float64 `json:"shields"` // [0], [1...] + Cargo float64 `json:"cargo"` // [0], [1...] } type ShipGroup struct { - Type ShipType - Number uint - State string // TODO: kinda enum: In_Orbit, In_Space, Transfer_State, Upgrade - Load float64 // Cargo loaded - "Масса груза" - Drive float64 - Weapons float64 - Shields float64 - Cargo float64 + TypeID uuid.UUID `json:"id"` + Type ShipType `json:"-"` // TODO: fill upon load from store + Number uint `json:"number"` + State string `json:"state"` // TODO: kinda enum: In_Orbit, In_Space, Transfer_State, Upgrade + Load float64 `json:"load"` // Cargo loaded - "Масса груза" + Drive float64 `json:"drive"` + Weapons float64 `json:"weapons"` + Shields float64 `json:"shields"` + Cargo float64 `json:"cargo"` } type Fleet struct { - ShipGroups []ShipGroup + ShipGroups []ShipGroup `json:"group"` } // TODO: test on real values diff --git a/pkg/game/game_test.go b/pkg/model/game/ship_test.go similarity index 93% rename from pkg/game/game_test.go rename to pkg/model/game/ship_test.go index d9b310e..d3e1846 100644 --- a/pkg/game/game_test.go +++ b/pkg/model/game/ship_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_ShipType(t *testing.T) { +func TestShipType(t *testing.T) { Gunship := game.ShipType{ Drive: 4, Armament: 2, @@ -42,7 +42,7 @@ func Test_ShipType(t *testing.T) { assert.Equal(t, upgradeCost, 225.) } -func Test_CargoCapacity(t *testing.T) { +func TestCargoCapacity(t *testing.T) { test := func(cargoSize float64, expectCapacity float64) { ship := game.ShipType{ Drive: 1, @@ -69,7 +69,7 @@ func Test_CargoCapacity(t *testing.T) { test(100, 600) } -func Test_BombingPower(t *testing.T) { +func TestBombingPower(t *testing.T) { Gunship := game.ShipType{ Drive: 60.0, Armament: 3, diff --git a/pkg/repo/fs/fs.go b/pkg/repo/fs/fs.go index 317e3ac..cf64c2a 100644 --- a/pkg/repo/fs/fs.go +++ b/pkg/repo/fs/fs.go @@ -75,6 +75,10 @@ func (f *fs) Lock() (func() error, error) { return unlock, nil } +func (f *fs) Exist(path string) (bool, error) { + return fileExists(filepath.Join(f.root, path)) +} + func (f *fs) Write(path string, v encoding.BinaryMarshaler) error { if v == nil { return errors.New("cant't marshal from nil object") diff --git a/pkg/repo/fs/fs_test.go b/pkg/repo/fs/fs_test.go index 94b73ff..a0485cd 100644 --- a/pkg/repo/fs/fs_test.go +++ b/pkg/repo/fs/fs_test.go @@ -33,6 +33,27 @@ func TestLock(t *testing.T) { assert.False(t, exists, "lock file must be removed") } +func TestExist(t *testing.T) { + root, cleanup := createWorkDir(t) + defer cleanup() + + fileName := "some-file.ext" + if err := os.WriteFile(filepath.Join(root, fileName), []byte{1, 2, 3, 4}, os.ModePerm); err != nil { + t.Fatal(err) + } + + fs, err := NewFileStorage(root) + assert.NoError(t, err, "create file storage") + + exist, err := fs.Exist(fileName) + assert.NoError(t, err) + assert.True(t, exist) + + exist, err = fs.Exist("random/path") + assert.NoError(t, err) + assert.False(t, exist) +} + func TestWrite(t *testing.T) { root, cleanup := createWorkDir(t) defer cleanup() diff --git a/pkg/repo/game.go b/pkg/repo/game.go new file mode 100644 index 0000000..6c069a2 --- /dev/null +++ b/pkg/repo/game.go @@ -0,0 +1,48 @@ +package repo + +/* + /state.json + /000/state.json + /000/race/{UUID}/order/001.json + /000/race/{UUID}/report.json + /000/battle/{planet_UUID} +*/ + +import ( + "fmt" + + "github.com/iliadenisov/galaxy/pkg/model/game" +) + +func (r *repo) SaveTurn(t uint, g game.Game) error { + return saveTurn(r.s, t, g) +} + +func saveTurn(s Storage, t uint, g game.Game) error { + path := fmt.Sprintf("%03d/state.json", t) + exist, err := s.Exist(path) + if err != nil { + return NewStorageError(err) + } + if exist { + return NewStateError(fmt.Sprintf("state for turn %d already saved", t)) + } + if err := s.Write(path, g); err != nil { + return NewStorageError(err) + } + // TODO: save reports + // TODO: save battles + return saveState(s, g) +} + +func (r *repo) SaveState(g game.Game) error { + return saveState(r.s, g) +} + +func saveState(s Storage, g game.Game) error { + path := "state.json" + if err := s.Write(path, g); err != nil { + return NewStorageError(err) + } + return nil +} diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go index 7cc9e48..ee7fd7f 100644 --- a/pkg/repo/repo.go +++ b/pkg/repo/repo.go @@ -2,12 +2,26 @@ package repo import ( "encoding" + "errors" "github.com/iliadenisov/galaxy/pkg/repo/fs" ) +type StorageError error + +func NewStorageError(err error) error { + return StorageError(err) +} + +type StateError error + +func NewStateError(msg string) error { + return StateError(errors.New(msg)) +} + type Storage interface { Lock() (func() error, error) + Exist(string) (bool, error) Write(string, encoding.BinaryMarshaler) error Read(string, encoding.BinaryUnmarshaler) error }