package generator import ( "encoding/json" "errors" "fmt" "math" "math/rand" "time" ) type TerritoryType int const ( EmptyTerritory TerritoryType = iota WastelandTerritory MountainTerritory RuinsTerritory ) const ( UpDirection int = iota RightDirection DownDirection LeftDirection ) type Tile struct { Territory TerritoryType `json:"territory"` } func (t Tile) MarshalJSON() ([]byte, error) { var s string switch t.Territory { case EmptyTerritory: s = "empty" case WastelandTerritory: s = "wasteland" case MountainTerritory: s = "mountain" case RuinsTerritory: s = "ruin" } return []byte("\"" + s + "\""), nil } func (t Tile) Plot() string { switch t.Territory { case EmptyTerritory: return " " case WastelandTerritory: return "#" case MountainTerritory: return "M" case RuinsTerritory: return "I" } return "?" } type World struct { Size int `json:"size"` Wastelands int `json:"wastelands"` Ruins int `json:"ruins"` Mountains int `json:"mountains"` World []Tile `json:"tiles"` } func (w World) Plot() string { var board string for i := 0; i < w.Size*w.Size; i++ { board += fmt.Sprintf("[%v]", w.World[i].Plot()) if (i+1)%w.Size == 0 { board += fmt.Sprintf("\n") } } return board } func (w World) JSON() string { output, _ := json.MarshalIndent(w, "", " ") return string(output) } func (w World) ExportToPDF(filename string) error { // TODO: Draw the PDF return nil } func New(size int, numWastelands int, numMountains int, numRuins int) World { rand.Seed(time.Now().UnixNano()) w := World{ Size: size, Wastelands: numWastelands, Mountains: numMountains, Ruins: numRuins, } // All empty for start for i := 0; i < w.Size*w.Size; i++ { w.World = append(w.World, Tile{Territory: EmptyTerritory}) } // Place wasteland area by finding a suitable place to start // and surround it with 6 more wastelands var wastelands []int startPos := roll(w.Size-1) + roll(w.Size-1)*w.Size w.place(WastelandTerritory, startPos) wastelands = append(wastelands, startPos) for i := 0; i < numWastelands-1; i++ { var candidates []int // Find all possible candidates (top, left, bottom, right), // from all already places wastelands, then choose one at // random and place it. for _, wl := range wastelands { for _, free := range w.neighboursOfType(wl, EmptyTerritory) { if !contains(candidates, free) { candidates = append(candidates, free) } } } candidate := randomItem(candidates) w.place(WastelandTerritory, candidate) wastelands = append(wastelands, candidate) } // Place 5 mountains on free tiles. We need to make sure that // a mountain do not touch another mountain. for i := 0; i < numMountains; i++ { var candidates []int // Start with all free fields for pos, tile := range w.World { if tile.Territory == EmptyTerritory && len(w.neighboursOfType(pos, MountainTerritory)) == 0 { candidates = append(candidates, pos) } } w.place(MountainTerritory, randomItem(candidates)) } // Place 6 ruins. Same constraint as mountains apply here (no // two ruins should not touch each other) for i := 0; i < numRuins; i++ { var candidates []int // Start with all free fields for pos, tile := range w.World { if tile.Territory == EmptyTerritory && len(w.neighboursOfType(pos, RuinsTerritory)) == 0 { candidates = append(candidates, pos) } } w.place(RuinsTerritory, randomItem(candidates)) } return w } func (w World) neighbour(direction int, pos int) (int, error) { switch direction { case UpDirection: if pos < w.Size { return 0, errors.New("Out of bounds") } return pos - w.Size, nil case RightDirection: if pos%w.Size == w.Size-1 || pos >= w.Size*w.Size-1 { return 0, errors.New("Out of bounds") } return pos + 1, nil case DownDirection: if pos >= ((w.Size-1)*w.Size)-1 { return 0, errors.New("Out of bounds") } return pos + w.Size, nil case LeftDirection: if pos%w.Size == 0 || pos <= 0 { return 0, errors.New("Out of bounds") } return pos - 1, nil } return 0, errors.New("Wrong direction") } func (w World) neighbours(pos int) []int { var neighbours []int for i := 0; i < 4; i++ { n, err := w.neighbour(i, pos) if err == nil { neighbours = append(neighbours, n) } } return neighbours } func (w World) neighboursOfType(pos int, territory TerritoryType) []int { var neighbours []int for _, n := range w.neighbours(pos) { if w.World[n].Territory == territory { neighbours = append(neighbours, n) } } return neighbours } func (w World) ToXY(pos int) (int, int) { return pos % w.Size, int(math.Floor((float64(pos) / float64(w.Size)))) } func roll(w int) int { return rand.Intn(w) } func contains(s []int, e int) bool { for _, a := range s { if a == e { return true } } return false } func randomItem(c []int) int { rand.Shuffle(len(c), func(i, j int) { c[i], c[j] = c[j], c[i] }) return c[0] } func (w World) place(territory TerritoryType, pos int) { w.World[pos] = Tile{Territory: territory} }