diff --git a/.gitignore b/.gitignore index aba02a1..029231a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,32 @@ .idea/ +*.exe +resources/novusorbis.wav +novusorbis/goals.txt +resources/Novus Orbis.mp3 +resources/Novus.aup +resources/Novus.wav +resources/NovusWithMusic.aup +resources/NovusWithMusic.wav +resources/NovusWithMusicShort.aup +resources/NovusWithMusicShort.wav +resources/NovusWithMusicShort_data/e00/d00/e000028f.au +resources/NovusWithMusicShort_data/e00/d00/e0000317.au +resources/NovusWithMusicShort_data/e00/d00/e000059c.au +resources/NovusWithMusicShort_data/e00/d00/e000099e.au +resources/NovusWithMusicShort_data/e00/d00/e0000c35.au +resources/NovusWithMusicShort_data/e00/d00/e0000e75.au +resources/NovusWithMusic_data/e00/d00/e000015b.au +resources/NovusWithMusic_data/e00/d00/e000021e.au +resources/NovusWithMusic_data/e00/d00/e0000291.au +resources/NovusWithMusic_data/e00/d00/e000059c.au +resources/NovusWithMusic_data/e00/d00/e000075a.au +resources/NovusWithMusic_data/e00/d00/e0000b64.au +resources/NovusWithMusic_data/e00/d00/e0000c35.au +resources/NovusWithMusic_data/e00/d00/e0000c50.au +resources/NovusWithMusic_data/e00/d00/e0000d0a.au +resources/NovusWithMusic_data/e00/d00/e0000e0c.au +resources/Novus_data/e00/d00/e0000037.au +resources/Novus_data/e00/d00/e0000753.au +resources/Novus_data/e00/d00/e000075f.au +resources/Novus_data/e00/d00/e0000f31.au +resources/Orbis.wav diff --git a/novusorbis/cmd/startnovus/novusorbis.go b/novusorbis/cmd/startnovus/novusorbis.go new file mode 100644 index 0000000..2e1637f --- /dev/null +++ b/novusorbis/cmd/startnovus/novusorbis.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/subspace-engine/subspace/novusorbis/ui" + "github.com/subspace-engine/subspace/novusorbis/game" +) + +func main() { + inOut := ui.NewInputOutput() + questionAsker := game.QuestionAsker{InputOutput : inOut} + baseFactory := game.BaseFactory{QuestionAsker : questionAsker} + game := game.GameManager{InputOutput : inOut, BaseFactory: baseFactory} + game.Start() +} diff --git a/novusorbis/cmd/testworld/gametest.go b/novusorbis/cmd/testworld/gametest.go new file mode 100644 index 0000000..1a87f45 --- /dev/null +++ b/novusorbis/cmd/testworld/gametest.go @@ -0,0 +1,63 @@ +package main + +/** + * This file sets up a small test world, skipping any intro etc. + */ + +import ( + "github.com/subspace-engine/subspace/novusorbis/ui" + "github.com/subspace-engine/subspace/novusorbis/game" + "github.com/subspace-engine/subspace/novusorbis/world" +) + +type testGameManager struct { + *game.GameManager +} + +func main() { + inOut := ui.NewInputOutput() + questionAsker := game.QuestionAsker{InputOutput : inOut} + baseFactory := game.BaseFactory{QuestionAsker : questionAsker} + g := game.GameManager{InputOutput : inOut, BaseFactory: baseFactory} + t := testGameManager{&g} + t.initializeTestWorld() + t.InitializeCommandsMap() + t.SetUpDirectionMaps() + t.MainLoop() +} + +func (g *testGameManager) initializeTestWorld() { + w := &world.World{} + w.Size = 5 + g.World = w + mid := w.Size/2 + + c := world.Position{mid,mid,mid} + w.Cursor = c + + w.GenerateTerrain() + + thingStore := &world.MapMoverStore{} + thingStore.Initialize() + w.Things = thingStore + + w.Structures = &world.MapMoverStore{} + + w.MainColonist = g.CreateDefaultColonist() // TODO + w.MainBase = g.CreateDefaultBase() // TODO + + pos := world.Position{mid,mid,mid} + mainAvatar := g.World.MainColonist.Avatar + mainAvatar.SetPosition(pos) + thingStore.AddObjectAt(mainAvatar, pos) + + baseAvatar := g.World.MainBase.Avatar + baseAvatar.SetPosition(pos) + thingStore.AddObject(baseAvatar) + + icePos, _ := world.NewPosition(mid).RelativePosition(-1,-1,0) + ice := world.NewMover("ice", "i", icePos) + thingStore.AddObject(ice) + + return +} \ No newline at end of file diff --git a/novusorbis/game/directional.go b/novusorbis/game/directional.go new file mode 100644 index 0000000..b950ef1 --- /dev/null +++ b/novusorbis/game/directional.go @@ -0,0 +1,157 @@ +package game + +import ( + "strings" + "errors" + "github.com/subspace-engine/subspace/novusorbis/world" +) + +func (g *GameManager) SetUpDirectionMaps() (err error) { + letterToDirection := map[rune]Direction{'h':HERE,'n':NORTH,'e':EAST,'s':SOUTH, + 'w':WEST,'u':UP,'d':DOWN,'x':CANCEL,'p':SHOW_POSSIBILITIES} + g.LetterToDirection = letterToDirection + + directionToString := map[Direction]string{HERE : "Here", NORTH : "North", EAST : "East", + SOUTH : "South", WEST : "West", UP : "Up", DOWN : "Down"} + g.DirectionToString = directionToString + + err = nil + return +} + + +type Direction int + +const ( + HERE Direction = iota + NORTH + EAST + SOUTH + WEST + UP + DOWN + SHOW_POSSIBILITIES + CANCEL + RETRY +) + + +func (g *GameManager) GetDirection(dirString string) (dir Direction) { + dirChar := dirString[0] + dir, isThere := g.LetterToDirection[rune(dirChar)] + + if !isThere { + dir = RETRY + } + return +} + +func (g *GameManager) ReadDirection(actionString string, args []string) (dir Direction) { + if (len(args) <= 1) { + g.Println("In which direction would you like to " + actionString + + "? ('c' to cancel or 'p' for possible directions)") + dirString, _ := g.Read() + dirString = strings.ToLower(dirString) + if (strings.HasPrefix(dirString, actionString)) { + dirString = strings.TrimSpace(dirString[4:]) + } + dir = g.GetDirection(dirString) + if (dir == RETRY) { + return + } + } else { + dirString := args[1] + dir = g.GetDirection(dirString) + if (dir == RETRY) { + return + } + } + return +} + +func (g *GameManager) getDirectionalPosition(dir Direction) (pos world.Position, err error) { + switch dir { + case HERE: pos = world.Position{0, 0, 0} + case NORTH: pos = world.Position{0, 1, 0} + case EAST: pos = world.Position{1, 0, 0} + case SOUTH: pos = world.Position{0, -1, 0} + case WEST: pos = world.Position{-1, 0, 0} + case UP: pos = world.Position{0, 0, 1} + case DOWN: pos = world.Position{0, 0, -1} + default: err = errors.New("UnrecognizedDirection") + } + return +} + +// TODO direction calculation logic is not correct +func (g *GameManager) StateItemsAtSpecifiedDirection(actionString string, +cancellationString string, args []string) (pos world.Position, err error){ + w := g.World + var name1, name2 string + var things []world.Mover + var isClear bool + + dir := g.ReadDirection(actionString, args) + + if dir == SHOW_POSSIBILITIES { + g.Println("The possible directions are H, N, E, S, W, U, D") + err = errors.New("Showing possibilities") + return + } else if dir == CANCEL { + g.Println("Cancelled " + cancellationString + ".") + err = errors.New("Cancelled") + return + } + + pos, err = g.getDirectionalPosition(dir); + if (err != nil) { + g.Println("Unrecognized direction. The possible directions are H, N, E, S, W, U, D,") + // TODO let user try again + return + } + + name1, name2, things, isClear, _ = w.GetNamesOfTerrainsAndObjects(pos) + + dirName := g.DirectionToString[dir] + + var middleString string + var endString string + if (isClear) { + middleString = " above " + name2 + } + + if (things != nil) && (len(things) > 0) { + endString = " with: " + for i, thing := range things { + endString += thing.Name() + if (i < len(things) - 2) { + endString += "," + } else if (i == len(things) - 2) { + endString += ", and " + } + } + } + g.Println(dirName + " is " + name1 + middleString + endString + ".") + err = nil + return +} + +func (g *GameManager) Look(args []string) (err error) { + actionString := "look" + cancellationString := "looking" + g.StateItemsAtSpecifiedDirection(actionString, cancellationString, args) + return +} + +func (g *GameManager) Move(args []string) (err error) { + actionString := "look" + cancellationString := "looking" + pos, err := g.StateItemsAtSpecifiedDirection(actionString, cancellationString, args) + if (err != nil) { + err = nil + return + } + g.World.ShiftColonist(pos) + err = nil + return +} \ No newline at end of file diff --git a/novusorbis/game/game.go b/novusorbis/game/game.go new file mode 100644 index 0000000..e5aab82 --- /dev/null +++ b/novusorbis/game/game.go @@ -0,0 +1,306 @@ +package game + +import ( + "strings" + "sort" + "strconv" + "time" + "github.com/subspace-engine/subspace/novusorbis/world" + "github.com/subspace-engine/subspace/novusorbis/ui" +) + +const GAME_LOGO = ` + :::;;;;;;::, + ,,,:::;;;;;:""'''":, + .,,,:::;;;;" ' + ...,,,:::;;;" N O V U S " + ...,,,,:::;;;" " + ...,,,:::;;;". O R B I S "; + ...,,,::::;;;". .";;; + ....,,,::::;;;;;:,....,:;;;;: + ....,,,:::::;;;;;;;;;;;;;::: + ....,,,,::::::;;;;;;;:::::: + ....,,,,,:::::::::::::,,, + .....,,,,,,,,,,,,,,,,,, + .......,,,,,,,,,... + ............... + + + + +` + +type GameManager struct{ + ui.InputOutput + CommandsMap map[string]func(args []string) error + World *world.World + LetterToDirection map[rune]Direction + DirectionToString map[Direction]string + BaseFactory BaseFactory +} + +func (g *GameManager) Start() { + g.PrintLogo() + g.InitializeCommandsMap() + g.SetUpDirectionMaps() + g.CreateWorld() + g.MainLoop() +} + +func (g *GameManager) PrintLogo() { + for _, r := range GAME_LOGO { + c := string(r) + g.Print(c) + if (r == rune('\n')) { + time.Sleep(250 * time.Millisecond) + } + } +} + +type ExitCalled struct { + s string +} + +func (e *ExitCalled) Error() string { + return e.s +} + +func (g *GameManager) InitializeCommandsMap() { + g.CommandsMap = make(map[string]func(args []string) error) + g.CommandsMap["exit"] = g.Exit + g.CommandsMap["q"] = g.Exit + g.CommandsMap["commands"] = g.PrintCommands + g.CommandsMap["look"] = g.Look + g.CommandsMap["move"] = g.Move + g.CommandsMap["pos"] = g.Position + g.CommandsMap["draw"] = g.DrawWorld + g.CommandsMap["take"] = g.TakeObject + g.CommandsMap["inv"] = g.ShowInventory +} + +func (g *GameManager) MainLoop() { + Loop: + for { + if doExit := g.LoopStep() ; doExit != nil { + break Loop + } + } +} + +func (g *GameManager) DrawWorld(args []string) (err error) { + z := g.World.Size/2 + if (len(args) > 1) { + z, err = strconv.Atoi(strings.TrimSpace(args[1])) + } + g.Println("Drawing terrain at " + strconv.Itoa(z)) + drawnTerrain, _ := g.World.DrawnWorldAtZ(z) + + g.Println(drawnTerrain) + + return nil +} + +func (g *GameManager) Position(args []string) (err error) { + err = nil + p := g.World.MainColonist.Avatar.Position() + g.Println("(" + strconv.Itoa(p.X) + ", " + strconv.Itoa(p.Y) + ", " + strconv.Itoa(p.Z) + ")") + return +} + +func (g *GameManager) Exit(args []string) (err error) { + g.Println("Are you sure you want to exit? (y/n)") + answer, err := g.Read() + answer = strings.ToLower(answer) + if (len(answer) > 0 && answer[0] == 'y') { + g.Println("Returning to reality.") + err = &ExitCalled{"A subroutine called exit"} + } else { + g.Println("Cancelled exit") + err = nil + } + return +} + +func (g *GameManager) LoopStep() (err error) { + err = nil + str, err := g.Read() + line := strings.Fields(strings.ToLower(str)) + + if len(line) == 0 { + return + } + + command := line[0] + commandsMap := g.CommandsMap + + commandFunction := commandsMap[command] + if commandFunction == nil { + g.Println("Command \"" + command + "\" not recognized.") + g.PrintCommands([]string{}) + } else { + err = commandFunction(line) + } + return +} + +func (g *GameManager) PrintCommands(args []string) (err error) { + commandsMap := g.CommandsMap + + keys := make([]string, 0, len(commandsMap)) + for k := range commandsMap { + keys = append(keys, k) + } + sort.Strings(keys) + + g.Print("The available commands are: ") + g.Println(strings.Join(keys, ", ")) + + err = nil + return +} + +func (g *GameManager) CreateWorld() (err error) { + return nil // TODO + /* + w := &world.World{} + w.Size = 5 + g.World = w + mid := w.Size/2 + + c := world.Position{mid,mid,mid} + w.Cursor = c + + w.GenerateTerrain() + + thingStore := &world.MapMoverStore{} + thingStore.Initialize() + w.Things = thingStore + + w.Structures = world.MapMoverStore{} + + w.MainColonist = g.CreateDefaultColonist() // TODO + w.MainBase = g.CreateDefaultBase() // TODO + + pos := world.Position{mid,mid,mid} + mainAvatar := g.World.MainColonist.Avatar + mainAvatar.SetPosition(pos) + thingStore.AddObjectAt(mainAvatar, pos) + + baseAvatar := g.World.MainBase.Avatar + baseAvatar.SetPosition(pos) + thingStore.AddObjectAt(baseAvatar, pos) + + err = nil + return + */ +} + +func (g *GameManager) CreateDefaultColonist() (mainColonist *world.Colonist) { + mainColonist = world.NewDefaultColonist() + return +} + +func (g *GameManager) CreateDefaultBase() (base *world.Base) { + base = world.NewDefaultBase() + return +} + + +func (g *GameManager) CreateColonist() (mainColonist *world.Colonist) { + g.Println("What would you like to name your first colonist?") + name, _ := g.Read() + + mainColonist = &world.Colonist{Name: name, Avatar:world.NewMover("You", "@", world.Position{2,2,2})} + g.Println("Creating a colonist with the name: \"" + mainColonist.Name + "\", is this correct? (y/n)") + answer, _ := g.Read() + answer = strings.ToLower(answer) + if (len(answer) > 0 && answer[0] == 'y') { + g.Println("Colonist with name \"" + mainColonist.Name + "\" created.") + } else { + mainColonist = g.CreateColonist() + } + return +} + +type QuestionAsker struct { + ui.InputOutput +} + +func (q *QuestionAsker) Ask(question string) (answer string) { + q.Println(question) + answer, _ = q.Read() + answer = strings.ToLower(answer) + return +} + +func (q *QuestionAsker) AskYesNo(question string) (yesno bool) { + q.Println(question) + answer, _ := q.Read() + answer = strings.ToLower(answer) + + if (answer[0] == 'y') { + yesno = true + } else { + yesno = false + } + return +} + +type BaseFactory struct{ + QuestionAsker +} + +func (b *BaseFactory) CreateBase() (base *world.Base) { + name := b.Ask("What would you like to name your base?") + base = &world.Base{Name: "Base" + name, Avatar: world.NewStructure("Base " + name, "B", world.Position{2,2,2})} + isAnswerYes := b.AskYesNo("Naming your base: \"" + base.Name + "\", is this correct? (y/n)") + + if (isAnswerYes) { + b.Println("Base with name \"" + base.Name + "\" created.") + } else { + base = b.CreateBase() + } + return +} + +func (g *GameManager) CreateBase() (base *world.Base){ + base = g.BaseFactory.CreateBase() // TODO Create Base Factory + return +} + +func (g *GameManager) TakeObject(args []string) (err error) { + // TODO specify which object(s) to take + // TODO differentiate between mobile and immobile objects + colonist := g.World.MainColonist + pos := colonist.Avatar.Position() + thingsHere, err := g.World.Things.AtPosition(pos) + if (len(thingsHere) <= 1) { + g.Println("Nothing to take.") + return + } + + g.Println("Took: ") + for index, thing := range thingsHere { + if (thing == colonist.Avatar) { + continue + } + g.Println(strconv.Itoa(index) + ". " + thing.Name()) + colonist.Inventory.AddObject(thing) + g.World.Things.Remove(thing, pos) + } + return +} + +func (g *GameManager) ShowInventory(args []string) (err error) { + colonist := g.World.MainColonist + inventory := colonist.Inventory.GetContents() + if (len(inventory) == 0) { + g.Println("Inventory empty") + return + } + g.Println("Inventory: ") + for index, thing := range inventory { + g.Println(strconv.Itoa(index) + ". " + thing.Name()) + } + return +} diff --git a/novusorbis/game/game_test.go b/novusorbis/game/game_test.go new file mode 100644 index 0000000..2d9af2c --- /dev/null +++ b/novusorbis/game/game_test.go @@ -0,0 +1,81 @@ +package game_test + +import ( + "testing" + "errors" + "github.com/subspace-engine/subspace/novusorbis/game" +) + +type MockIO struct { + InIter int + Announcements []string + CollectedPrints []string +} + +func NewMockIO(announcements []string) (*MockIO) { + collectedPrints := make([]string, 0) + return &MockIO{InIter:0, Announcements:announcements, CollectedPrints:collectedPrints} +} + +func (t *MockIO) Print(s string) { + t.CollectedPrints = append(t.CollectedPrints, s) +} + +func (t *MockIO) Println(s string) { + t.CollectedPrints = append(t.CollectedPrints, s) +} + +func (t *MockIO) Read() (s string, err error){ + err = nil + if t.InIter < len(t.Announcements) { + s = t.Announcements[t.InIter] + t.InIter++ + return + } + err = errors.New("Tried to read too many outputs") + return +} + +func TestMockioReading(t *testing.T) { + mockIO := NewMockIO([]string{"hello"}) + read, err := mockIO.Read() + if (err != nil || read != "hello") { + t.Fail() + } +} + +func TestMockioReadingMultiple(t *testing.T) { + mockIO := NewMockIO([]string{"Hello,", "World!"}) + read1, err := mockIO.Read() + read2, err := mockIO.Read() + if (err != nil || read1 != "Hello," || read2 != "World!") { + t.Fatalf("Expected \"%s\" but got \"%s\", \"%s\"", "Hello, World!", read1, read2) + } +} + +func TestMockioWriting(t *testing.T) { + mockIO := NewMockIO([]string{}) + mockIO.Print("Hello!") + outputs := mockIO.CollectedPrints + if (outputs[0] != "Hello!") { + t.Fatalf("Expected \"%s\" but got \"%s\"", "Hello!", outputs[0]) + } +} + +func TestExitCalled_Error(t *testing.T) { + mockIo := NewMockIO([]string{"y"}) + g := game.GameManager{InputOutput : mockIo} + err := g.Exit([]string{}) + outputs := mockIo.CollectedPrints + if (err == nil) { + t.Fatalf("Expected an error but got nil. Outputs: %v", outputs) + } +} + +func TestGameManager_Exit(t *testing.T) { + mockIo := NewMockIO([]string{"q", "y"}) + g := game.GameManager{InputOutput : mockIo} + g.InitializeCommandsMap() + g.MainLoop() + // If this doesn't work we expect a timeout +} \ No newline at end of file diff --git a/novusorbis/ui/terminal.go b/novusorbis/ui/terminal.go new file mode 100644 index 0000000..e39a915 --- /dev/null +++ b/novusorbis/ui/terminal.go @@ -0,0 +1,38 @@ +package ui + +import ( + "fmt" + "bufio" + "os" + "strings" +) + +type InputOutput interface { + Print(s string) + Println(s string) + Read() (s string, err error) +} + +type ui struct { + reader *bufio.Reader +} + +func NewInputOutput() (io InputOutput) { + reader := bufio.NewReader(os.Stdin) + io = &ui{reader} + return +} + +func (u *ui) Print(message string) { + fmt.Print(message) +} + +func (u *ui) Println(message string) { + fmt.Println(message) +} + +func (u *ui) Read() (message string, err error) { + message , err = u.reader.ReadString('\n') + message = strings.TrimSpace(message) + return +} \ No newline at end of file diff --git a/novusorbis/ui/ui_test.go b/novusorbis/ui/ui_test.go new file mode 100644 index 0000000..89e6ba0 --- /dev/null +++ b/novusorbis/ui/ui_test.go @@ -0,0 +1,23 @@ +package ui_test + +import ( + // "testing" + "github.com/subspace-engine/subspace/novusorbis/ui" +) + +func ExampleUi_Print() { + io := ui.NewInputOutput() + io.Print("Test String 1, ") + io.Print("Test String 2") + + // Output: Test String 1, Test String 2 +} + + +func ExampleUi_Println() { + io := ui.NewInputOutput() + io.Println("Test String 1,") + io.Println("Test String 2") + // Output: Test String 1, + // Test String 2 +} \ No newline at end of file diff --git a/novusorbis/world/structures.go b/novusorbis/world/structures.go new file mode 100644 index 0000000..d3045f0 --- /dev/null +++ b/novusorbis/world/structures.go @@ -0,0 +1,15 @@ +package world + +type Structure interface { + Mover + Container +} + +type BasicStructure struct { + Mover + Container +} + +func NewStructure(name string, symbol string, position Position) (Structure) { + return &BasicStructure{NewMover(name, symbol, position), NewContainer()} +} diff --git a/novusorbis/world/terrain.go b/novusorbis/world/terrain.go new file mode 100644 index 0000000..6d69eff --- /dev/null +++ b/novusorbis/world/terrain.go @@ -0,0 +1,120 @@ +package world + +type Terrain struct { + size int + voxels [][][]TerrainType + TerrainToString map[TerrainType]string + TerrainToSymbol map[TerrainType]string + TerrainToOpacity map[TerrainType]TerrainOpacity +} + +type TerrainType uint8 +type TerrainOpacity bool + +const ( + SPACE TerrainType = iota + GAS + SMOKE + SAND + STONE + UNSEALED_STRUCTURE + AIRTIGHT_STRUCTURE + SOLID_STRUCTURE + UNKNOWN +) + +const ( + OPAQUE TerrainOpacity = true + CLEAR = false +) + +func (t *Terrain) SetUpTerrainConversions() (err error) { + terrainToString := map[TerrainType]string{SPACE : "space", + GAS : "clear gas", SMOKE: "opaque gas", + SAND : "soft terrain", STONE : "hard terrain", + UNSEALED_STRUCTURE : "unsealed structure", + AIRTIGHT_STRUCTURE : "airtight structure", + SOLID_STRUCTURE : "solid structure", + UNKNOWN : "unknown"} + t.TerrainToString = terrainToString + + terrainToSymbol := map[TerrainType]string{SPACE: ".", GAS: "-", + SMOKE: "~" , SAND: ":", + STONE: "^", UNSEALED_STRUCTURE: "U", AIRTIGHT_STRUCTURE: "A", + SOLID_STRUCTURE: "S", UNKNOWN: "_"} + t.TerrainToSymbol = terrainToSymbol + + terrainToOpacity := map[TerrainType]TerrainOpacity{SPACE: CLEAR, GAS: CLEAR, + SMOKE:OPAQUE, SAND: OPAQUE, + STONE: OPAQUE, UNSEALED_STRUCTURE: CLEAR, + AIRTIGHT_STRUCTURE: CLEAR, + SOLID_STRUCTURE: CLEAR, + UNKNOWN: OPAQUE} + t.TerrainToOpacity = terrainToOpacity + + err = nil + return +} + +func (t *Terrain) GetNameOfTerrainAt(p Position) (terrainName string, err error) { + terrainType, err := t.GetTerrainTypeAt(p) + terrainName = t.TerrainToString[terrainType] + return +} + +func (t *Terrain) GetSymbolOfTerrainAt(p Position) (terrainChar string, err error) { + terrainType, err := t.GetTerrainTypeAt(p) + terrainChar = t.TerrainToSymbol[terrainType] + return +} + +func (t *Terrain) GetTerrainTypeAt(p Position) (terrainType TerrainType, err error) { + terrainType = t.voxels[p.Z][p.Y][p.X] + err = nil + return +} + +func (w *World) GenerateTerrain() (err error) { + var size = w.Size + mid := size/2 + voxels := make([][][]TerrainType, size) + for z := 0; z < size; z++ { + voxels[z] = make([][]TerrainType, size) + for y := 0; y < size; y++ { + voxels[z][y] = make([]TerrainType, size) + } + } + + for z := 0; z < mid ; z++ { + for x := 0; x < size; x++ { + for y:=0; y < size; y++ { + voxels[z][y][x] = STONE + } + } + } + + for z := mid; z < size-1 ; z++ { + for x := 0; x < size; x++ { + for y:=0; y < size; y++ { + voxels[z][y][x] = GAS + } + } + } + + for z := size-1; z < size ; z++ { + for x := 0; x < size; x++ { + for y:=0; y < size; y++ { + voxels[z][y][x] = SPACE + } + } + } + + voxels[mid][mid][mid] = AIRTIGHT_STRUCTURE + voxels[mid][mid][mid+1] = STONE + voxels[mid+1][mid+1][mid-1] = STONE + + terrain := &Terrain{size:size, voxels:voxels} + terrain.SetUpTerrainConversions() + w.Terrain = terrain + return nil +} \ No newline at end of file diff --git a/novusorbis/world/things.go b/novusorbis/world/things.go new file mode 100644 index 0000000..70debbb --- /dev/null +++ b/novusorbis/world/things.go @@ -0,0 +1,149 @@ +package world + +type NamedThing interface { + Name() (s string) +} + +// For now, a mover is anything with a position, even if it doesn't actually move +type Mover interface { + NamedThing + Symbol() (s string) + Position() (p Position) + SetPosition(p Position) +} + +type BasicNamedThing struct { + name string +} + +type BasicMover struct { + NamedThing + symbol string + position Position +} + +func NewNamedThing(Name string) (NamedThing){ + return &BasicNamedThing{name: Name} +} + +func NewMover(Name string, Symbol string, Position Position) (Mover) { + return &BasicMover{NamedThing : NewNamedThing(Name), symbol: Symbol, position: Position} +} + +type MoverStore interface { + Initialize() + AtPosition(p Position) (things []Mover, err error) + AddObjectAt(obj Mover, p Position) (err error) + MoveObjectTo(obj Mover, p Position) (err error) + ShiftObjBy(obj Mover, p Position) (err error) + Remove(obj Mover, p Position) (err error) +} + +type MapMoverStore struct { + Movers map[Position][]Mover +} + +func (store *MapMoverStore) Initialize() { + store.Movers = make(map[Position][]Mover) +} + +func (store *MapMoverStore) AtPosition(p Position) (movers []Mover, err error) { + movers, _ = store.Movers[p] + err = nil + return +} + +func (store *MapMoverStore) AddObjectAt(obj Mover, p Position) (err error) { + const DEFAULT_STORE_SIZE = 3 + if store.Movers[p] == nil { + store.Movers[p] = make([]Mover, 0, DEFAULT_STORE_SIZE) + } + store.Movers[p] = append(store.Movers[p], obj) + err = nil + return +} + +func (store *MapMoverStore) AddObject(obj Mover) (err error) { + const DEFAULT_STORE_SIZE = 3 + p := obj.Position() + if store.Movers[p] == nil { + store.Movers[p] = make([]Mover, 0, DEFAULT_STORE_SIZE) + } + store.Movers[p] = append(store.Movers[p], obj) + err = nil + return +} + +func remove(s []Mover, i int) []Mover { + s[len(s)-1], s[i] = s[i], s[len(s)-1] + return s[:len(s)-1] +} + +func (store *MapMoverStore) Remove(obj Mover, p Position) (err error) { + things, err := store.AtPosition(p) + for index, element := range things { + if(element == obj) { + things = remove(things , index) + store.Movers[p] = things + return + } + } + return +} + +func (store *MapMoverStore) MoveObjectTo(obj Mover, p Position) (err error) { + const DEFAULT_STORE_SIZE = 3 + if store.Movers[p] == nil { + store.Movers[p] = make([]Mover, 0, DEFAULT_STORE_SIZE) + } + origPos := obj.Position() + store.Remove(obj, origPos) + store.Movers[p] = append(store.Movers[p], obj) + + err = nil + return +} + +func (store *MapMoverStore) ShiftObjBy(obj Mover, p Position) (err error) { + newPos, err := obj.Position().RelativePosition(p.X, p.Y, p.Z) + store.MoveObjectTo(obj,newPos) + return +} + +func (thing *BasicNamedThing) Name() (s string) { + return thing.name +} + +func (thing *BasicMover) Symbol() (s string){ + return thing.symbol +} + +func (thing *BasicMover) Position() (p Position) { + return thing.position +} + +func (thing *BasicMover) SetPosition(p Position) { + thing.position = p +} + + +type Container interface { + AddObject(thing NamedThing) + GetContents() (things []NamedThing) +} + +type BasicContainer struct { + things []NamedThing +} + +func NewContainer() (Container) { + return &BasicContainer{things : make([]NamedThing, 0, 4)} +} + +func (c *BasicContainer) AddObject(thing NamedThing) { + c.things = append(c.things, thing) +} + +func (c *BasicContainer) GetContents() ([]NamedThing){ + return c.things +} diff --git a/novusorbis/world/world.go b/novusorbis/world/world.go new file mode 100644 index 0000000..d61ed8d --- /dev/null +++ b/novusorbis/world/world.go @@ -0,0 +1,135 @@ +package world + +import ( + "bytes" +) + +type World struct { + MainColonist *Colonist + MainBase *Base + Size int + Terrain *Terrain + Things MoverStore + Structures MoverStore + Cursor Position +} + +type Position struct { + X int + Y int + Z int +} + +type Base struct { + Name string + Avatar Structure +} + +type Colonist struct { + Name string + Avatar Mover + Inventory Container +} + +func NewPosition(midpoint int) (pos Position) { + pos = Position{X: midpoint, Y: midpoint, Z: midpoint} + return +} + +func NewDefaultColonist() (*Colonist){ + return &Colonist{"You", NewMover("Mark", "@" , Position{2,2,2}), NewContainer()} +} + +func NewDefaultBase() (*Base){ + return &Base{Name : "Base Omicron", Avatar : NewStructure("Base", "B", Position{2,2,2})} +} + +func (w *World) ShiftThing(thing Mover, pos Position) (err error) { + w.Things.ShiftObjBy(thing, pos) + newPos, err := thing.Position().RelativePosition(pos.X, pos.Y, pos.Z) + thing.SetPosition(newPos) + err = nil + return +} + +func (w *World) ShiftColonist(pos Position) (err error) { + c := w.MainColonist + w.ShiftThing(c.Avatar, pos) + err = w.ShiftCursor(pos) + return +} + +func (w *World) ShiftCursor(pos Position) (err error) { + w.Cursor, err = w.Cursor.RelativePosition(pos.X, pos.Y, pos.Z) + return err +} + +func (inPos Position) RelativePosition(x int, y int, z int) (outPos Position, err error) { + outPos = Position{inPos.X + x, inPos.Y + y, inPos.Z + z} + err = nil + return +} + +func (w *World) DrawnWorldAtZ(z int) (drawnWorld string, err error){ + size := w.Size + err = nil + var byteWorld bytes.Buffer + + if z >= size || z < 0 { + drawnWorld = "z value out of range\n" + return + } + + for y:=size-1; y >= 0; y-- { + for x := 0; x < size; x++ { + symbol, _ := w.GetSymbolOfWorldAt(Position{x,y,z}) + byteWorld.WriteString(symbol) + } + byteWorld.WriteString("\n") + } + + drawnWorld = byteWorld.String() + return +} + +func (w *World) GetSymbolOfWorldAt(p Position) (worldChar string, err error) { + err = nil + + terrain := w.Terrain + things, _ := w.Things.AtPosition(p) + if len(things) == 0 { + worldChar, err = terrain.GetSymbolOfTerrainAt(p) + return + } + + for _, thing := range things { + if (thing == w.MainColonist.Avatar) { + worldChar = thing.Symbol() + return + } + } + worldChar = things[0].Symbol() + return +} + +func (w *World) GetNamesOfTerrainsAndObjects(relPos Position) (name1 string, + name2 string, + things []Mover, + isClear bool, + err error) { + pos, _ := w.Cursor.RelativePosition(relPos.X, relPos.Y, relPos.Z) + terrainType, _ := w.Terrain.GetTerrainTypeAt(pos) + name1, _ = w.Terrain.GetNameOfTerrainAt(pos) + isClear = (w.Terrain.TerrainToOpacity[terrainType] == CLEAR) + + things, _ = w.Things.AtPosition(pos) + + if (isClear) { + pos, _ = pos.RelativePosition(0, 0, -1) + name2, _ = w.Terrain.GetNameOfTerrainAt(pos) + } + + err = nil + return +} + diff --git a/resources/Ascii-sphere-7.txt b/resources/Ascii-sphere-7.txt new file mode 100644 index 0000000..da14585 --- /dev/null +++ b/resources/Ascii-sphere-7.txt @@ -0,0 +1,14 @@ + :::;;;;;;::, + ,,,:::;;;;;:""'''":, + .,,,:::;;;;" ' + ...,,,:::;;;" N O V U S " + ...,,,,:::;;;" " + ...,,,:::;;;". O R B I S "; + ...,,,::::;;;". .";;; + ....,,,::::;;;;;:,....,:;;;;: + ....,,,:::::;;;;;;;;;;;;;::: + ....,,,,::::::;;;;;;;:::::: + ....,,,,,:::::::::::::,,, + .....,,,,,,,,,,,,,,,,,, + .......,,,,,,,,,... + ............... \ No newline at end of file diff --git a/resources/DemiSphere.png b/resources/DemiSphere.png new file mode 100644 index 0000000..a429665 Binary files /dev/null and b/resources/DemiSphere.png differ diff --git a/subspace.exe b/subspace.exe deleted file mode 100644 index 7772ac4..0000000 Binary files a/subspace.exe and /dev/null differ diff --git a/subspace.exe~ b/subspace.exe~ new file mode 100644 index 0000000..532422b Binary files /dev/null and b/subspace.exe~ differ diff --git a/subspace.go b/subspace.go index b93d82d..0db886e 100644 --- a/subspace.go +++ b/subspace.go @@ -1,67 +1,5 @@ package main -import ( - "github.com/subspace-engine/subspace/world" - "bufio" - "net" - "strings" -) - -func handleConnection(con net.Conn, sim *world.Sim) { - defer con.Close() - rw := bufio.NewReadWriter(bufio.NewReader(con), bufio.NewWriter(con)) - agent := world.NewWObject("you", world.NewBox(0,0,0,1,2,1)) - client := world.NewClient(agent) - sim.AddClient(client) - rw.WriteString("Subspace 1.0\n") - rw.Flush() - for { - s, err := rw.ReadString('\n') - if err != nil { - return - } - s = strings.TrimSpace(s) - if s == "done" { - rw.WriteString("bye\n") - rw.Flush() - return - } - client.Queue <- s - resp := <- client.Queue - rw.WriteString(resp) - rw.Flush() - } -} - -func RunSim(sim *world.Sim) { - world1 := world.NewWObject("world", world.NewBox(-1000000, -1000000, -1000000, 2000000, 2000000, 2000000)) - sim.AddWorld(world1) - sim.Run() -} - func main() { - runTermbox() - - /* - obj := world.NewObject() - fmt.Println(obj) - fmt.Println("Subspace Core Starting") - ln, err := net.Listen("tcp", ":4444") - if err != nil { - fmt.Println("Unable to create listener!") - return - } - sim := world.NewSim() - go RunSim(&sim) - for { - con, err := ln.Accept() - if err != nil { - fmt.Println("Unable to accept connection!") - return - } - - go handleConnection(con, &sim) - } - - */ + // TODO } diff --git a/subspace_test.go b/subspace_test.go new file mode 100644 index 0000000..0a483fa --- /dev/null +++ b/subspace_test.go @@ -0,0 +1,9 @@ +package main_test + +import ( + "testing" +) + +func TestFake(t *testing.T) { + // Do nothing +} \ No newline at end of file diff --git a/terminal.exe b/terminal.exe deleted file mode 100644 index 1c8ef86..0000000 Binary files a/terminal.exe and /dev/null differ diff --git a/terminal.go b/terminal.go deleted file mode 100644 index 82740ea..0000000 --- a/terminal.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "github.com/nsf/termbox-go" -) - -func runTermbox() { - err := termbox.Init() - if err != nil { - panic(err) - } - - numX := 0 - numY := 0 - loop: - for { - switch ev := termbox.PollEvent(); ev.Type { - case termbox.EventKey: - switch ev.Key { - case termbox.KeyEsc: - break loop - case termbox.KeyArrowUp: - numY -= 1 - case termbox.KeyArrowDown: - numY += 1 - case termbox.KeyArrowLeft: - numX -= 1 - case termbox.KeyArrowRight: - numX += 1 - } - } - termbox.SetCell(numX, numY, rune(48+((numX + numY)%10)), termbox.ColorGreen, termbox.ColorDefault) - termbox.Flush() - } - - defer termbox.Close() -} diff --git a/world/mainloop.go b/world/mainloop.go deleted file mode 100644 index 5d4cb21..0000000 --- a/world/mainloop.go +++ /dev/null @@ -1,62 +0,0 @@ -package world - -import "fmt" -import "time" - -type Client struct { - Queue chan string - Agent WObject -} - -func NewClient(agent WObject) Client { - return Client{make(chan string), agent} -} - - -type Sim struct { - clients []Client - worlds []WObject -} - -func NewSim() Sim { - return Sim {make([]Client, 0), make([]WObject, 0)} -} - -func (sim Sim)AddClient(client Client) { - sim.clients = append(sim.clients, client) -} - -func (sim Sim)AddWorld(world WObject) { - sim.worlds = append(sim.worlds, world) -} - -func ListCommands() string { - return "Help:\nrelations: print your relations" -} - -func PrintRelations(agent WObject) string { - ret := "" - for key, val := range agent.Relations { - ret+=fmt.Sprintf("%s %s\n", key, val.Name()) - } - return ret -} - - -func (sim Sim)Run() { - fmt.Println("Running mainloop") - for { - time.Sleep(1000*time.Millisecond) - fmt.Println(len(sim.clients)) - for _, client := range sim.clients { - fmt.Println(client) - command := <- client.Queue - switch command { - case "help": - client.Queue <- ListCommands() - case "relations": - client.Queue <- PrintRelations(client.Agent) - } - } - } -} diff --git a/world/objects.go b/world/objects.go deleted file mode 100644 index de4b843..0000000 --- a/world/objects.go +++ /dev/null @@ -1,104 +0,0 @@ -package world - -import "io" - -type Saver interface { - save(io.Writer) bool -} - -type Loader interface { - load(io.Reader) bool -} - -type Object map[string](interface{}) - -func NewObject() Object { - obj := make(map[string](interface{})) - return obj -} - -type Shape interface { - MaxX() float64 - MaxY() float64 - MaxZ() float64 - MinX() float64 - MinY() float64 - MinZ() float64 - IsTouching(othr Shape) bool - -} - -type Box struct { - x float64 - y float64 - z float64 - width float64 - height float64 - depth float64 -} - -func NewBox(x float64, y float64, z float64, width float64, height float64, depth float64) Box { - return Box{x, y, z, width, height, depth} -} - -func (b Box)MinX() float64 { - return b.x -} - -func (b Box)MinY() float64 { - return b.y -} - -func (b Box)MinZ() float64 { - return b.z -} - -func (b Box)MaxX() float64 { - return b.x+b.width -} - -func (b Box)MaxY() float64 { - return b.y+b.height -} - -func (b Box)MaxZ() float64 { - return b.z+b.depth -} - -func (b Box)IsTouching(othr Shape) bool { - return !((b.MinX()>othr.MaxX()||b.MaxX()othr.MaxY()||b.MaxY()othr.MaxZ()||b.MaxZ()