439 lines
10 KiB
Go
439 lines
10 KiB
Go
package day10
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
func Run() int {
|
|
fmt.Println("hello day 10")
|
|
// filename := "day10/example2noisy"
|
|
filename := "day10/example6"
|
|
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
|
|
|
|
|
|
// TODO Hardcode change S to correct Title
|
|
fixedBeast := fieldMap.Cells[fieldMap.BeastCoord]
|
|
fixedBeast.Tile = '7'
|
|
fieldMap.Cells[fieldMap.BeastCoord] = fixedBeast
|
|
|
|
fieldMap.countIntersectionsTopDown()
|
|
// 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 && !c.IsOuter {
|
|
return "I"
|
|
}
|
|
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 := ""
|
|
result += fmt.Sprintf("map of height %d and with %d\n", m.Height, m.Width)
|
|
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 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
|
|
m.markOuter(Coord{x, y - 1})
|
|
break outer
|
|
}
|
|
}
|
|
}
|
|
startPoint := pathCunner
|
|
previous := startPoint
|
|
firstDirection := startPoint.OutDirections()[0]
|
|
nextCoord := previous.Coord.Shift(firstDirection)
|
|
currentCell := m.Cells[nextCoord]
|
|
|
|
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)
|
|
|
|
// m.markOuterAroundPathElem(currentCell)
|
|
|
|
var err error
|
|
nextCoord, _, err = currentCell.Next(previous.Coord)
|
|
if err != nil {
|
|
panic("initial mark cycle can't get next")
|
|
}
|
|
previous = currentCell
|
|
currentCell = m.Cells[nextCoord]
|
|
|
|
}
|
|
}
|
|
|
|
func (m *Map) markOuter(outerPointerCoord Coord) {
|
|
if !m.isValidCoord(outerPointerCoord) {
|
|
log.Printf("non valid %+v to mark as Outer", outerPointerCoord)
|
|
return
|
|
}
|
|
outerPointer := m.Cells[outerPointerCoord]
|
|
if outerPointer.IsOnMainPath {
|
|
return
|
|
}
|
|
outerPointer.IsOuter = true
|
|
m.Cells[outerPointer.Coord] = outerPointer
|
|
}
|
|
|
|
// 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.Width || c.Y >= m.Height {
|
|
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))
|
|
}
|
|
text := string(bytes)
|
|
text = strings.TrimSpace(text)
|
|
lines := strings.Split(text, "\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{}
|
|
}
|
|
}
|
|
|
|
func (m *Map)countIntersectionsTopDown() {
|
|
stacks := make([][]rune, m.Width) // stack for each X
|
|
for y := 0; y < m.Height; y++ {
|
|
for x := 0; x < m.Width; x++ {
|
|
stack := stacks[x]
|
|
len := len(stack)
|
|
cell := m.Cells[Coord{x, y}]
|
|
if cell.IsOnMainPath {
|
|
if len == 0 {
|
|
stack = append(stack, cell.Tile)
|
|
} else {
|
|
top := stack[len-1]
|
|
if isOpposite(top, cell.Tile) {
|
|
stack = stack[:len-1]
|
|
} else if isScrunching(top, cell.Tile) {
|
|
stack = stack[:len-1]
|
|
stack = append(stack, '-')
|
|
stack, _ = popTwoHorizontals(stack)
|
|
} else if cell.Tile != '|' {
|
|
stack = append(stack, cell.Tile)
|
|
}
|
|
}
|
|
stacks[x] = stack
|
|
} else {
|
|
if len == 0 {
|
|
m.markOuter(cell.Coord)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for x := 0; x < m.Width; x++ {
|
|
stack := stacks[x]
|
|
fmt.Println(string(stack))
|
|
}
|
|
fmt.Print(stacks)
|
|
}
|
|
|
|
func popTwoHorizontals(stack []rune) ([]rune, bool) {
|
|
len := len(stack)
|
|
if len >= 2 {
|
|
top := stack[len-1]
|
|
prev := stack[len-2]
|
|
if top == '-' && prev == '-' {
|
|
return stack[:len-2], true
|
|
}
|
|
}
|
|
|
|
return stack, false
|
|
}
|
|
|
|
func isScrunching(pipe, other rune) bool {
|
|
switch pipe {
|
|
case 'F':
|
|
return other == 'J'
|
|
case '7':
|
|
return other == 'L'
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isOpposite(pipe, other rune) bool {
|
|
switch pipe {
|
|
case '-':
|
|
return other == '-'
|
|
case 'L':
|
|
return other == 'F'
|
|
case 'J':
|
|
return other == '7'
|
|
case 'F':
|
|
return other == 'L'
|
|
case '7':
|
|
return other == 'J'
|
|
}
|
|
return false
|
|
}
|