Advent-of-Code-2023/day10/dayTen.go

402 lines
9.7 KiB
Go

package day10
import (
"errors"
"fmt"
"log"
"os"
"slices"
"strings"
)
func Run() int {
fmt.Println("hello day 10")
// filename := "day10/example2noisy"
filename := "day10/example5"
fieldMap := Read(filename)
fmt.Println(fieldMap.BeastCoord)
// fmt.Println(fieldMap.String())
// fmt.Printf("%+v\n", fieldMap.Cells)
// log.Printf(">> does Equals work? {1,2} == {1,2} is %t\n", (Coord{1,2} == Coord{1,2}))
// log.Printf(">> does Index work? {1,2} in [{2,2}, {1,2}] is %d \n", slices.Index([]Coord{{2,2}, {1,2}}, Coord{1,2}))
// fieldMap.checkDirectionFromBeast(Coord{1,2})
beastNeighborCoords := fieldMap.Cells[fieldMap.BeastCoord].Neighbords
len := 0
for _, coord := range beastNeighborCoords {
log.Printf("checking neighbor %v\n", coord)
isCycle, curLen := fieldMap.checkDirectionFromBeast(coord)
if isCycle {
log.Printf("found cycle through %v\n", coord)
len = curLen
break
}
}
// fmt.Println("beore marking:")
fieldMap.markMainLoop()
// fmt.Println("after marking loop:")
// fmt.Println(fieldMap.String())
fmt.Println("beore marking closest Outer:")
// now main loop is closed with regards to 'S' neighbors
fieldMap.initialMarkOuter()
fmt.Println("after marking closest Outer:")
fmt.Println(fieldMap.String())
return (len / 2) + (len % 2)
}
// so do i work with just [][]rune ?
// func Next(from Coord, through Coord) (Coord, error) ?
// and here check that 'from' has exit into 'through'
// and check that 'through' has entrance from 'from'
// so, i guess i could do 'exit direction' and 'entrance direction'
// then compare 'exit direction' with what's available on 'from'
//
// or i can just have function 'canExit(from, to Coord)' and canEnter(from, to Coord)
// i suppose it would be nice to just create Cell(Coord, Type) and
// cell would map 'from' to 'to'
type Cell struct {
Coord Coord
Tile rune
Neighbords []Coord
IsOnMainPath bool
IsOuter bool
}
func (c *Cell) String() string {
if c.Tile == 'S' {
return "S"
}
if c.IsOuter {
return "O"
}
if !c.IsOnMainPath {
return " "
}
switch c.Tile {
case '7':
return "⌝"
case 'J':
return "⌟"
case 'F':
return "⌜"
case 'L':
return "⌞"
case '.':
return " "
default:
return string(c.Tile)
}
}
type Coord struct {
X, Y int
}
func (c Coord) Equal(other Coord) bool {
return c.X == other.X && c.Y == other.Y
}
type Direction int
const (
UP Direction = iota
DOWN
LEFT
RIGHT
)
func (d Direction)String() string {
names := []string{"UP", "DOWN", "LEFT", "RIGHT"}
return names[d]
}
func (c Coord) Shift(d Direction) Coord {
x, y := c.X, c.Y
result := Coord{}
switch d {
case UP:
result = Coord{x, y - 1}
case DOWN:
result = Coord{x, y + 1}
case LEFT:
result = Coord{x - 1, y}
case RIGHT:
result = Coord{x + 1, y}
}
return result
}
type Map struct {
Cells map[Coord]Cell
Height, Width int
BeastCoord Coord
}
func (m *Map) String() string {
result := ""
for y := 0; y < m.Height; y++ {
for x := 0; x < m.Width; x++ {
cell := m.Cells[Coord{x, y}]
result += cell.String()
}
result += "\n"
}
return result
}
func (m *Map) markMainLoop() {
start := m.Cells[m.BeastCoord]
start.IsOnMainPath = true
m.Cells[m.BeastCoord] = start
previous := start
currentCell := m.Cells[previous.Neighbords[0]]
// log.Printf("starting marking of main loop from %+v through %+v\n", start, currentCell)
for currentCell.Tile != 'S' {
currentCell.IsOnMainPath = true
m.Cells[currentCell.Coord] = currentCell
// log.Printf("marking loop on %+v (%s)\n", currentCell, currentCell.String())
nextCoord, _, err := currentCell.Next(previous.Coord)
// log.Printf("next coord will be %v %s\n", nextCoord, err)
if err != nil {
return
}
previous = currentCell
currentCell = m.Cells[nextCoord]
}
}
func (m *Map) initialMarkOuter() {
// for start point let's take my highest on main path and one above
// and will have a runner pointer to the cell on the outside
var outerRunner Cell
var pathCunner Cell
outer:
for y := 0; y < m.Height; y++ {
for x := 0; x < m.Width; x++ {
if cell := m.Cells[Coord{x, y}]; cell.IsOnMainPath {
pathCunner = cell
outerRunner = m.Cells[Coord{x, y - 1}]
break outer
}
}
}
startPoint := pathCunner
previous := startPoint
firstDirection := startPoint.OutDirections()[0]
nextCoord := previous.Coord.Shift(firstDirection)
currentCell := m.Cells[nextCoord]
var exitingPreviousBy Direction = firstDirection
stepsToDo := 1
for currentCell.Coord != startPoint.Coord {
// looping once. and need to operae on the outer runner
// and i don't have the direction? well, i guess i could use direction
outerRunner = m.markOuterAndMove(previous, outerRunner, exitingPreviousBy)
var err error
nextCoord, exitingPreviousBy, err = currentCell.Next(previous.Coord)
if err != nil {
panic("initial mark cycle can't get next")
}
previous = currentCell
currentCell = m.Cells[nextCoord]
stepsToDo -= 1
if stepsToDo == 0 {
break
}
}
}
func (m *Map) markOuter(outerPointerCoord Coord) {
if !m.isValidCoord(outerPointerCoord) {
return
}
outerPointer := m.Cells[outerPointerCoord]
if outerPointer.IsOnMainPath {
return
}
outerPointer.IsOuter = true
m.Cells[outerPointer.Coord] = outerPointer
}
// move both inner path Cell and OuterCell through direction from inner path cell
// and i need to know direction from which we came into 'pathPointer'
func (m *Map) markOuterAndMove(pathPointer Cell, outerPointer Cell, exitingCurrentBy Direction) Cell {
// mark &save outer, get moves from pathPointer & direct
// do 1 or 2 moves and on each mark & save
m.markOuter(outerPointer.Coord)
outerPointerMovements := outerPointerMovements[pathPointer.Tile][exitingCurrentBy]
log.Printf("moving outer from %s exited via %s with moves %+v\n", pathPointer.String(), exitingCurrentBy.String(), outerPointerMovements)
coord := outerPointer.Coord
for _, movement := range outerPointerMovements {
coord = coord.Shift(movement)
m.markOuter(coord)
}
newPointer := m.Cells[coord]
return newPointer
}
// yeah, this is not enough. if we move down from | we could be directly up to -
// so we need TurnLeft & TurnRight things
var outerPointerMovements map[rune]map[Direction][]Direction = map[rune]map[Direction][]Direction{
'|': {
UP: {UP},
DOWN: {DOWN},
},
'-': {
LEFT: {LEFT},
RIGHT: {RIGHT},
},
'L': {
DOWN: {DOWN, RIGHT},
LEFT: {RIGHT, UP},
},
'J': {
DOWN: {DOWN, LEFT},
RIGHT: {RIGHT, UP},
},
'F': {
DOWN: {LEFT, DOWN},
RIGHT: {UP, RIGHT},
},
'7': {
RIGHT: {RIGHT, DOWN},
UP: {UP, LEFT},
},
}
// call for each direction from beast.
// will run the path until it loops back at best, or terminates
func (m *Map) checkDirectionFromBeast(through Coord) (isCycle bool, len int) {
// defer log.Printf("about to return check from beast %v, isCycle : %t. len is %d", through, isCycle, len)
len = 1
previous := m.Cells[m.BeastCoord]
currentCell, found := m.Cells[through]
// log.Printf("check direction init for %+v\n", currentCell)
for found && currentCell.Tile != 'S' {
// log.Printf("check direction loop for %+v (%s)\n", currentCell, currentCell.String())
len += 1
nextCoord, _, err := currentCell.Next(previous.Coord)
// log.Printf("next coord will be %v %s\n", nextCoord, err)
if err != nil {
return
}
previous = currentCell
currentCell, found = m.Cells[nextCoord]
}
if currentCell.Tile == 'S' {
// log.Printf("found cycle, len is %d\n", len)
isCycle = true
// let's close the loop now.
beastCell := m.Cells[m.BeastCoord]
beastCell.Neighbords = []Coord{previous.Coord, through}
m.Cells[m.BeastCoord] = beastCell
// log.Printf("cells are not %+v", m.Cells)
}
return
}
func (m *Map) isValidCoord(c Coord) bool {
if c.X < 0 || c.Y < 0 || c.X >= m.Height || c.Y >= m.Width {
return false
}
return true
}
func Read(filename string) Map {
result := Map{}
bytes, err := os.ReadFile(filename)
if err != nil {
panic(fmt.Sprint("cannot read file ", filename))
}
lines := strings.Split(string(bytes), "\n")
result.Height = len(lines)
result.Width = len(lines[0])
result.Cells = map[Coord]Cell{}
for y, line := range lines {
for x, symb := range line {
coord := Coord{X: x, Y: y}
if symb == 'S' {
result.BeastCoord = coord
}
cell := Cell{
Coord: coord,
Tile: symb,
}
cell.Neighbords = cell.GetNeighbors()
result.Cells[coord] = cell
}
}
return result
}
func (c *Cell) GetNeighbors() []Coord {
result := make([]Coord, 0)
for _, direction := range c.OutDirections() {
result = append(result, c.Coord.Shift(direction))
}
return result
}
// doesn't check whether 'from' has exit into c
// only whether c can accept conntion from that direction
// - check if 'from' is in neighbors
// if it is - then take another neighbor
// wouldn't work for 'S' but we don't need it to
func (c *Cell) Next(from Coord) (to Coord, cameFrom Direction, err error) {
if len(c.Neighbords) != 2 {
return Coord{}, 0, errors.New(fmt.Sprintf("not 2 neighbors: cannot get next from %v through %c", from, c))
}
i := slices.Index(c.Neighbords, from)
if i == -1 {
return Coord{}, 0, errors.New(fmt.Sprintf("cannot find next from %v through %+v", from, c))
}
var nextDirection Direction
for _, direction := range c.OutDirections() {
if c.Coord.Shift(direction) != from {
nextDirection = direction
}
}
otherIndex := 1 - i
return c.Neighbords[otherIndex], nextDirection, nil
}
// x from left to right; y from top to bottom
func (c *Cell) OutDirections() []Direction {
switch c.Tile {
case '|':
return []Direction{UP, DOWN}
case '-':
return []Direction{LEFT, RIGHT}
case 'L':
return []Direction{UP, RIGHT}
case 'J':
return []Direction{UP, LEFT}
case 'F':
return []Direction{RIGHT, DOWN}
case '7':
return []Direction{LEFT, DOWN}
case 'S': // all
return []Direction{UP, DOWN, LEFT, RIGHT}
default:
return []Direction{}
}
}