1 package war
2 3 import (
4 "fmt"
5 "github.com/l0k18/alieninvasion/pkg/world"
6 "math/rand"
7 "os"
8 )
9 10 type Aliens map[uint32]uint32
11 12 func War(w *world.World, aliens, seed int64) {
13 14 aliens++
15 16 // Create a slice matching the cities, and then shuffle them
17 cityCount := len(w.Cities)
18 cities := make([]int32, cityCount)
19 20 for i := range cities {
21 22 if int64(i) < aliens {
23 24 // add ascending numbers to represent the aliens
25 cities[i] = int32(i)
26 27 } else {
28 29 // once aliens numbers are added, we are finished
30 break
31 }
32 }
33 34 // to initially place aliens we create a slice of the cities and shuffle it
35 rand.Seed(seed)
36 37 // to leave the first element alone we shorten the length by 1 and add 1 to
38 // the fields to be shuffled
39 rand.Shuffle(
40 cityCount-1,
41 func(i int, j int) {
42 cities[i+1], cities[j+1] = cities[j+1], cities[i+1]
43 },
44 )
45 46 alienMap := make(Aliens, aliens)
47 48 for i := range cities {
49 50 if cities[i] != 0 {
51 52 alienMap[uint32(cities[i])] = uint32(i)
53 }
54 }
55 56 // run for a maximum of 10000 turns or until there is one or no aliens left
57 for turn := 0; turn < 10000 && len(alienMap) > 1; turn++ {
58 59 // iterate the list of aliens and move them to a new location
60 //
61 // we move first then check collisions, so it is possible for up to 4
62 // aliens to collide and annihilate one city
63 for i := range alienMap {
64 65 // randomly select a direction to move each alien
66 moveDir := rand.Intn(5)
67 68 // If the random number is 4 the alien decides not to move
69 if moveDir > 3 {
70 continue
71 }
72 73 // get the index of the alien's current location
74 city := alienMap[i]
75 76 // change the alien's location to the new location
77 newLoc := w.Cities[city].Neighbour[moveDir]
78 79 // if the new location is index 0 the alien cannot move this
80 // direction and forfeits this turn
81 if newLoc != 0 {
82 83 alienMap[i] = newLoc
84 }
85 }
86 87 // we will use a map to detect location collision, when more than one
88 // alien is in a city each alien's identity is appended to a list
89 detector := make(map[uint32][]uint32)
90 91 // iterate the aliens and append them to the map entries
92 for i := range alienMap {
93 94 if _, ok := detector[alienMap[i]]; !ok {
95 96 // if the map entry is empty, add the initial alien
97 detector[alienMap[i]] = []uint32{i}
98 99 } else {
100 101 // if there is already an entry, append the new alien
102 detector[alienMap[i]] = append(detector[alienMap[i]], i)
103 }
104 }
105 106 // iterate the detector and delete cities
107 for city := range detector {
108 109 // if the map entry for a city has more than one alien it is
110 // destroyed
111 if len(detector[city]) > 1 {
112 113 fmt.Print("Aliens ")
114 115 // first we will print that the aliens have collided and
116 // destroyed the city
117 for alien := range detector[city] {
118 119 // if this is the last in the list, separate with and
120 if alien == len(detector[city])-1 {
121 122 fmt.Print(" and ")
123 124 // if this is not the first, separate with a comma
125 } else if alien != 0 {
126 127 fmt.Print(", ")
128 }
129 130 fmt.Print(detector[city][alien])
131 // lastly, delete the alien
132 delete(alienMap, detector[city][alien])
133 134 }
135 fmt.Printf(
136 " converged on %s and destroyed it!\n",
137 w.Lookup.Index[city],
138 )
139 140 // remove neighbours references to the destroyed city
141 for dir, neighbour := range w.Cities[city].Neighbour {
142 143 // the neighbour in a given direction the opposite numbered
144 // neighbour must be zeroed to the empty city
145 w.Cities[neighbour].Neighbour[^dir&3] = 0
146 147 // then we delete the outbound neighbour entry
148 w.Cities[city].Neighbour[dir] = 0
149 }
150 idx := w.Lookup.Index[city]
151 delete(w.Lookup.Index, city)
152 delete(w.Lookup.Name, idx)
153 154 }
155 }
156 }
157 158 w.Print(os.Stdout)
159 }
160