package common import ( "bufio" "fmt" "math" ) type Tile byte const ( Empty Tile = iota Elf ) type Direction byte const ( East Direction = iota West North South ) func (direction Direction) GetNext() Direction { switch direction { case North: return South case South: return West case West: return East default: return North } } type Coordinates struct { x int y int } type Map struct { tiles map[Coordinates]Tile currentRound Direction boundedCoordinates []Coordinates } func (tileMap *Map) Round() bool { nextCoordinates := map[Coordinates][]Coordinates{} isFinalRound := true for coordinates, tile := range tileMap.tiles { if tile == Elf { neighbor := false outer: for i := -1; i <= 1; i++ { for j := -1; j <= 1; j++ { if i != 0 || j != 0 { if tile, ok := tileMap.tiles[Coordinates{coordinates.x+i, coordinates.y+j}]; ok && tile == Elf { neighbor = true break outer } } } } if !neighbor { continue } var nextC Coordinates round := tileMap.currentRound move := false loop: for i := 0; i < 4; i++ { switch round { case North: tile1, ok1 := tileMap.tiles[Coordinates{coordinates.x-1, coordinates.y-1}] tile2, ok2 := tileMap.tiles[Coordinates{coordinates.x, coordinates.y-1}] tile3, ok3 := tileMap.tiles[Coordinates{coordinates.x+1, coordinates.y-1}] nextC = Coordinates{coordinates.x, coordinates.y-1} if (!ok1 || tile1 == Empty) && (!ok2 || tile2 == Empty) && (!ok3 || tile3 == Empty) { move = true break loop } case South: tile1, ok1 := tileMap.tiles[Coordinates{coordinates.x-1, coordinates.y+1}] tile2, ok2 := tileMap.tiles[Coordinates{coordinates.x, coordinates.y+1}] tile3, ok3 := tileMap.tiles[Coordinates{coordinates.x+1, coordinates.y+1}] nextC = Coordinates{coordinates.x, coordinates.y+1} if (!ok1 || tile1 == Empty) && (!ok2 || tile2 == Empty) && (!ok3 || tile3 == Empty) { move = true break loop } case West: tile1, ok1 := tileMap.tiles[Coordinates{coordinates.x-1, coordinates.y-1}] tile2, ok2 := tileMap.tiles[Coordinates{coordinates.x-1, coordinates.y}] tile3, ok3 := tileMap.tiles[Coordinates{coordinates.x-1, coordinates.y+1}] nextC = Coordinates{coordinates.x-1, coordinates.y} if (!ok1 || tile1 == Empty) && (!ok2 || tile2 == Empty) && (!ok3 || tile3 == Empty) { move = true break loop } case East: tile1, ok1 := tileMap.tiles[Coordinates{coordinates.x+1, coordinates.y-1}] tile2, ok2 := tileMap.tiles[Coordinates{coordinates.x+1, coordinates.y}] tile3, ok3 := tileMap.tiles[Coordinates{coordinates.x+1, coordinates.y+1}] nextC = Coordinates{coordinates.x+1, coordinates.y} if (!ok1 || tile1 == Empty) && (!ok2 || tile2 == Empty) && (!ok3 || tile3 == Empty) { move = true break loop } } round = round.GetNext() } if move { if _, okc := nextCoordinates[nextC]; !okc { nextCoordinates[nextC] = []Coordinates{} } nextCoordinates[nextC] = append(nextCoordinates[nextC], coordinates) } } } for nextC := range nextCoordinates { if len(nextCoordinates[nextC]) == 1 { tileMap.tiles[nextCoordinates[nextC][0]] = Empty tileMap.tiles[nextC] = Elf isFinalRound = false } } tileMap.currentRound = tileMap.currentRound.GetNext() return isFinalRound } func (tileMap *Map) GetEmpty() int { nEmpty := 0 for x := tileMap.boundedCoordinates[0].x; x <= tileMap.boundedCoordinates[1].x; x++ { for y := tileMap.boundedCoordinates[0].y; y <= tileMap.boundedCoordinates[1].y; y++ { if tileMap.tiles[Coordinates{x, y}] == Empty { nEmpty++ } } } return nEmpty } func (tileMap *Map) Bound() { minX := math.MaxInt32 minY := math.MaxInt32 maxX := math.MinInt32 maxY := math.MinInt32 for coordinates, tile := range tileMap.tiles { if tile == Elf { if coordinates.x < minX { minX = coordinates.x } if coordinates.x > maxX { maxX = coordinates.x } if coordinates.y < minY { minY = coordinates.y } if coordinates.y > maxY { maxY = coordinates.y } } } tileMap.boundedCoordinates = []Coordinates{{minX, minY}, {maxX, maxY}} for x := minX; x <= maxX; x++ { for y := minY; y <= maxY; y++ { c := Coordinates{x, y} if _, ok := tileMap.tiles[c]; !ok { tileMap.tiles[c] = Empty } } } } func (tileMap *Map) Print() { for y := tileMap.boundedCoordinates[0].y; y <= tileMap.boundedCoordinates[1].y; y++ { for x := tileMap.boundedCoordinates[0].x; x <= tileMap.boundedCoordinates[1].x; x++ { switch tileMap.tiles[Coordinates{x, y}] { case Empty: fmt.Print(".") case Elf: fmt.Print("#") } } fmt.Println() } } func Parse(scanner bufio.Scanner) Map { tileMap := Map{} tileMap.tiles = make(map[Coordinates]Tile) i := 0 for scanner.Scan() { line := scanner.Text() for j := range line { switch line[j] { case '.': tileMap.tiles[Coordinates{j, i}] = Empty case '#': tileMap.tiles[Coordinates{j, i}] = Elf } } i++ } tileMap.currentRound = North return tileMap }