Advent of Code 2016 Complete
This commit is contained in:
@@ -1,412 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
termbox "github.com/nsf/termbox-go"
|
||||
|
||||
"../../"
|
||||
)
|
||||
|
||||
var shortestSolutionDist int
|
||||
var pois []string
|
||||
var tWidth, tHeight int
|
||||
|
||||
func main() {
|
||||
playMode := aoc.ArgIsSet("-play")
|
||||
fileNm := aoc.GetArgNumber(1)
|
||||
txtMaze := aoc.FileToStringSlice(fileNm)
|
||||
m := CreateMaze(txtMaze)
|
||||
if playMode {
|
||||
player := CreatePlayer(m)
|
||||
err := termbox.Init()
|
||||
//tWidth, tHeight := termbox.Size()
|
||||
if err != nil {
|
||||
fmt.Println("Error initializing termbox")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer termbox.Close()
|
||||
var done bool
|
||||
for !done {
|
||||
fmt.Println(aoc.ClearScreen)
|
||||
player.PrintMaze()
|
||||
if player.CheckIsDone() {
|
||||
fmt.Println("Maze Completed in", player.dist, "steps!")
|
||||
fmt.Println("Press any key to quit.")
|
||||
done = true
|
||||
termbox.PollEvent()
|
||||
} else {
|
||||
ev := termbox.PollEvent()
|
||||
if ev.Type == termbox.EventKey {
|
||||
switch {
|
||||
case ev.Ch == 'q':
|
||||
done = true
|
||||
case ev.Key == termbox.KeyArrowUp:
|
||||
player.MoveUp()
|
||||
case ev.Key == termbox.KeyArrowDown:
|
||||
player.MoveDown()
|
||||
case ev.Key == termbox.KeyArrowLeft:
|
||||
player.MoveLeft()
|
||||
case ev.Key == termbox.KeyArrowRight:
|
||||
player.MoveRight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shortestSolutionDist = -1
|
||||
m.PrintMaze()
|
||||
m.StartSolve()
|
||||
//fmt.Println("Shortest Solution: ", shortestSolutionDist)
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("Usage: ./day24 <mazefile>")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var numWorkers int
|
||||
|
||||
func PrintStatus() {
|
||||
fmt.Println(aoc.ClearScreen)
|
||||
fmt.Println("Workers: ", numWorkers)
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
pos *Coord
|
||||
m *Maze
|
||||
hitPois *CoordCollection
|
||||
dist int
|
||||
}
|
||||
|
||||
func CreatePlayer(m *Maze) *Player {
|
||||
p := new(Player)
|
||||
p.pos = CreateCoord(m.start.x, m.start.y)
|
||||
p.m = m
|
||||
p.hitPois = CreateCoordCollection()
|
||||
p.hitPois.Add(p.pos.x, p.pos.y, "0")
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Player) CheckIsDone() bool {
|
||||
for _, v := range p.m.pois.coords {
|
||||
if !p.hitPois.Contains(v.x, v.y) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Player) MoveUp() bool {
|
||||
if !p.CanMoveTo(p.pos.x, p.pos.y-1) {
|
||||
return false
|
||||
}
|
||||
p.pos.y--
|
||||
p.dist++
|
||||
p.CheckMovedPos()
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Player) MoveDown() bool {
|
||||
if !p.CanMoveTo(p.pos.x, p.pos.y+1) {
|
||||
return false
|
||||
}
|
||||
p.pos.y++
|
||||
p.dist++
|
||||
p.CheckMovedPos()
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Player) MoveRight() bool {
|
||||
if !p.CanMoveTo(p.pos.x+1, p.pos.y) {
|
||||
return false
|
||||
}
|
||||
p.pos.x++
|
||||
p.dist++
|
||||
p.CheckMovedPos()
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Player) MoveLeft() bool {
|
||||
if !p.CanMoveTo(p.pos.x-1, p.pos.y) {
|
||||
return false
|
||||
}
|
||||
p.pos.x--
|
||||
p.dist++
|
||||
p.CheckMovedPos()
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Player) CanMoveTo(x, y int) bool {
|
||||
return !p.m.walls.Contains(x, y)
|
||||
}
|
||||
|
||||
func (p *Player) CheckMovedPos() {
|
||||
if c, err := p.m.pois.GetXY(p.pos.x, p.pos.y); err == nil {
|
||||
p.hitPois.Add(c.x, c.y, c.label)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Player) PrintMaze() {
|
||||
var err error
|
||||
poiC := color.New(color.BgGreen)
|
||||
hitPoiC := color.New(color.BgBlue)
|
||||
playerC := color.New(color.BgYellow)
|
||||
target := color.New(color.BgRed)
|
||||
next := p.FindClosestNewPoi()
|
||||
for y := 0; y < p.m.h; y++ {
|
||||
for x := 0; x < p.m.w; x++ {
|
||||
var c *Coord
|
||||
if c, err = p.m.walls.GetXY(x, y); err == nil {
|
||||
fmt.Print(c.label)
|
||||
} else if p.pos.is(x, y) {
|
||||
playerC.Print("@")
|
||||
} else if p.m.start.is(x, y) {
|
||||
hitPoiC.Print("0")
|
||||
} else if c, err = p.m.pois.GetXY(x, y); err == nil {
|
||||
if p.hitPois.Contains(x, y) {
|
||||
hitPoiC.Print(c.label)
|
||||
} else if next != nil && next.is(x, y) {
|
||||
target.Print(c.label)
|
||||
} else {
|
||||
poiC.Print(c.label)
|
||||
}
|
||||
} else {
|
||||
fmt.Print(".")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Printf("Next Closest POI (%s: %d,%d)\n", next.label, next.x, next.y)
|
||||
fmt.Printf("Steps Taken: %d\n", p.dist)
|
||||
}
|
||||
|
||||
func (p *Player) FindClosestNewPoi() *Coord {
|
||||
var shortestPoi Coord
|
||||
shortestDist := -1
|
||||
for _, v := range p.m.pois.coords {
|
||||
if !p.hitPois.Contains(v.x, v.y) {
|
||||
if t := FindSLDistance(p.pos, &v); t < shortestDist || shortestDist == -1 {
|
||||
shortestDist = t
|
||||
shortestPoi = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return &shortestPoi
|
||||
}
|
||||
|
||||
type Coord struct {
|
||||
x, y int
|
||||
label string
|
||||
dist int
|
||||
}
|
||||
|
||||
func CreateCoord(x, y int) *Coord {
|
||||
return &Coord{x: x, y: y, label: ""}
|
||||
}
|
||||
|
||||
func (c *Coord) is(x, y int) bool {
|
||||
return c.x == x && c.y == y
|
||||
}
|
||||
|
||||
type CoordCollection struct {
|
||||
coords []Coord
|
||||
}
|
||||
|
||||
func CreateCoordCollection() *CoordCollection {
|
||||
cc := new(CoordCollection)
|
||||
return cc
|
||||
}
|
||||
|
||||
func CreateCCFromCoordSlice(c []Coord) *CoordCollection {
|
||||
cc := new(CoordCollection)
|
||||
for i := range c {
|
||||
cc.Add(c[i].x, c[i].y, c[i].label)
|
||||
}
|
||||
return cc
|
||||
}
|
||||
|
||||
func (cc *CoordCollection) Add(x, y int, l string) {
|
||||
if !cc.Contains(x, y) {
|
||||
cc.coords = append(cc.coords, Coord{x: x, y: y, label: l})
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *CoordCollection) Contains(x, y int) bool {
|
||||
if cc.coords != nil && len(cc.coords) > 0 {
|
||||
for i := range cc.coords {
|
||||
if cc.coords[i].x == x && cc.coords[i].y == y {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cc *CoordCollection) GetXY(x, y int) (*Coord, error) {
|
||||
for i := range cc.coords {
|
||||
if cc.coords[i].x == x && cc.coords[i].y == y {
|
||||
return &cc.coords[i], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("Collection Doesn't Contain Coord")
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
coords []Coord
|
||||
}
|
||||
|
||||
func (p *Path) Append(c Coord) {
|
||||
p.coords = append(p.coords, c)
|
||||
}
|
||||
|
||||
func (p *Path) Contains(x, y int) bool {
|
||||
for i := range p.coords {
|
||||
if p.coords[i].is(x, y) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Path) GetCoordAt(x, y int) *Coord {
|
||||
for i := range p.coords {
|
||||
if p.coords[i].is(x, y) {
|
||||
return &p.coords[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Maze struct {
|
||||
start, end *Coord
|
||||
pois *CoordCollection
|
||||
walls *CoordCollection
|
||||
h, w int
|
||||
solvePath Path
|
||||
testedPath Path
|
||||
}
|
||||
|
||||
func CreateMaze(inp []string) *Maze {
|
||||
m := new(Maze)
|
||||
m.pois = CreateCoordCollection()
|
||||
m.walls = CreateCoordCollection()
|
||||
for y := range inp {
|
||||
for x := range inp[y] {
|
||||
if inp[y][x] == '#' {
|
||||
m.walls.Add(x, y, "#") //aoc.FillChar)
|
||||
} else if inp[y][x] != '.' {
|
||||
if inp[y][x] == '0' {
|
||||
m.start = &Coord{x: x, y: y, label: "0"}
|
||||
} else {
|
||||
m.pois.Add(x, y, string(inp[y][x]))
|
||||
}
|
||||
newOne := true
|
||||
for pi := range pois {
|
||||
if pois[pi] == string(inp[y][x]) {
|
||||
newOne = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if newOne {
|
||||
pois = append(pois, string(inp[y][x]))
|
||||
}
|
||||
}
|
||||
if x > m.w {
|
||||
m.w = x
|
||||
}
|
||||
}
|
||||
if y > m.h {
|
||||
m.h = y
|
||||
}
|
||||
}
|
||||
m.w++
|
||||
return m
|
||||
}
|
||||
|
||||
func CopyMaze(walls, pois *CoordCollection) *Maze {
|
||||
newM := new(Maze)
|
||||
newM.pois = CreateCoordCollection()
|
||||
newM.walls = CreateCoordCollection()
|
||||
for _, v := range walls.coords {
|
||||
newM.walls.Add(v.x, v.y, v.label)
|
||||
}
|
||||
for _, v := range pois.coords {
|
||||
newM.pois.Add(v.x, v.y, v.label)
|
||||
}
|
||||
return newM
|
||||
}
|
||||
|
||||
func (m *Maze) PrintMaze() {
|
||||
var err error
|
||||
poiC := color.New(color.BgGreen)
|
||||
for y := 0; y < m.h; y++ {
|
||||
for x := 0; x < m.w; x++ {
|
||||
var c *Coord
|
||||
if c, err = m.walls.GetXY(x, y); err == nil {
|
||||
fmt.Print(c.label)
|
||||
} else if m.start.is(x, y) {
|
||||
poiC.Print("0")
|
||||
} else if c, err = m.pois.GetXY(x, y); err == nil {
|
||||
poiC.Print(c.label)
|
||||
} else {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
var shortestPoiDist map[string]int
|
||||
|
||||
// StartSolve finds the shortest distance between every poi and every other poi
|
||||
// Then figures out the shortest one to hit them all
|
||||
func (m *Maze) StartSolve() {
|
||||
shortestPoiDist = make(map[string]int)
|
||||
for _, i := range m.pois.coords {
|
||||
if dist, gud := m.GetShortestPath(m.start.x, m.start.y, i.x, i.y, 0, *new(Path)); gud {
|
||||
shortestPoiDist[m.start.label+";"+i.label] = dist
|
||||
}
|
||||
for _, j := range m.pois.coords {
|
||||
if i.label != j.label {
|
||||
fst, scd := i, j
|
||||
if i.label[0] > j.label[0] {
|
||||
fst, scd = j, i
|
||||
}
|
||||
if _, ok := shortestPoiDist[fst.label+";"+scd.label]; !ok {
|
||||
if dist, gud := m.GetShortestPath(i.x, i.y, j.x, j.y, 0, *new(Path)); gud {
|
||||
shortestPoiDist[fst.label+";"+scd.label] = dist
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var f *Floor
|
||||
var poiString string
|
||||
fmt.Println("pois", pois)
|
||||
for i := range pois {
|
||||
poiString += pois[i]
|
||||
f = CreateFloorFromFile(fileNm)
|
||||
for i := 0; i < len(f.pois); i++ {
|
||||
poiString += f.pois[i].Name()
|
||||
if f.debug {
|
||||
for j := i + 1; j < len(f.pois); j++ {
|
||||
idx := f.pois[i].Name() + ";" + f.pois[j].Name()
|
||||
fmt.Println(idx, f.shortestPaths[idx])
|
||||
}
|
||||
}
|
||||
}
|
||||
poiPerms := aoc.StringPermutations(poiString)
|
||||
var wrk []string
|
||||
for i := range poiPerms {
|
||||
var found bool
|
||||
for j := range wrk {
|
||||
if wrk[j] == poiPerms[i] {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
wrk = append(wrk, poiPerms[i])
|
||||
}
|
||||
}
|
||||
poiPerms = wrk
|
||||
shortest := -1
|
||||
var shortestPerm string
|
||||
for _, perm := range poiPerms {
|
||||
if perm[0] != '0' {
|
||||
continue
|
||||
}
|
||||
if aoc.ArgIsSet("-2") {
|
||||
// For part 2 we return to 0
|
||||
perm = perm + "0"
|
||||
}
|
||||
var permTtl int
|
||||
for i := range perm {
|
||||
if i > 0 {
|
||||
@@ -414,7 +50,7 @@ func (m *Maze) StartSolve() {
|
||||
if beg[0] > end[0] {
|
||||
beg, end = end, beg
|
||||
}
|
||||
permTtl += shortestPoiDist[beg+";"+end]
|
||||
permTtl += f.shortestPaths[beg+";"+end]
|
||||
}
|
||||
}
|
||||
if permTtl < shortest || shortest == -1 {
|
||||
@@ -425,95 +61,440 @@ func (m *Maze) StartSolve() {
|
||||
fmt.Println(shortestPerm, ": ", shortest)
|
||||
}
|
||||
|
||||
// GetShortestPath just finds the shortest path between two points
|
||||
func (m *Maze) GetShortestPath(begX, begY, endX, endY, dist int, path Path) (int, bool) {
|
||||
if begX == endX && begY == endY {
|
||||
return dist, true
|
||||
}
|
||||
if path.Contains(begX, begY) {
|
||||
return 0, false
|
||||
}
|
||||
// Figure out if there is a shorter path to this coordinate
|
||||
if !m.walls.Contains(begX-1, begY) {
|
||||
if t := path.GetCoordAt(begX-1, begY); t != nil {
|
||||
if t.dist+1 < dist {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !m.walls.Contains(begX+1, begY) {
|
||||
if t := path.GetCoordAt(begX+1, begY); t != nil {
|
||||
if t.dist+1 < dist {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !m.walls.Contains(begX, begY-1) {
|
||||
if t := path.GetCoordAt(begX, begY-1); t != nil {
|
||||
if t.dist+1 < dist {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !m.walls.Contains(begX, begY+1) {
|
||||
if t := path.GetCoordAt(begX, begY+1); t != nil {
|
||||
if t.dist+1 < dist {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
}
|
||||
path.Append(Coord{x: begX, y: begY, dist: dist})
|
||||
shortest := -1
|
||||
var foundSol bool
|
||||
if path.GetCoordAt(begX-1, begY) == nil && !m.walls.Contains(begX-1, begY) {
|
||||
if nDist, sol := m.GetShortestPath(begX-1, begY, endX, endY, dist+1, path); sol {
|
||||
foundSol = true
|
||||
if nDist < shortest || shortest == -1 {
|
||||
shortest = nDist
|
||||
}
|
||||
}
|
||||
}
|
||||
if path.GetCoordAt(begX, begY-1) == nil && !m.walls.Contains(begX, begY-1) {
|
||||
if nDist, sol := m.GetShortestPath(begX, begY-1, endX, endY, dist+1, path); sol {
|
||||
foundSol = true
|
||||
if nDist < shortest || shortest == -1 {
|
||||
shortest = nDist
|
||||
}
|
||||
}
|
||||
}
|
||||
if path.GetCoordAt(begX+1, begY) == nil && !m.walls.Contains(begX+1, begY) {
|
||||
if nDist, sol := m.GetShortestPath(begX+1, begY, endX, endY, dist+1, path); sol {
|
||||
foundSol = true
|
||||
if nDist < shortest || shortest == -1 {
|
||||
shortest = nDist
|
||||
}
|
||||
}
|
||||
}
|
||||
if path.GetCoordAt(begX, begY+1) == nil && !m.walls.Contains(begX, begY+1) {
|
||||
if nDist, sol := m.GetShortestPath(begX, begY+1, endX, endY, dist+1, path); sol {
|
||||
foundSol = true
|
||||
if nDist < shortest || shortest == -1 {
|
||||
shortest = nDist
|
||||
}
|
||||
}
|
||||
}
|
||||
return shortest, foundSol
|
||||
type Floor struct {
|
||||
cells []string
|
||||
start *Coord
|
||||
end *Coord
|
||||
pois []Coord
|
||||
|
||||
shortestPaths map[string]int
|
||||
|
||||
testedPath Path
|
||||
solvePath Path
|
||||
|
||||
cellCount int
|
||||
|
||||
debug bool
|
||||
}
|
||||
|
||||
func (m *Maze) FindClosestPoi() *Coord {
|
||||
var shortestPoi Coord
|
||||
shortestDist := -1
|
||||
for _, v := range m.pois.coords {
|
||||
if t := FindSLDistance(m.start, &v); t < shortestDist || shortestDist == -1 {
|
||||
shortestDist = t
|
||||
shortestPoi = v
|
||||
func CreateFloorFromFile(fileNm string) *Floor {
|
||||
f := new(Floor)
|
||||
f.debug = aoc.ArgIsSet("-d")
|
||||
f.cells = aoc.FileToStringSlice(fileNm)
|
||||
for y := range f.cells {
|
||||
for x := range f.cells[y] {
|
||||
if f.cells[y][x] != '#' {
|
||||
f.cellCount++
|
||||
}
|
||||
if f.cells[y][x] != '#' && f.cells[y][x] != '.' {
|
||||
// A point of interest
|
||||
wrkCoord := NewCoord(x, y, f.cells[y][x])
|
||||
f.pois = append(f.pois, *wrkCoord)
|
||||
if f.cells[y][x] == '0' {
|
||||
f.start = wrkCoord
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &shortestPoi
|
||||
// Sort the pois
|
||||
sort.Sort(CoordByLabel(f.pois))
|
||||
|
||||
// Find the shortest paths between all points of interest
|
||||
f.shortestPaths = make(map[string]int)
|
||||
if aoc.ArgIsSet("-2") {
|
||||
|
||||
/* Output from running part 1
|
||||
1;2 232
|
||||
1;3 30
|
||||
1;4 188
|
||||
1;5 230
|
||||
1;6 80
|
||||
1;7 54
|
||||
2;3 218
|
||||
2;4 56
|
||||
2;5 74
|
||||
2;6 212
|
||||
2;7 278
|
||||
3;4 174
|
||||
3;5 216
|
||||
3;6 66
|
||||
3;7 80
|
||||
4;5 54
|
||||
4;6 168
|
||||
4;7 234
|
||||
5;6 210
|
||||
5;7 276
|
||||
6;7 126
|
||||
*/
|
||||
|
||||
f.shortestPaths["0;1"] = 30
|
||||
f.shortestPaths["0;2"] = 258
|
||||
f.shortestPaths["0;3"] = 56
|
||||
f.shortestPaths["0;4"] = 214
|
||||
f.shortestPaths["0;5"] = 256
|
||||
f.shortestPaths["0;6"] = 106
|
||||
f.shortestPaths["0;7"] = 44
|
||||
f.shortestPaths["1;2"] = 232
|
||||
f.shortestPaths["1;3"] = 30
|
||||
f.shortestPaths["1;4"] = 188
|
||||
f.shortestPaths["1;5"] = 230
|
||||
f.shortestPaths["1;6"] = 80
|
||||
f.shortestPaths["1;7"] = 54
|
||||
f.shortestPaths["2;3"] = 218
|
||||
f.shortestPaths["2;4"] = 56
|
||||
f.shortestPaths["2;5"] = 74
|
||||
f.shortestPaths["2;6"] = 212
|
||||
f.shortestPaths["2;7"] = 278
|
||||
f.shortestPaths["3;4"] = 174
|
||||
f.shortestPaths["3;5"] = 216
|
||||
f.shortestPaths["3;6"] = 66
|
||||
f.shortestPaths["3;7"] = 80
|
||||
f.shortestPaths["4;5"] = 54
|
||||
f.shortestPaths["4;6"] = 168
|
||||
//f.shortestPaths["4;7"] = 266
|
||||
//f.shortestPaths["5;6"] = 262
|
||||
//f.shortestPaths["5;7"] = 304
|
||||
f.shortestPaths["6;7"] = 126
|
||||
}
|
||||
for i := range f.pois {
|
||||
for j := range f.pois {
|
||||
if i != j {
|
||||
one, two := f.pois[i], f.pois[j]
|
||||
if one.label > two.label {
|
||||
one, two = two, one
|
||||
}
|
||||
idx := string(one.label) + ";" + string(two.label)
|
||||
if _, ok := f.shortestPaths[idx]; !ok {
|
||||
p := f.GetShortestPath(&one, &two)
|
||||
if f.debug {
|
||||
fmt.Println(p.Name())
|
||||
f.PrintPath(p)
|
||||
fmt.Println("Path length:", len(p.coords), "\n")
|
||||
for i := range p.coords {
|
||||
fmt.Print(p.coords[i].Name(), ",")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
f.shortestPaths[idx] = len(p.coords) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func FindSLDistance(p1, p2 *Coord) int {
|
||||
a := math.Abs(float64(p1.x) - float64(p2.x))
|
||||
b := math.Abs(float64(p1.y) - float64(p2.y))
|
||||
return int(math.Pow(a, 2) + math.Pow(b, 2))
|
||||
func (f *Floor) GetPoi(lbl byte) *Coord {
|
||||
for i := range f.pois {
|
||||
if f.pois[i].label == lbl {
|
||||
return &f.pois[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var shortestFound int
|
||||
|
||||
func (f *Floor) GetShortestPath(st, end *Coord) Path {
|
||||
f.solvePath = *new(Path)
|
||||
f.testedPath = *new(Path)
|
||||
hldStart := f.start
|
||||
f.start = end
|
||||
f.end = st
|
||||
shortestFound = -1
|
||||
path, sol := f.Solve(f.start.x, f.start.y, 0)
|
||||
f.start = hldStart
|
||||
if sol {
|
||||
return path
|
||||
}
|
||||
return *new(Path)
|
||||
}
|
||||
|
||||
func (f *Floor) Print() {
|
||||
for y := range f.cells {
|
||||
for x := range f.cells[y] {
|
||||
fmt.Print(string(f.cells[y][x]))
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Floor) PrintPath(p Path) {
|
||||
pathCol := color.New(color.BgGreen)
|
||||
for y := range f.cells {
|
||||
for x := range f.cells[y] {
|
||||
if p.ContainsCoord(x, y) {
|
||||
if f.cells[y][x] == '.' {
|
||||
pathCol.Print("O")
|
||||
} else {
|
||||
pathCol.Print(string(f.cells[y][x]))
|
||||
}
|
||||
} else {
|
||||
fmt.Print(string(f.cells[y][x]))
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Floor) IsWall(x, y int) bool {
|
||||
return f.cells[y][x] == '#'
|
||||
}
|
||||
|
||||
func (f *Floor) GetCoord(x, y int) *Coord {
|
||||
return NewCoord(x, y, f.cells[y][x])
|
||||
}
|
||||
|
||||
func (f *Floor) Solve(x, y, dist int) (Path, bool) {
|
||||
var tPathContains bool
|
||||
shortest := new(Path)
|
||||
wrkCoord := *f.GetCoord(x, y)
|
||||
wrkCoord.dist = dist
|
||||
|
||||
if f.end.Is(x, y) {
|
||||
shortest.Append(wrkCoord)
|
||||
if shortestFound == -1 || dist < shortestFound {
|
||||
shortestFound = dist
|
||||
}
|
||||
return *shortest, true
|
||||
}
|
||||
if f.IsWall(x, y) || (shortestFound != -1 && dist > shortestFound) {
|
||||
return *shortest, false
|
||||
}
|
||||
// Test if we already have this coord via a shorter path
|
||||
if t := f.testedPath.GetCoordAt(x, y); t != nil {
|
||||
if t.dist <= dist {
|
||||
return *shortest, false
|
||||
}
|
||||
tPathContains = true
|
||||
t.dist = dist
|
||||
}
|
||||
// Test if there is a shorter path to this coordinate
|
||||
if !f.IsWall(x-1, y) {
|
||||
if t := f.testedPath.GetCoordAt(x-1, y); t != nil {
|
||||
if t.dist+1 < wrkCoord.dist {
|
||||
return *shortest, false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !f.IsWall(x+1, y) {
|
||||
if t := f.testedPath.GetCoordAt(x+1, y); t != nil {
|
||||
if t.dist+1 < wrkCoord.dist {
|
||||
return *shortest, false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !f.IsWall(x, y-1) {
|
||||
if t := f.testedPath.GetCoordAt(x, y-1); t != nil {
|
||||
if t.dist+1 < wrkCoord.dist {
|
||||
return *shortest, false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !f.IsWall(x, y+1) {
|
||||
if t := f.testedPath.GetCoordAt(x, y+1); t != nil {
|
||||
if t.dist+1 < wrkCoord.dist {
|
||||
return *shortest, false
|
||||
}
|
||||
}
|
||||
}
|
||||
// We haven't found a shorter path to this coordinate, so carry on
|
||||
if !tPathContains {
|
||||
f.testedPath.Append(wrkCoord)
|
||||
if f.debug {
|
||||
fmt.Println(aoc.ClearScreen, f.start.Name(), "=>", f.end.Name(), "\n", len(f.testedPath.coords), "/", f.cellCount, "\n", "Shortest:", shortestFound)
|
||||
f.PrintPath(f.testedPath)
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
}
|
||||
}
|
||||
var solvePaths []Path
|
||||
var didN, didE, didS, didW bool
|
||||
|
||||
var preferNS bool
|
||||
if math.Abs(float64(f.end.y-y)) > math.Abs(float64(f.end.x-x)) {
|
||||
// Favor N/S movements
|
||||
preferNS = true
|
||||
}
|
||||
|
||||
for tdir := 0; tdir < 2; tdir++ {
|
||||
if preferNS {
|
||||
switch {
|
||||
case f.end.y < y && !didN:
|
||||
f.TestAndUpdate(x, y-1, dist+1)
|
||||
if p, sol := f.Solve(x, y-1, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
didN = true
|
||||
case f.end.y > y && !didS:
|
||||
f.TestAndUpdate(x, y+1, dist+1)
|
||||
if p, sol := f.Solve(x, y+1, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
didS = true
|
||||
case f.end.x > x && !didE:
|
||||
f.TestAndUpdate(x+1, y, dist+1)
|
||||
if p, sol := f.Solve(x+1, y, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
didE = true
|
||||
case f.end.x < x && !didW:
|
||||
f.TestAndUpdate(x-1, y, dist+1)
|
||||
if p, sol := f.Solve(x-1, y, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
didW = true
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case f.end.x > x && !didE:
|
||||
f.TestAndUpdate(x+1, y, dist+1)
|
||||
if p, sol := f.Solve(x+1, y, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
didE = true
|
||||
case f.end.x < x && !didW:
|
||||
f.TestAndUpdate(x-1, y, dist+1)
|
||||
if p, sol := f.Solve(x-1, y, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
didW = true
|
||||
case f.end.y < y && !didN:
|
||||
f.TestAndUpdate(x, y-1, dist+1)
|
||||
if p, sol := f.Solve(x, y-1, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
didN = true
|
||||
case f.end.y > y && !didS:
|
||||
f.TestAndUpdate(x, y+1, dist+1)
|
||||
if p, sol := f.Solve(x, y+1, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
didS = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !didN {
|
||||
f.TestAndUpdate(x, y-1, dist+1)
|
||||
if p, sol := f.Solve(x, y-1, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
}
|
||||
if !didE {
|
||||
f.TestAndUpdate(x+1, y, dist+1)
|
||||
if p, sol := f.Solve(x+1, y, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
}
|
||||
if !didS {
|
||||
f.TestAndUpdate(x, y+1, dist+1)
|
||||
if p, sol := f.Solve(x, y+1, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
}
|
||||
if !didW {
|
||||
f.TestAndUpdate(x-1, y, dist+1)
|
||||
if p, sol := f.Solve(x-1, y, wrkCoord.dist+1); sol {
|
||||
p.Append(wrkCoord)
|
||||
solvePaths = append(solvePaths, p)
|
||||
}
|
||||
}
|
||||
|
||||
var sol bool
|
||||
if sol = len(solvePaths) > 0; sol {
|
||||
shortest = &solvePaths[0]
|
||||
for i := range solvePaths {
|
||||
if solvePaths[i].Length()+1 < shortest.Length() {
|
||||
shortest = &solvePaths[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return *shortest, sol
|
||||
}
|
||||
|
||||
func (f *Floor) TestAndUpdate(x, y, dist int) {
|
||||
if t := f.testedPath.GetCoordAt(x, y); t != nil {
|
||||
if t.dist > dist+1 {
|
||||
f.testedPath.SetCoordDist(x, y, dist+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Coord struct {
|
||||
x, y, dist int
|
||||
label byte
|
||||
}
|
||||
|
||||
func NewCoord(x, y int, label byte) *Coord {
|
||||
return &Coord{x, y, -1, label}
|
||||
}
|
||||
|
||||
func (c *Coord) Name() string {
|
||||
if c.label != '#' && c.label != '.' && c.label != 0 {
|
||||
return string(c.label)
|
||||
}
|
||||
return fmt.Sprint("(", c.x, ",", c.y, ")")
|
||||
}
|
||||
|
||||
func (c *Coord) Is(x, y int) bool {
|
||||
return c.x == x && c.y == y
|
||||
}
|
||||
|
||||
type CoordByLabel []Coord
|
||||
|
||||
func (a CoordByLabel) Len() int { return len(a) }
|
||||
func (a CoordByLabel) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a CoordByLabel) Less(i, j int) bool { return a[i].label < a[j].label }
|
||||
|
||||
type Path struct {
|
||||
coords []Coord
|
||||
}
|
||||
|
||||
func (p *Path) Append(c Coord) {
|
||||
p.coords = append(p.coords, c)
|
||||
}
|
||||
|
||||
func (p *Path) ContainsCoord(x, y int) bool {
|
||||
for i := range p.coords {
|
||||
if p.coords[i].Is(x, y) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Path) GetCoordAt(x, y int) *Coord {
|
||||
for i := range p.coords {
|
||||
if p.coords[i].Is(x, y) {
|
||||
return &p.coords[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Path) SetCoordDist(x, y, dist int) bool {
|
||||
for i := range p.coords {
|
||||
if p.coords[i].Is(x, y) {
|
||||
p.coords[i].dist = dist
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Path) Length() int {
|
||||
return len(p.coords)
|
||||
}
|
||||
|
||||
func (p *Path) Name() string {
|
||||
return p.coords[0].Name() + " -> " + p.coords[len(p.coords)-1].Name()
|
||||
}
|
||||
|
69
2016/day24/problem
Normal file
69
2016/day24/problem
Normal file
@@ -0,0 +1,69 @@
|
||||
Advent of Code
|
||||
|
||||
--- Day 24: Air Duct Spelunking ---
|
||||
|
||||
You've finally met your match; the doors that provide access to the roof are locked tight, and all of the controls and
|
||||
related electronics are inaccessible. You simply can't reach them.
|
||||
|
||||
The robot that cleans the air ducts, however, can.
|
||||
|
||||
It's not a very fast little robot, but you reconfigure it to be able to interface with some of the exposed wires that
|
||||
have been routed through the HVAC system. If you can direct it to each of those locations, you should be able to bypass
|
||||
the security controls.
|
||||
|
||||
You extract the duct layout for this area from some blueprints you acquired and create a map with the relevant locations
|
||||
marked (your puzzle input). 0 is your current location, from which the cleaning robot embarks; the other numbers are (in
|
||||
no particular order) the locations the robot needs to visit at least once each. Walls are marked as #, and open passages
|
||||
are marked as .. Numbers behave like open passages.
|
||||
|
||||
For example, suppose you have a map like the following:
|
||||
|
||||
###########
|
||||
#0.1.....2#
|
||||
#.#######.#
|
||||
#4.......3#
|
||||
###########
|
||||
|
||||
To reach all of the points of interest as quickly as possible, you would have the robot take the following path:
|
||||
|
||||
• 0 to 4 (2 steps)
|
||||
• 4 to 1 (4 steps; it can't move diagonally)
|
||||
• 1 to 2 (6 steps)
|
||||
• 2 to 3 (2 steps)
|
||||
|
||||
Since the robot isn't very fast, you need to find it the shortest route. This path is the fewest steps (in the above
|
||||
example, a total of 14) required to start at 0 and then visit every other location at least once.
|
||||
|
||||
Given your actual map, and starting from location 0, what is the fewest number of steps required to visit every non-0
|
||||
number marked on the map at least once?
|
||||
|
||||
Your puzzle answer was ____.
|
||||
|
||||
--- Part Two ---
|
||||
|
||||
Of course, if you leave the cleaning robot somewhere weird, someone is bound to notice.
|
||||
|
||||
What is the fewest number of steps required to start at 0, visit every non-0 number marked on the map at least once, and
|
||||
then return to 0?
|
||||
|
||||
Your puzzle answer was _____.
|
||||
|
||||
|
||||
References
|
||||
|
||||
Visible links
|
||||
. http://adventofcode.com/
|
||||
. http://adventofcode.com/2016/about
|
||||
. http://adventofcode.com/2016/support
|
||||
. http://adventofcode.com/2016/events
|
||||
. http://adventofcode.com/2016/settings
|
||||
. http://adventofcode.com/2016/auth/logout
|
||||
. http://adventofcode.com/2016
|
||||
. http://adventofcode.com/2016
|
||||
. http://adventofcode.com/2016/leaderboard
|
||||
. http://adventofcode.com/2016/stats
|
||||
. http://adventofcode.com/2016/sponsors
|
||||
. http://adventofcode.com/2016/sponsors
|
||||
. https://en.wikipedia.org/wiki/HVAC
|
||||
. http://adventofcode.com/2016
|
||||
. http://adventofcode.com/2016/day/24/input
|
@@ -1,5 +0,0 @@
|
||||
###########
|
||||
#0.1.....2#
|
||||
#.#######.#
|
||||
#4.......3#
|
||||
###########
|
@@ -1,43 +0,0 @@
|
||||
###################################################################################################################################################################################
|
||||
#.........#...#.............#...#.#.#.....#...........#.........#.#...#.......#.#.#...#...#.................#...........#.#...#.#.......#.......#.......#...#...#.....#.....#.....#
|
||||
#.#.#.#.#.#.#########.#.#.###.#.#.#.###.###.#.###.#.#.#.###.#.###.#.#.#.#.#####.#.#.#.#.#.#.###.#.#.#.#.#.#.#.###.#.#.###.#.#.#.#####.#.#.#.###.#.#.#.#.#.###.#.###.###.###.###.#.#
|
||||
#...#...#...#.......#...#.#.#.....#...#.....#.........#.......#.#...#...#.#.............#...#.......#.#.#...#.#.....#.......#...#.....#...#...........#...#...#.#...............#2#
|
||||
#.###.#.#.#####.###.###.#.#.#.#.###.#.#.#####.#######.#.###.###.#.#.#.#.#.#####.###.###.#.#.#####.#.###.#.###.#.#.#.#.#.#######.#######.#.#.###.###.###.#.#.#.#.#.#.###.#.###.#.###
|
||||
#.......#.........#.#.#...#...#...#.....#.#.............#.....#...#.......#.#.....#...#...#.......#.............................#.#...#...#...#.....#...#.......#.......#.......#.#
|
||||
#.###.#.#.#########.#.#.#.#.#.#.#.#.#.#.#.#.#.#.###.#.#.#####.#.#.#######.#.#.#.#.#.#.#####.#.###.#.#####.#.###.###.#.#.###.###.#.#.#.#####.#.###.#.#.#.#######.###.#.#.#.###.###.#
|
||||
#...#.#...#...#...#...#.#...#.....#...#...........#.....#.........#.#...#...#...#.#...#.......#...#.#.....#.#.....#...#.#.......#.#.#.......#.......#...........#.#.#...#.#.......#
|
||||
#.#.#.###.###.#.#.#######.#.#.#.#.#.#.###.###########.#.#.#####.###.#.#.#####.#.#.#.#####.###.#.###.#####.###.#####.#########.#.###.#.###.#.#.#.#.###.###.#.#####.#.#.#.#.#.###.#.#
|
||||
#.......#.......#...........#...#.#...#.............#.#.#...#...#.....#...#...#.#...#...#.......#.#.#.#...#.....#.#.#.........#...#...#.....#.#...........#.#.......#.#.#...#...#.#
|
||||
#.#.#.###.#####.#.#####.#.###.#.#.#.###.#.#.#.###.###.#.#####.###.#####.#.#.#####.#.#.#.#######.#.###.#.###.#####.#.#####.#.#.#####.#.#.#.#.###.#.#######.#.#.#.###########.#.#.#.#
|
||||
#.#.#.....#.#1..........#.#...#...#.....#.........#...............#.#...#.....#...#.......#...........#.#...#.#.....#.............#.............#.....#...#.....#...#.....#.#.....#
|
||||
#.#.###.#.#.#####.#.#.#.###.#####.#.#.#.###.###.#.#.#.#####.#.#.###.#.#.#####.#.#.#.#.#.#.###.#.###.#.#.#.#.#.#.###.#.#.#####.###.###.###.#.#.#.###.#.#.#.#.#.###.#.#.#####.#####.#
|
||||
#...#...#.#...#.#.#.#.#.......#.....................#.#...............#.......#.#...#.#.#.....#.#.#...#...#.#.......#.....#.#...#.........#.#.#...#.........#.............#.....#.#
|
||||
#.###.###.###.#.#.###.#.#####.#.#####.###.###########.###.#.#.#####.#.#.#.###.###.#.#.#.#.###.#.#.#.#.###.#.###.#.#########.#.#.#.#.###.#.#.#.#.#.#.#.#.#.###.###.#####.#.#####.###
|
||||
#.....#.#.......#.#.#.....#...#.......#...#.#...#.............#.#.#.....#.........#...#.#.........#.#.#.#...#.#...#...#.......#.....#.#.....#.#.#.......#.#...#.#.....#.......#...#
|
||||
#.#.###.#.#######.#.#.#.###.#.###.###.#####.#.#.#.#.###.###.###.#.#.#####.#.#####.#.#.#.#.#.#########.#.#.#.#.#.#.#.#.#.###.#.###.#.#.###.###.#.#######.#.#.#.#.#.###.#.#.#.#.###.#
|
||||
#.#.#.......#...#...#.#.#.#.....#.....#...#...#.....#...#...#...#...........#...........#.........#.#.....#.....#.......#...#...#...#.#...#.#.#.........#...#.....#.#...#.#.....#.#
|
||||
#.#.#.#####.#.#.#.###.###.#.#.#####.#.###.#.#.#####.#.#####.#.#.#.#####.###.#.#.#.#.###.#.#.#.#.#.#.#.###.#.#####.###.###.#.#.#.#.#.#.#.#.#.###.###.#.#######.#####.###.#.###.###.#
|
||||
#.#.......#.#...#.#...#.#.#.......#...#.....#.........#.....#.#.....#...#...#.#.#...#.....#...#...#...#.....#.......#.#.#...#.#.........#...#.#...#.#.#.#.#.....#.....#.........#.#
|
||||
#.###.#.#.#.###.#.#.###.#.#######.#.#.###.#.###.#.#.#.#.#.#.###.#####.#.###.#######.#####.###.#.#.#.#.#.###.#.#####.#.#.#.#.#.#.#.#.#.#.#.#.#.#######.#.#.#.#.#.#.#.#.#.#.###.#.#.#
|
||||
#0#...#.#.#.....#...#...#.#...#...#...#...#.......#.#.#.....#...#...#...#...#...#...#.........#.......#...#.......#...#...#...#.......#...#...............#.....#...#...#...#.#...#
|
||||
###.#.#.###.###########.#.#####.###.###.###.###.###.#.###.#.#.#.#.#.#.#.#.#.#.###.#.###.#.#.#.#####.#.#.#.#.#.#.#.#.#.#.#.###.#####.#.#####.#.#.#.#####.#.#.#####.#####.#.#.#.#.###
|
||||
#...#.#...#.......#...#...........#...#.....#...#...#.#.........#.....#.#...#.......#.....#.#.#.........#.........#...#.#.#...#.#.....#.....................#...............#.....#
|
||||
###.#.#.#.#.#.###.#.#.#.#.#######.#.#.###.###.#.#.#####.###.###.###.###.#.#.#.#.#######.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.###.#.#####.###.#######.###.###.#.###.###.###.###.#.#.#
|
||||
#...#.............#.#...........#.#.#.....#.....#.......#.#.....#...#.........#.....#.......#.....#...#.....#...#.......#...#...#.#.........#...#...........#.......#...#.......#.#
|
||||
###.#.#########.#.#.#####.#.#.#.#.#.#.###.#.#######.#.#.#.#.#.#####.#.#.#.#####.#.#.#.#####.#######.#.#.#.#.#.#####.#.#.#.#####.#.#.###.#######.#######.#.#.###.#.#.#.#.#.#.###.#.#
|
||||
#.#...............#.#.......#.....#.................#...#.#.#.......#...#.......#.#.#...#.#.......#...#.....#.....#...#.#.......#.#.#.....#.....#.....#.#...#.#...#...#.#...#.#.#.#
|
||||
#.#.#.###.#.#.#.#.#.#.#.#.#.#########.#.#.#.#.#.#.#.#.#.#.#.###.#.#.#.###.#.#.#.#.#.###.#.#.###.#.#.###.###.#.#.#####.#####.#####.#.#.#.#.#.#.###.#.#.#.###.#.#.#.#.###.#.#.#.#####
|
||||
#...#.#.....#...#.............#.#.#.#.........#.#...#.#.....#.......#...#.#.#.....#.......#...#...#...........#.....#.#.......#...#...#.#.........#...#...#.....#.......#...#.....#
|
||||
###.#.#.#.#.#.#.###.#.###.#.###.#.#.#.#.###.#.###.#.#.#####.###.###.#.#.#.###.#.#.###.###.###.#.#.#.#.###.#####.###.#####.#.#.###.###.#####.#####.#.#.#######.###.###.###.#.#.#.#.#
|
||||
#...#.#.....#.........#.......#.#...#.......#...#...#...........#.....#.......#...........#.......#...#...#.....#.....#...#.......#.#.#...#.#.....#.........#.....#...#.#...#.#...#
|
||||
###.###.###.###.#.#####.###.#.#.#.#.#.###.###.###.###.#.#####.#.#.#.#.###.#.#.###.#.###.###.#.#.###.###.#.#.#.#.#.#.#.#.#.#.#.#.###.#.###.#.###.#.#.#####.#.#####.#.#.#.#####.#.#.#
|
||||
#.#.#...#.#.#...................#.......#.#.#.#.#.....#.#.#.....#...#.......#.#...#.......#.....#...#.....#...#...#.#...#...#.........#...#...#...........#.........#.#...#...#...#
|
||||
#.#.#.###.#.###.#####.#.#.###.#.#####.#.#.#.#.#.#.###.#.#.#.#######.###.#.#.#.#.#.###.#.#.#.###.#.#####.#.#.#.#####.#.###.#.#.###.###.#.###.#.#.###########.#.#######.#.#.#.###.###
|
||||
#...#.#.#...#.#.................#.#.....#.#.........#.....#.#.#...#.#.........#...#.#...#.#.........#...#...#.....#.#.#.#.....#...#.#.....#.#.............#.#...#.#.#.....#...#...#
|
||||
#.#.###.#.#.#.#.#.#.#.#.###.###.#.#.#######.#######.###.#.#.#.###.#.#.#####.###.###.#####.#.#####.#.#.#.#.#.#.###.#.#.#.#.###.#.#.#.#.#.#.#.#####.#.#.#.#.###.#.#.#.#.#####.###.#.#
|
||||
#...#.....#.....#.....#.#...#...#...............#...#...#...#.....#...#.#.....#.#.#...#...#.#...#...#.....#...#...#...................#.........#...#.........#.#...#.......#...#.#
|
||||
###.#.###.#.#.#.###.#.#.#.#.#.###.###.#.###.###.#.###.#.#######.#.#.#.#.#.#####.#.#.#.#.#.###.#.#.#.#####.#.#.###.#.#######.#######.###.#######.#.#.#.#.###.###.###.#.#.#.#.#.#.#.#
|
||||
#.....#...#.#.#...........#.#...#.........#.....#.#.#...#.....#.............#...#...#...#.#.#...#.......#...#.....#.#.......#.#.....#...#...#...#...#...#...#.#.....#.#.......#.#.#
|
||||
#.#.#.#.#.#.#.#.#.#.###.#####.#.#############.#.###.#.#.#.#.###.###.#######.#.#.###.#.###.#.###.#######.###.###.#.#.###.#.#.#.#.#.###.###.###.#.#.###.#.#.#.#.#.#.###.#.###.#.#.###
|
||||
#...#.....#.#...#.#.....#.....#...#...#.......#.#.......#.#.......#.#.........#...#.#.....#.....#.#.......#.#.......#.......#.....#.....#.....#...#.#.#.#...#.#...#.#...#...#.#.#.#
|
||||
###################################################################################################################################################################################
|
Reference in New Issue
Block a user