1 package world
2 3 import (
4 "bufio"
5 "errors"
6 "fmt"
7 "log"
8 "os"
9 "strings"
10 )
11 12 //AddFromFile reads in a file line by line to input city description lines and generate a world
13 func (w *World) AddFromFile(filename string) {
14 15 fd, err := os.Open(filename)
16 if err != nil {
17 log.Fatal(err)
18 }
19 defer fd.Close()
20 21 scanner := bufio.NewScanner(fd)
22 var counter int
23 for scanner.Scan() {
24 err := w.AddFromString(scanner.Text())
25 if err != nil {
26 fmt.Println("error reading file on line", counter)
27 fmt.Println(err)
28 os.Exit(1)
29 }
30 counter++
31 }
32 }
33 34 //AddFromString inputs a new city given a string with name, and up to 4 key=value with keys for cardinal directions
35 func (w *World) AddFromString(input string) (err error) {
36 37 // City name is first and then space separated up to 4 neighbours one for
38 // each direction
39 split := strings.Split(input, " ")
40 41 newName := split[0]
42 43 // The first string should be a city name and not a key value pair
44 if strings.Contains(newName, kvSep) {
45 46 return errors.New("malformed city name: may not contain '='")
47 }
48 49 // There cannot be more than 4 directions and a city so the line is
50 // malformed if it is more than 5 items (with 4 spaces)
51 if len(split) > 5 {
52 53 return fmt.Errorf("input string \"%s\" contains too many fields", input)
54 }
55 56 neighbours := split[1:]
57 58 if len(neighbours) > 0 {
59 60 // Validate that all specified neighbours are `dir=Name`
61 for i, v := range neighbours {
62 63 if !strings.Contains(v, kvSep) {
64 65 return fmt.Errorf(
66 "input string \"%s\" field %d contains malformed key/value pair \"%s\"",
67 input, i, v,
68 )
69 } else {
70 71 if strings.Count(v, kvSep) > 1 {
72 73 return fmt.Errorf(
74 "input string \"%s\" contains multiple key"+
75 "/value separators on element %d \"%s\"",
76 input, i, v,
77 )
78 }
79 }
80 81 }
82 }
83 84 // if city doesn't exist, add it to the lookup
85 var city uint32
86 var ok bool
87 if city, ok = w.Lookup.Name[newName]; !ok {
88 // Add new index to lookup table
89 city = uint32(w.Length)
90 err = w.Lookup.Add(newName, city)
91 if err != nil {
92 93 // This means we are trying to add the same city on a new index
94 return err
95 }
96 // Append new empty city entry with matching index
97 w.Cities = append(w.Cities, City{})
98 99 // The Cities slice is now one element longer,
100 // next addition must be on the next index
101 w.Length++
102 }
103 104 // If the city does exist it was made by a neighbour and has no
105 // neighbours yet
106 107 if len(neighbours) > 0 {
108 109 for i, v := range neighbours {
110 111 // Split the key and value
112 kvs := strings.Split(v, kvSep)
113 114 // We want to allow any variant of case and only count the first
115 // character, as it is distinctive
116 key := strings.ToLower(kvs[0][:1])
117 118 newNeighbour := kvs[1]
119 120 var validDir bool
121 var dir int
122 123 // Check that the key is a valid direction string.
124 // Note we only compare the first character.
125 for d := range Dirs {
126 if key == Dirs[d][:1] {
127 128 validDir = true
129 dir = d
130 break
131 }
132 }
133 if !validDir {
134 135 return fmt.Errorf(
136 "specified neighbour of %s %d \"%s\" using invalid key \"%s\"",
137 newName, i, v, key,
138 )
139 }
140 141 // if the value is empty the neighbour was destroyed,
142 // and addition will also add nothing, just continue
143 if newNeighbour == "" {
144 145 continue
146 }
147 148 var n uint32
149 // if the key doesn't exist, create it
150 if n, ok = w.Lookup.Name[newNeighbour]; !ok {
151 152 // automatically set current to be opposite neighbour
153 newCity :=
154 fmt.Sprintf("%s %s=%s", newNeighbour, Dirs[^dir&3], newName)
155 156 err := w.AddFromString(newCity)
157 if err != nil {
158 159 return err
160 }
161 162 // If the neighbour in the specified direction already exists
163 // and doesn't already point back to the current city there
164 // is an error in the specification
165 } else if w.Cities[n].Neighbour[^dir&3] != city &&
166 n != 0 &&
167 w.Cities[n].Neighbour[^dir&3] != 0 {
168 169 return fmt.Errorf(
170 "error adding city %s as preexisting neighbour %s is"+
171 " pointing to different city %s",
172 newName, newNeighbour,
173 w.Lookup.Index[w.Cities[n].Neighbour[^dir&3]],
174 )
175 }
176 177 // Store the index of the neighbour city in the respective direction slot
178 w.Cities[city].Neighbour[dir] = w.Lookup.Name[newNeighbour]
179 w.Cities[n].Neighbour[^dir&3] = city
180 }
181 }
182 183 return nil
184 }
185