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  }