github.com/mnlphlp/aoc22@v0.0.0-20230330151331-c1dc4bff1b9b/day23/grid/grid.go (about) 1 package grid 2 3 import ( 4 "strings" 5 "time" 6 7 "github.com/mnlphlp/aoc22/util" 8 ) 9 10 const TIMING_ACTIVE = false 11 12 type Stat struct { 13 Time time.Duration 14 Calls int 15 } 16 17 func (s *Stat) Add(t time.Duration) { 18 s.Calls++ 19 s.Time += t 20 } 21 22 var Timing struct { 23 Insert, Contains, CanMove, NextMove, Hash, Remove Stat 24 } 25 26 type Grid struct { 27 field [][]bool 28 offsetX int 29 offsetY int 30 } 31 32 func (g Grid) Contains(p util.Pos2) bool { 33 if TIMING_ACTIVE { 34 start := time.Now() 35 defer func() { Timing.Contains.Add(time.Since(start)) }() 36 } 37 p.X += g.offsetX 38 p.Y += g.offsetY 39 return p.X >= 0 && p.Y >= 0 && p.X < len(g.field) && p.Y < len(g.field[p.X]) && g.field[p.X][p.Y] 40 } 41 42 func (g Grid) Insert(p util.Pos2) Grid { 43 if TIMING_ACTIVE { 44 start := time.Now() 45 defer func() { Timing.Insert.Add(time.Since(start)) }() 46 } 47 if diff := -(p.X + g.offsetX); diff > 0 { 48 g.offsetX += diff 49 for i := 0; i < diff; i++ { 50 g.field = append(g.field, []bool{}) 51 } 52 // move values back 53 for i := len(g.field) - diff - 1; i >= 0; i-- { 54 g.field[i+diff] = g.field[i] 55 } 56 for i := 0; i < diff; i++ { 57 g.field[i] = []bool{} 58 } 59 } 60 p.X += g.offsetX 61 for len(g.field) <= p.X { 62 g.field = append(g.field, []bool{}) 63 } 64 if diff := -(p.Y + g.offsetY); diff > 0 { 65 g.offsetY += diff 66 for x := 0; x < len(g.field); x++ { 67 // extend grid 68 for i := 0; i < diff; i++ { 69 g.field[x] = append(g.field[x], false) 70 } 71 // move values back 72 for i := len(g.field[x]) - diff - 1; i >= 0; i-- { 73 g.field[x][i+diff] = g.field[x][i] 74 } 75 for i := 0; i < diff; i++ { 76 g.field[x][i] = false 77 } 78 } 79 } 80 p.Y += g.offsetY 81 for len(g.field[p.X]) <= p.Y { 82 g.field[p.X] = append(g.field[p.X], false) 83 } 84 g.field[p.X][p.Y] = true 85 return g 86 } 87 func (g Grid) Remove(p util.Pos2) Grid { 88 if TIMING_ACTIVE { 89 start := time.Now() 90 defer func() { Timing.Remove.Add(time.Since(start)) }() 91 } 92 g.field[p.X+g.offsetX][p.Y+g.offsetY] = false 93 return g 94 } 95 96 func (g Grid) String() string { 97 maxX := len(g.field) 98 maxY := 0 99 for _, row := range g.field { 100 if len(row) > maxY { 101 maxY = len(row) 102 } 103 } 104 s := "" 105 for y := 0; y < maxY; y++ { 106 for x := 0; x < maxX; x++ { 107 if len(g.field[x]) <= y || !g.field[x][y] { 108 s += "." 109 } else { 110 s += "#" 111 } 112 } 113 s += "\n" 114 } 115 return s 116 } 117 118 func (g Grid) ForEach(f func(util.Pos2)) { 119 x := 0 120 for x = 0; x < len(g.field); x++ { 121 for y := 0; y < len(g.field[x]); y++ { 122 if g.field[x][y] { 123 f(util.Pos2{x - g.offsetX, y - g.offsetY}) 124 } 125 } 126 } 127 } 128 129 type GridHash []int 130 131 func (h1 GridHash) Equals(h2 GridHash) bool { 132 if len(h1) != len(h2) { 133 return false 134 } 135 for i := range h1 { 136 if h1[i] != h2[i] { 137 return false 138 } 139 } 140 return true 141 } 142 143 func (g Grid) Hash() GridHash { 144 if TIMING_ACTIVE { 145 start := time.Now() 146 defer func() { Timing.Hash.Add(time.Since(start)) }() 147 } 148 positions := make([]int, 0) 149 x := 0 150 for x = 0; x < len(g.field); x++ { 151 for y := 0; y < len(g.field[x]); y++ { 152 if g.field[x][y] { 153 positions = append(positions, x<<32+y) 154 } 155 } 156 } 157 return positions 158 } 159 160 func (g Grid) HasNeighbor(p util.Pos2) bool { 161 if TIMING_ACTIVE { 162 start := time.Now() 163 defer func() { Timing.CanMove.Add(time.Since(start)) }() 164 } 165 p.X += g.offsetX 166 p.Y += g.offsetY 167 for x := -1; x <= 1; x++ { 168 for y := -1; y <= 1; y++ { 169 // skip own element and invalid cases 170 if x == 0 && y == 0 || p.X+x < 0 || p.Y+y < 0 || p.X+x >= len(g.field) || p.Y+y >= len(g.field[p.X+x]) { 171 continue 172 } 173 if g.field[p.X+x][p.Y+y] { 174 return true 175 } 176 } 177 } 178 return false 179 } 180 181 var Directions = []util.Pos2{ 182 {0, -1}, // North 183 {0, 1}, // South 184 {-1, 0}, // West 185 {1, 0}, // East 186 } 187 188 var Directions_N = []util.Pos2{{X: -1, Y: -1}, {X: 0, Y: -1}, {X: 1, Y: -1}} 189 var Directions_E = []util.Pos2{{X: 1, Y: -1}, {X: 1, Y: 0}, {X: 1, Y: 1}} 190 var Directions_S = []util.Pos2{{X: -1, Y: 1}, {X: 0, Y: 1}, {X: 1, Y: 1}} 191 var Directions_W = []util.Pos2{{X: -1, Y: -1}, {X: -1, Y: 0}, {X: -1, Y: 1}} 192 193 var DirectionGroups = [][]util.Pos2{ 194 Directions_N, 195 Directions_S, 196 Directions_W, 197 Directions_E, 198 } 199 200 func (g Grid) NextPos(p util.Pos2, startDir int) (util.Pos2, bool) { 201 if TIMING_ACTIVE { 202 start := time.Now() 203 defer func() { Timing.NextMove.Add(time.Since(start)) }() 204 } 205 p.X += g.offsetX 206 p.Y += g.offsetY 207 // check all 4 directions 208 for i := 0; i < 4; i++ { 209 dir := (startDir + i) % 4 210 ok := true 211 for _, d := range DirectionGroups[dir] { 212 // skip invalid cases 213 if p.X+d.X < 0 || p.Y+d.Y < 0 || p.X+d.X >= len(g.field) || p.Y+d.Y >= len(g.field[p.X+d.X]) { 214 continue 215 } 216 if g.field[p.X+d.X][p.Y+d.Y] { 217 ok = false 218 } 219 } 220 if ok { 221 p.X = p.X + Directions[dir].X - g.offsetX 222 p.Y = p.Y + Directions[dir].Y - g.offsetY 223 return p, true 224 } 225 } 226 return util.Pos2{}, false 227 } 228 229 func ParseInput(input string) Grid { 230 g := Grid{} 231 for y, l := range strings.Split(input, "\n") { 232 for x, c := range l { 233 if c == '#' { 234 g = g.Insert(util.Pos2{X: x, Y: y}) 235 } 236 } 237 } 238 return g 239 }