add.go raw

   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