war.go raw

   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