diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..42f3349 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/iliadenisov/galaxy + +go 1.20 diff --git a/pkg/generator/draw/draw.go b/pkg/generator/draw/draw.go new file mode 100644 index 0000000..da72c26 --- /dev/null +++ b/pkg/generator/draw/draw.go @@ -0,0 +1,148 @@ +package draw + +import ( + "fmt" + "math" +) + +const batchSize = 32 + +type Plane struct { + Width uint + Height uint + pixels []uint32 +} + +func (p *Plane) pixelsValue() []uint32 { + return p.pixels +} + +func New(width uint, height uint) Plane { + elements := int(math.Ceil(float64(width*height) / batchSize)) + return Plane{Width: width, Height: height, pixels: make([]uint32, elements)} +} + +func (p Plane) Mark(x, y int) { + boundX := (p.Width + uint(x)) % p.Width + boundY := (p.Height + uint(y)) % p.Height + p.mark(boundX + boundY*p.Width) + + // p.mark(x + y*p.Width) + + // number := x + y*p.width + // index := number / batchSize + // bit := number % batchSize + // p.pixels[index] |= (0b1 << bit) +} + +func (p Plane) mark(number uint) { + // index := number / batchSize + // bit := number % batchSize + p.pixels[number/batchSize] |= (0b1 << (number % batchSize)) +} + +func (p Plane) Marked(x, y uint) bool { + return p.marked(x + y*p.Width) + // number := x + y*p.width + // index := number / batchSize + // bit := number % batchSize + // return p.pixels[index]&(0b1< 0 +} + +func (p Plane) marked(number uint) bool { + // index := number / batchSize + // bit := number % batchSize + return p.pixels[number/batchSize]&(0b1<<(number%batchSize)) > 0 +} + +func (p Plane) Circle(x, y int, r float64) { + plotX := 0 + plotY := int(math.Ceil(r)) + delta := 3 - 2*plotY + lastY := plotY + for plotX <= plotY { + p.octant(x, y, plotX, plotY) + if plotY < lastY { + for lineX := 0; lineX < plotX; lineX++ { + p.octant(x, y, lineX, plotY) + } + lastY = plotY + } + if delta < 0 { + delta += 4*plotX + 6 + } else { + delta += 4*(plotX-plotY) + 10 + plotY -= 1 + } + plotX += 1 + } + for fillX := 0; fillX < plotX; fillX++ { + for fillY := 0; fillY <= fillX; fillY++ { + p.octant(x, y, fillX, fillY) + } + } +} + +func (p Plane) CircleAdjacent(x, y int, r float64) { + plotX := 0 + plotY := int(math.Ceil(r)) + delta := 1 - 2*plotY + err := 0 + for plotX <= plotY { + p.octant(x, y, plotX, plotY) + err = 2*(delta+plotY) - 1 + if delta < 0 && err <= 0 { + plotX += 1 + delta += 2*plotX + 1 + continue + } + if delta > 0 && err > 0 { + plotY -= 1 + delta -= 2*plotY + 1 + continue + } + plotX += 1 + plotY -= 1 + delta += 2 * (plotX - plotY) + } +} + +func (p Plane) octant(x, y int, plotX, plotY int) { + p.Mark(x+plotX, y+plotY) + p.Mark(x+plotX, y-plotY) + p.Mark(x-plotX, y+plotY) + p.Mark(x-plotX, y-plotY) + p.Mark(x+plotY, y+plotX) + p.Mark(x+plotY, y-plotX) + p.Mark(x-plotY, y+plotX) + p.Mark(x-plotY, y-plotX) +} + +func (p Plane) Clear() { + for i := range p.pixels { + p.pixels[i] &= 0 + } +} + +func (p Plane) String() string { + px := map[bool]string{true: "x", false: "_"} + var result string + // for y := 0; y < int(p.height); y++ { + // for x := 0; x < int(p.width); x++ { + // result += px[p.Marked(uint(x), uint(y))] + " " + // } + // result += "\n" + // } + // result += "\n" + cnt := 0 + for i := 0; i < len(p.pixels); i++ { + for bit := 0; bit < batchSize && cnt < int(p.Width*p.Height); bit++ { + result += fmt.Sprintf("%s ", px[p.pixels[i]&(0b1< 0]) + cnt++ + if cnt%int(p.Width) == 0 { + result += "\n" + } + } + } + return result +} diff --git a/pkg/generator/draw/draw_test.go b/pkg/generator/draw/draw_test.go new file mode 100644 index 0000000..0a60269 --- /dev/null +++ b/pkg/generator/draw/draw_test.go @@ -0,0 +1,82 @@ +package draw_test + +import ( + "fmt" + "testing" + + "github.com/iliadenisov/galaxy/pkg/generator/draw" +) + +func Test_CreatePlot(t *testing.T) { + var pane = draw.New(10, 10) + if len(draw.PixelsHolder(&pane)) != 4 { + t.Errorf("wrong Pane data size, expected: %d given: %d", 4, len(draw.PixelsHolder(&pane))) + } + + pane = draw.New(1, 1) + if len(draw.PixelsHolder(&pane)) != 1 { + t.Errorf("wrong Pane data size, expected: %d given: %d", 4, len(draw.PixelsHolder(&pane))) + } + + pane = draw.New(32, 32) + if len(draw.PixelsHolder(&pane)) != 32 { + t.Errorf("wrong Pane data size, expected: %d given: %d", 4, len(draw.PixelsHolder(&pane))) + } +} + +func Test_Mark(t *testing.T) { + var size int = 7 + var pane = draw.New(uint(size), uint(size)) + for i := 0; i < int(size); i++ { + pane.Mark(0, i) + pane.Mark(i, 0) + pane.Mark(size-1, i) + pane.Mark(i, size-1) + pane.Mark(i, i) + pane.Mark(size-1-i, i) + } + + pixelsHolder := draw.PixelsHolder(&pane) + + if len(pixelsHolder) != 2 { + t.Errorf("expected len: %d given: %d", 2, len(pixelsHolder)) + } + + // if !pane.Marked(0, 0) { + // t.Errorf("should be marked at x=%d y=%d", 0, 0) + // } + + fmt.Println(pane) +} + +func Test_Cleat(t *testing.T) { + var pane = draw.New(10, 10) + pane.Mark(5, 5) + pane.Mark(0, 0) + pane.Mark(9, 9) + if !pane.Marked(0, 0) { + t.Errorf("should be marked at x=%d y=%d", 0, 0) + } + if !pane.Marked(5, 5) { + t.Errorf("should be marked at x=%d y=%d", 5, 5) + } + if !pane.Marked(9, 9) { + t.Errorf("should be marked at x=%d y=%d", 9, 9) + } + pane.Clear() + for _, holder := range draw.PixelsHolder(&pane) { + if holder != 0 { + t.Errorf("should be all bits clear") + } + } +} + +func Test_Circle(t *testing.T) { + var size int = 40 + var pane1 = draw.New(uint(size), uint(size)) + pane1.Circle(size/2+5, size/2-5, float64(size/2)-3) + fmt.Println(pane1) + var pane2 = draw.New(uint(size), uint(size)) + pane2.CircleAdjacent(size/2+20, size/2, float64(size/2)-5) + // fmt.Println(pane2) +} diff --git a/pkg/generator/draw/export_test.go b/pkg/generator/draw/export_test.go new file mode 100644 index 0000000..1c71f99 --- /dev/null +++ b/pkg/generator/draw/export_test.go @@ -0,0 +1,3 @@ +package draw + +var PixelsHolder = (*Plane).pixelsValue diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go new file mode 100644 index 0000000..3d5778b --- /dev/null +++ b/pkg/generator/generator.go @@ -0,0 +1,160 @@ +package generator + +import ( + "errors" + "fmt" + "math" + "math/rand" + + "github.com/iliadenisov/galaxy/pkg/generator/draw" +) + +type Map struct { + Width uint + Height uint + HomePlanets []PlanetarySystem + FreePlanets []Planet + plotter Plotter +} + +type Coordinate struct { + X, Y float64 +} + +type Planet struct { + Position Coordinate + Size float64 + Resources float64 // Сырьё +} + +type PlanetarySystem struct { + HW Planet + DW []Planet +} + +type Plotter struct { + factor float64 + sectors draw.Plane +} + +func Generate(param MapParameters) (result Map) { + pl := func(c Coordinate, param UninhabitedPlanetParameters) Planet { + return Planet{ + Position: c, + Size: param.MinSize + rand.Float64()*(param.MaxSize-param.MinSize), + Resources: float64(param.MinResource) + rand.Float64()*(param.MaxResource-param.MinResource)} + } + // mapSize := uint(math.Ceil(math.Sqrt(float64(param.Players)))) * param.HW_MinDistance + var mapSize uint = 200 + + result = NewMap(mapSize, mapSize, param.Players) + + totalPlanets := param.Players * 10 + freePlanets := totalPlanets - param.Players*(param.DW_Count+1) + + fmt.Println("map:", mapSize, "players:", param.Players, "planets:", totalPlanets, "uninhabited:", freePlanets) + + giantsNum := int(math.Ceil(float64(freePlanets) * param.GiantPlanets.Probability)) + fmt.Println("generating", giantsNum, "giant planets") + for i := 0; i < giantsNum; i++ { + coord := result.newPlanet(float64(param.GiantPlanets.MinDistanceHW)) + planet := pl(coord, param.GiantPlanets) + result.addPlanet(planet) + // result.FreePlanets = append(result.FreePlanets, planet) + } + + bigsNum := int(math.Ceil(float64(freePlanets) * param.BigPlanets.Probability)) + fmt.Println("generating", bigsNum, "big planets") + for i := 0; i < bigsNum; i++ { + coord := result.newPlanet(float64(param.BigPlanets.MinDistanceHW)) + planet := pl(coord, param.BigPlanets) + result.addPlanet(planet) + // result.FreePlanets = append(result.FreePlanets, planet) + } + + for player := 0; player < int(param.Players); player++ { + fmt.Println("generating HW #", player) + coord := result.newPlanet(float64(param.HW_MinDistance)) + planet := Planet{Position: coord, Size: float64(param.HW_Size), Resources: float64(param.HW_Resources)} + // fmt.Println("HW: ", planet) + result.HomePlanets[player] = PlanetarySystem{HW: planet} + } + + fmt.Println("free sectors left: ", len(result.plotter.freeSectors())) + + result.plotter.sectors.Clear() + + for _, hw := range result.HomePlanets { + result.plotter.markNoGoZone(hw.HW.Position.X, hw.HW.Position.Y, 5) + for _, dw := range hw.DW { + result.plotter.markNoGoZone(dw.Position.X, dw.Position.Y, 5) + } + } + + for _, planet := range result.FreePlanets { + result.plotter.markNoGoZone(planet.Position.X, planet.Position.Y, 5) + + } + + fmt.Println("free sectors after reset: ", len(result.plotter.freeSectors())) + + return +} + +func (m Map) newPlanet(deadZoneRaduis float64) Coordinate { + fs := m.plotter.freeSectors() + fmt.Println("free sectors: ", len(fs)) + fsCount := len(fs) + if fsCount == 0 { + panic("no more space for planets") + } + next := rand.Intn(fsCount) + x := fs[next][0] + y := fs[next][1] + fmt.Println("planet on plot: x =", x, "y =", y) + planetX := float64(x)*m.plotter.factor + rand.Float64()*m.plotter.factor + planetY := float64(y)*m.plotter.factor + rand.Float64()*m.plotter.factor + m.plotter.markDeadZone(int(x), int(y), deadZoneRaduis) + return Coordinate{X: planetX, Y: planetY} +} + +func (m *Map) addPlanet(planet Planet) { + m.FreePlanets = append(m.FreePlanets, planet) +} + +func (p Plotter) freeSectors() (result [][]uint) { + result = make([][]uint, 0) + for x := uint(0); x < p.sectors.Width; x++ { + for y := uint(0); y < p.sectors.Height; y++ { + if !p.sectors.Marked(x, y) { + result = append(result, []uint{x, y}) + } + } + } + return +} + +func (p Plotter) markDeadZone(x, y int, radius float64) { + p.sectors.Circle(x, y, radius) +} + +func (p Plotter) markNoGoZone(x, y float64, radius float64) { // TODO: test + p.markDeadZone(int(x/p.factor), int(y/p.factor), radius) +} + +func NewMap(width, height, players uint) Map { + var factor float64 = 1.0 + sectorsX := uint(float64(width) / factor) + sectorsY := uint(float64(height) / factor) + return Map{ + Width: width, + Height: height, + HomePlanets: make([]PlanetarySystem, players), + plotter: Plotter{ + factor: factor, + sectors: draw.New(sectorsX, sectorsY)}} +} + +func errfmt(message string) error { + return errors.New("generator: " + message) +} diff --git a/pkg/generator/generator_test.go b/pkg/generator/generator_test.go new file mode 100644 index 0000000..9e61687 --- /dev/null +++ b/pkg/generator/generator_test.go @@ -0,0 +1,11 @@ +package generator_test + +import ( + "testing" + + "github.com/iliadenisov/galaxy/pkg/generator" +) + +func Test_Generator(t *testing.T) { + generator.Generate(generator.DefaultMapParameters()) +} diff --git a/pkg/generator/map_parameter.go b/pkg/generator/map_parameter.go new file mode 100644 index 0000000..e878de1 --- /dev/null +++ b/pkg/generator/map_parameter.go @@ -0,0 +1,82 @@ +package generator + +type MapParameters struct { + Players uint + HW_Size uint + HW_Resources uint + HW_MinDistance uint + DW_Count uint + DW_Size uint + DW_Resources uint + DW_MinDistance uint + DW_MaxDistance uint + + GiantPlanets UninhabitedPlanetParameters + BigPlanets UninhabitedPlanetParameters + NormalPlanets UninhabitedPlanetParameters + RichPlanets UninhabitedPlanetParameters + Asterioids UninhabitedPlanetParameters +} + +type UninhabitedPlanetParameters struct { + MinDistanceHW uint + MinSize float64 + MaxSize float64 + MinResource float64 + MaxResource float64 + Probability float64 +} + +func DefaultMapParameters() MapParameters { + return MapParameters{ + Players: 25, + HW_Size: 1000, + HW_Resources: 10, + HW_MinDistance: 30, + DW_Count: 2, + DW_Size: 500, + DW_Resources: 10, + DW_MinDistance: 5, + DW_MaxDistance: 15, + GiantPlanets: UninhabitedPlanetParameters{ + MinDistanceHW: 20, + MinSize: 1500, + MaxSize: 2500, + MinResource: 0, + MaxResource: 3, + Probability: 0.06, + }, + BigPlanets: UninhabitedPlanetParameters{ + MinDistanceHW: 10, + MinSize: 1000, + MaxSize: 2000, + MinResource: 1, + MaxResource: 10, + Probability: 0.18, + }, + NormalPlanets: UninhabitedPlanetParameters{ + MinDistanceHW: 0, + MinSize: 0.001, + MaxSize: 1000, + MinResource: 0, + MaxResource: 10, + Probability: 0.5, + }, + RichPlanets: UninhabitedPlanetParameters{ + MinDistanceHW: 0, + MinSize: 0.001, + MaxSize: 500, + MinResource: 5, + MaxResource: 25, + Probability: 0.18, + }, + Asterioids: UninhabitedPlanetParameters{ + MinDistanceHW: 0, + MinSize: 0.001, + MaxSize: 10, + MinResource: 0, + MaxResource: 0, + Probability: 0.08, + }, + } +} diff --git a/pkg/server/game.go b/pkg/server/game.go new file mode 100644 index 0000000..47ff4c5 --- /dev/null +++ b/pkg/server/game.go @@ -0,0 +1,221 @@ +package server + +import "math" + +type GameIdentifier string +type RaceIdentifier string + +type Game struct { + Id GameIdentifier + Name string + Schedule string // TODO: implement somehow + Turn uint + Races []Race + Planets []Planet + warState map[uint]uint +} + +type Race struct { + Id RaceIdentifier + Name string + Drive float64 + Weapons float64 + Shields float64 + Cargo float64 + Planets []Planet +} + +func (r Race) FlightDistance() float64 { + return r.Drive * 40 +} + +func (r Race) VisibilityDistance() float64 { + return r.Drive * 30 +} + +type Coordinate struct { + X, Y float64 +} + +type Planet struct { + Number uint + Name string + Position Coordinate + Size float64 + Production string // TODO: kinda enum + Resources float64 // Сырьё + Industry float64 // Промышленность + Population float64 // Население + + Capital float64 // CAP $ - Запасы промышленности + Material float64 // MAT M - Запасы сырья + Colonists float64 // COL C - Количество колонистов + // Параметр "L" означает количество свободных производственных единиц. +} + +// Производственный потенциал (I) +// промышленность * 0.75 + население * 0.25 +func (p Planet) ProductionCapacity() float64 { + return p.Industry*0.75 + p.Population*0.25 +} + +// Производство промышленности +// TODO: test on real values +func (p *Planet) IncreaseIndustry() { + prod := p.ProductionCapacity() / 5 + industryIncrement := math.Min(prod, p.Material) + p.Industry += industryIncrement + if p.Industry > p.Population { + p.Industry = p.Population + p.Capital += p.Population - p.Industry + } +} + +// Производство материалов +// TODO: test on real values +func (p *Planet) IncreaseMaterial() { + p.Material += p.ProductionCapacity() * p.Industry +} + +// Автоматическое увеличение населения на каждом ходу +func (p *Planet) IncreasePopulation() { + p.Population *= 1.08 + var extraPopulation = p.Size - p.Population + if extraPopulation > 0 { + p.Colonists += extraPopulation / 8 + p.Population -= extraPopulation + } +} + +type Science struct { + Name string + Drive float64 + Weapons float64 + Shields float64 + Cargo float64 +} + +type ShipType struct { + Name string + Drive float64 // [0], [1...] + Armament uint + Weapons float64 // [0], [1...] + Shields float64 // [0], [1...] + Cargo float64 // [0], [1...] +} + +// TODO: test on real values +func (st ShipType) EmptyMass() float64 { + shipMass := st.DriveMass() + st.ShieldsMass() + st.CargoMass() + st.WeaponsMass() + return shipMass +} + +func (st ShipType) DriveMass() float64 { + return st.Drive +} + +func (st ShipType) ShieldsMass() float64 { + return st.Shields +} + +func (st ShipType) CargoMass() float64 { + return st.Cargo +} + +func (st ShipType) WeaponsMass() float64 { + return float64(st.Armament)*(st.Weapons/2) + st.Weapons/2 +} + +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 +} + +// Грузоподъёмность +func (sg ShipGroup) CargoCapacity() float64 { + return sg.Drive * (sg.Type.Cargo + (sg.Type.Cargo*sg.Type.Cargo)/20) +} + +// "Масса перевозимого груза" +func (sg ShipGroup) CarryingMass() float64 { + return sg.Load / sg.Cargo +} + +func (sg ShipGroup) FullMass() float64 { + return sg.Type.EmptyMass() + sg.CarryingMass() +} + +// "Эффективность двигателя" +// равна мощности Двигателей умноженной на текущий технологический уровень блока Двигателей +func (sg ShipGroup) DriveEffective() float64 { + return sg.Type.Drive * sg.Drive +} + +// TODO: test this +func (sg ShipGroup) Speed() float64 { + return sg.DriveEffective() * 20 / sg.FullMass() +} + +func (sg ShipGroup) UpgradeDriveCost(drive float64) float64 { + return (1 - sg.Drive/drive) * 10 * sg.Type.Drive +} + +// TODO: test on other values +func (sg ShipGroup) UpgradeWeaponsCost(weapons float64) float64 { + return (1 - sg.Weapons/weapons) * 10 * sg.Type.WeaponsMass() +} + +func (sg ShipGroup) UpgradeShieldsCost(shields float64) float64 { + return (1 - sg.Shields/shields) * 10 * sg.Type.Shields +} + +func (sg ShipGroup) UpgradeCargoCost(cargo float64) float64 { + return (1 - sg.Cargo/cargo) * 10 * sg.Type.Cargo +} + +// Мощность бомбардировки +// TODO: maybe rounding must be done only for display? +func (sg ShipGroup) BombingPower() float64 { + // return math.Sqrt(sg.Type.Weapons * sg.Weapons) + result := (math.Sqrt(sg.Type.Weapons*sg.Weapons)/10. + 1.) * + sg.Type.Weapons * + sg.Weapons * + float64(sg.Type.Armament) * + float64(sg.Number) + return toFixed3(result) +} + +type Fleet struct { + ShipGroups []ShipGroup +} + +// TODO: test this +func (fl Fleet) Speed() float64 { + result := math.MaxFloat64 + for _, sg := range fl.ShipGroups { + if sg.Speed() < result { + result = sg.Speed() + } + } + return result +} + +func round(num float64) int { + return int(num + math.Copysign(0.5, num)) +} + +// TODO: move to more common place +func toFixed(num float64, precision int) float64 { + output := math.Pow(10, float64(precision)) + return float64(round(num*output)) / output +} + +func toFixed3(num float64) float64 { + return toFixed(num, 3) +} diff --git a/pkg/server/game_test.go b/pkg/server/game_test.go new file mode 100644 index 0000000..bd1a51d --- /dev/null +++ b/pkg/server/game_test.go @@ -0,0 +1,105 @@ +package server_test + +import ( + "testing" + + "github.com/iliadenisov/galaxy/pkg/server" +) + +func Test_ShipType(t *testing.T) { + Gunship := server.ShipType{ + Drive: 4, + Armament: 2, + Weapons: 2, + Shields: 4, + Cargo: 0, + } + + if Gunship.EmptyMass() != 11. { + t.Errorf("Cruiser mass expected %.3f but %.3f given", 11., Gunship.EmptyMass()) + } + + Cruiser := server.ShipType{ + Drive: 15, + Armament: 1, + Weapons: 15, + Shields: 15, + Cargo: 0, + } + + if Cruiser.EmptyMass() != 45. { + t.Errorf("Cruiser mass expected %.3f but %.3f given", 45., Cruiser.EmptyMass()) + } + + sg := server.ShipGroup{ + Type: Cruiser, + Number: 1, + State: "In_Orbit", + Drive: 1.0, + Weapons: 1.0, + Shields: 1.0, + Cargo: 1.0, + } + + upgradeCost := sg.UpgradeDriveCost(2.0) + + sg.UpgradeWeaponsCost(2.0) + + sg.UpgradeShieldsCost(2.0) + + sg.UpgradeCargoCost(2.0) + + if upgradeCost != 225. { + t.Errorf("Cruiser upgrade cost expected %.3f but %.3f given", 225., upgradeCost) + } +} + +func Test_CargoCapacity(t *testing.T) { + test := func(cargoSize float64, expectCapacity float64) { + ship := server.ShipType{ + Drive: 1, + Armament: 1, + Weapons: 1, + Shields: 1, + Cargo: cargoSize, + } + sg := server.ShipGroup{ + Type: ship, + Number: 1, + State: "In_Orbit", + Drive: 1.0, + Weapons: 1.0, + Shields: 1.0, + Cargo: 1.0, + } + if sg.CargoCapacity() != expectCapacity { + t.Errorf("expected CargoCapacity=%.3f but %.3f given", expectCapacity, sg.CargoCapacity()) + } + } + test(1, 1.05) + test(5, 6.25) + test(10, 15) + test(50, 175) + test(100, 600) +} + +func Test_BombingPower(t *testing.T) { + Gunship := server.ShipType{ + Drive: 60.0, + Armament: 3, + Weapons: 30.0, + Shields: 100.0, + Cargo: 0.0, + } + sg := server.ShipGroup{ + Type: Gunship, + Number: 1, + State: "In_Orbit", + Drive: 1.0, + Weapons: 1.0, + Shields: 1.0, + Cargo: 1.0, + } + expectedBombingPower := 139.295 + result := sg.BombingPower() + if result != expectedBombingPower { + t.Errorf("expected BombingPower=%.3f but %.3f given", expectedBombingPower, result) + } +} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go new file mode 100644 index 0000000..7cd6055 --- /dev/null +++ b/pkg/storage/storage.go @@ -0,0 +1,26 @@ +package storage + +import "github.com/iliadenisov/galaxy/pkg/server" + +// games/ +// data.json - id, name, turn, schedule, status +// game123/ +// racelist//data.json - account_id, name, status, war/peace(?), last_order, etc. +// order///0.json - incoming orders +// turn/12/log/ - ? +// turn/12/order//0.json - processed orders +// turn/12/state/0.json - initital, contains for planet +// state/<1...N>.json - instant commands changes state +// turn/12/report/global.json +// report/.json +// turn/12/battle//.json + +type Storage interface { + ListGames() ([]server.GameIdentifier, error) + + LoadRace(game_id server.GameIdentifier, race_id server.RaceIdentifier) (server.Race, error) + SaveRace(game_id server.GameIdentifier, race server.Race) error + + LoadState(game_id server.GameIdentifier) (server.Game, error) + SaveState(game server.Game) error +}