Day 15 commit, not done
This commit is contained in:
parent
943079f64f
commit
08c6209701
203
2018/day15/day15-overthink
Normal file
203
2018/day15/day15-overthink
Normal file
@ -0,0 +1,203 @@
|
||||
package overthink
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
UseEmoji = false
|
||||
MaxInt = int(^uint(0) >> 1)
|
||||
ClearScreen = "\033[H\033[2J"
|
||||
|
||||
DIR_N = -1i
|
||||
DIR_E = 1
|
||||
DIR_S = 1i
|
||||
DIR_W = -1
|
||||
)
|
||||
|
||||
var width int
|
||||
var input []byte
|
||||
|
||||
var elves []*complex64
|
||||
var goblins []*complex64
|
||||
var allChars []*complex64
|
||||
var charMap map[complex64]*Character
|
||||
|
||||
func main() {
|
||||
stdinToByteSlice()
|
||||
setupBattle()
|
||||
part1()
|
||||
}
|
||||
|
||||
func part1() {
|
||||
//for {
|
||||
sortCharacters()
|
||||
printBattleField()
|
||||
for _, c := range allChars {
|
||||
charMap[*c].tick()
|
||||
}
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
printBattleField()
|
||||
//}
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
func sortCharacters() {
|
||||
sort.Sort(ByPos(allChars))
|
||||
sort.Sort(ByPos(elves))
|
||||
sort.Sort(ByPos(goblins))
|
||||
}
|
||||
|
||||
type Character struct {
|
||||
tp byte
|
||||
pos complex64
|
||||
power int
|
||||
health int
|
||||
}
|
||||
|
||||
func (c *Character) tick() {
|
||||
// If we're already in range of a target, don't look for a new one
|
||||
var alreadyAtTarget bool
|
||||
var chosenTarget *complex64
|
||||
lowestTargetHp := MaxInt
|
||||
for _, v := range elves {
|
||||
if c.isAdjacentTo(*v) {
|
||||
if charMap[*v].health < lowestTargetHp {
|
||||
chosenTarget = v
|
||||
}
|
||||
alreadyAtTarget = true
|
||||
}
|
||||
}
|
||||
if alreadyAtTarget {
|
||||
// Attack the target
|
||||
charMap[*chosenTarget].health -= c.power
|
||||
} else {
|
||||
// Looking for a target
|
||||
if c.tp == 'G' {
|
||||
// First identify all possible targets (elves that have an open adjacent space)
|
||||
for _, v := range elves {
|
||||
|
||||
}
|
||||
} else {
|
||||
// First identify all possible targets (goblins that have an open adjacent space)
|
||||
for _, v := range goblins {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Character) isAdjacentTo(p complex64) bool {
|
||||
return c.pos+DIR_N == p || c.pos+DIR_E == p ||
|
||||
c.pos+DIR_S == p || c.pos+DIR_W == p
|
||||
}
|
||||
|
||||
func (c *Character) hasOpenFlank() bool {
|
||||
|
||||
}
|
||||
|
||||
// Not sure if we'll use this...
|
||||
func manhattanDistance(p1, p2 complex64) int {
|
||||
x1, y1, x2, y2 := real(p1), imag(p1), real(p2), imag(p2)
|
||||
return int(math.Abs(float64(x1)-float64(x2)) + math.Abs(float64(y1)-float64(y2)))
|
||||
}
|
||||
|
||||
// We have to sort the characters on each tick by y,x position
|
||||
// y is the imaginary part, x is the real part
|
||||
type ByPos []*complex64
|
||||
|
||||
func (c ByPos) Len() int { return len(c) }
|
||||
func (c ByPos) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ByPos) Less(i, j int) bool {
|
||||
return imag(*c[i]) < imag(*c[j]) ||
|
||||
(imag(*c[i]) == imag(*c[j]) && real(*c[i]) < real(*c[j]))
|
||||
}
|
||||
|
||||
// getByte pulls a byte from the given position in the input
|
||||
func getByte(pos complex64) byte {
|
||||
return input[int(real(pos))+int(imag(pos))*width]
|
||||
}
|
||||
|
||||
func setByte(pos complex64, b byte) {
|
||||
input[int(real(pos))+int(imag(pos))*width] = b
|
||||
}
|
||||
|
||||
func getPosFromInt(i int) complex64 {
|
||||
return complex(float32(i%width), float32(i/width))
|
||||
}
|
||||
|
||||
func printBattleField() {
|
||||
fmt.Print(ClearScreen)
|
||||
for i := 0; i < len(input); i++ {
|
||||
pos := getPosFromInt(i)
|
||||
var bt byte
|
||||
if c, ok := charMap[pos]; ok {
|
||||
if UseEmoji {
|
||||
switch c.tp {
|
||||
case 'G':
|
||||
if UseEmoji {
|
||||
fmt.Print("👺")
|
||||
}
|
||||
case 'E':
|
||||
fmt.Print("😃")
|
||||
}
|
||||
} else {
|
||||
fmt.Print(string(c.tp))
|
||||
}
|
||||
bt = c.tp
|
||||
} else {
|
||||
bt = getByte(pos)
|
||||
if UseEmoji {
|
||||
if bt == '#' {
|
||||
fmt.Print("🏿")
|
||||
} else {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
} else {
|
||||
fmt.Print(string(bt))
|
||||
}
|
||||
}
|
||||
if i%width == width-1 {
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupBattle() {
|
||||
charMap = make(map[complex64]*Character)
|
||||
for i := 0; i < len(input); i++ {
|
||||
pos := complex(float32(i%width), float32(i/width))
|
||||
bt := getByte(pos)
|
||||
if bt == 'G' || bt == 'E' {
|
||||
charMap[pos] = &Character{
|
||||
tp: bt,
|
||||
pos: pos,
|
||||
power: 3,
|
||||
health: 200,
|
||||
}
|
||||
setByte(pos, '.')
|
||||
allChars = append(allChars, &charMap[pos].pos)
|
||||
switch bt {
|
||||
case 'G':
|
||||
goblins = append(goblins, &charMap[pos].pos)
|
||||
case 'E':
|
||||
elves = append(elves, &charMap[pos].pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stdinToByteSlice() {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
data := scanner.Bytes()
|
||||
if width == 0 {
|
||||
width = len(data)
|
||||
}
|
||||
input = append(input, data...)
|
||||
}
|
||||
}
|
413
2018/day15/day15.go
Normal file
413
2018/day15/day15.go
Normal file
@ -0,0 +1,413 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
* 173327 is too low
|
||||
*/
|
||||
var input []byte
|
||||
var width int
|
||||
var characters map[complex64]*Character
|
||||
var charSlice []*complex64
|
||||
var turnCount int
|
||||
|
||||
const (
|
||||
ClearScreen = "\033[H\033[2J"
|
||||
MaxInt = int(^uint(0) >> 1)
|
||||
|
||||
DIR_N = -1i
|
||||
DIR_E = 1
|
||||
DIR_S = 1i
|
||||
DIR_W = -1
|
||||
)
|
||||
|
||||
func main() {
|
||||
characters = make(map[complex64]*Character)
|
||||
stdinToByteSlice()
|
||||
setupBattle()
|
||||
part1()
|
||||
}
|
||||
|
||||
func part1() {
|
||||
for {
|
||||
charSlice = charSlice[0:0]
|
||||
for i := 0; i < len(input); i++ {
|
||||
pos := getPosFromInt(i)
|
||||
bt := getByte(pos)
|
||||
if bt == 'G' || bt == 'E' {
|
||||
charSlice = append(charSlice, &pos)
|
||||
}
|
||||
}
|
||||
sort.Sort(ByPos(charSlice))
|
||||
fmt.Printf("= Turn %d =\n", turnCount)
|
||||
printBattlefield()
|
||||
for _, v := range charSlice {
|
||||
if char, ok := characters[*v]; ok {
|
||||
if !char.tick() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if false {
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
}
|
||||
printBattlefield()
|
||||
if checkBattleOver() {
|
||||
break
|
||||
}
|
||||
turnCount++
|
||||
}
|
||||
var totalHP int
|
||||
sort.Sort(ByPos(charSlice))
|
||||
for _, v := range charSlice {
|
||||
if c, ok := characters[*v]; ok {
|
||||
fmt.Println(c.string())
|
||||
totalHP += c.health
|
||||
}
|
||||
}
|
||||
fmt.Println(turnCount, totalHP)
|
||||
fmt.Println("Result:", (totalHP * turnCount))
|
||||
}
|
||||
|
||||
func checkBattleOver() bool {
|
||||
var elves, gobs int
|
||||
for _, v := range characters {
|
||||
if v.tp == 'E' {
|
||||
elves++
|
||||
} else if v.tp == 'G' {
|
||||
gobs++
|
||||
}
|
||||
}
|
||||
return elves == 0 || gobs == 0
|
||||
}
|
||||
|
||||
type Character struct {
|
||||
tp byte
|
||||
health int
|
||||
power int
|
||||
pos complex64
|
||||
kills int
|
||||
}
|
||||
|
||||
func (c *Character) hasEnemies() bool {
|
||||
for _, oppPos := range charSlice {
|
||||
if v, ok := characters[*oppPos]; ok {
|
||||
if v.tp != c.tp {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Character) tick() bool {
|
||||
// If we're already in range of a target, don't look for a new one
|
||||
if tPos, err := c.easiestAdjacentTarget(); err == nil {
|
||||
c.attack(tPos)
|
||||
return c.hasEnemies()
|
||||
}
|
||||
// If we have no open sides, we can't more
|
||||
if !c.hasOpenFlank() {
|
||||
fmt.Println(c.string(), "blocked in")
|
||||
return true
|
||||
}
|
||||
// Otherwise, find a target
|
||||
var opportunities []complex64
|
||||
for _, oppPos := range charSlice {
|
||||
if v, ok := characters[*oppPos]; ok {
|
||||
if v.tp != c.tp {
|
||||
opportunities = append(opportunities, *oppPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
var opportunities []complex64
|
||||
for _, oppPos := range charSlice {
|
||||
if v, ok := characters[*oppPos]; ok {
|
||||
if v.tp != c.tp {
|
||||
fmt.Println(c.string(), "checking:", v.string())
|
||||
opportunities = append(opportunities, v.getOpenSides()...)
|
||||
}
|
||||
}
|
||||
}
|
||||
closestDistance := MaxInt
|
||||
var closestPos complex64
|
||||
for _, v := range opportunities {
|
||||
if distance(c.pos, v) < closestDistance {
|
||||
closestDistance = distance(c.pos, v)
|
||||
closestPos = v
|
||||
}
|
||||
}
|
||||
*/
|
||||
// If there are no opportunities, we're done
|
||||
if len(opportunities) == 0 {
|
||||
fmt.Println(c.string(), "sees no opportunities")
|
||||
return true
|
||||
}
|
||||
var bestOpportunities []*complex64
|
||||
moves := make(map[complex64]complex64)
|
||||
bestDist := MaxInt
|
||||
for _, opp := range opportunities {
|
||||
paths := append([]pathPoint{}, pathPoint{
|
||||
pos: opp,
|
||||
count: 0,
|
||||
})
|
||||
move, length := findMove(c.pos, paths)
|
||||
if length < bestDist {
|
||||
bestOpportunities = append([]*complex64{}, &opp)
|
||||
for k := range moves {
|
||||
delete(moves, k)
|
||||
}
|
||||
moves[opp] = move
|
||||
bestDist = length
|
||||
} else if length == bestDist {
|
||||
bestOpportunities = append(bestOpportunities, &opp)
|
||||
moves[opp] = move
|
||||
}
|
||||
}
|
||||
sort.Sort(ByPos(bestOpportunities))
|
||||
//paths := append([]pathPoint{}, pathPoint{
|
||||
// pos: closestPos,
|
||||
// count: 0,
|
||||
//})
|
||||
//move := findMove(c.pos, paths)
|
||||
if len(bestOpportunities) > 0 {
|
||||
goWith := bestOpportunities[0]
|
||||
fmt.Println(c.string(),
|
||||
"moving to",
|
||||
getCoordString(moves[*goWith]),
|
||||
"(->",
|
||||
getCoordString(*goWith),
|
||||
bestDist, ")")
|
||||
c.moveTo(*goWith)
|
||||
} else {
|
||||
fmt.Println(c.string(), "can't find a path")
|
||||
}
|
||||
// After moving, check for an attack
|
||||
if tPos, err := c.easiestAdjacentTarget(); err == nil {
|
||||
c.attack(tPos)
|
||||
}
|
||||
return c.hasEnemies()
|
||||
}
|
||||
|
||||
func (c *Character) string() string {
|
||||
return fmt.Sprintf("[%s%s:%d]", string(c.tp), getCoordString(c.pos), c.health)
|
||||
}
|
||||
|
||||
func (c *Character) attack(p complex64) {
|
||||
fmt.Println(c.string(), "attacks", characters[p].string())
|
||||
characters[p].health -= c.power
|
||||
if characters[p].health <= 0 {
|
||||
c.kills++
|
||||
delete(characters, p)
|
||||
setByte(p, '.')
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Character) easiestAdjacentTarget() (complex64, error) {
|
||||
var wrk *Character
|
||||
if v, ok := characters[c.pos+DIR_N]; ok && v != nil && v.tp != c.tp {
|
||||
wrk = v
|
||||
}
|
||||
if v, ok := characters[c.pos+DIR_W]; ok && v != nil && v.tp != c.tp {
|
||||
if wrk == nil || v.health < wrk.health {
|
||||
wrk = v
|
||||
}
|
||||
}
|
||||
if v, ok := characters[c.pos+DIR_E]; ok && v != nil && v.tp != c.tp {
|
||||
if wrk == nil || v.health < wrk.health {
|
||||
wrk = v
|
||||
}
|
||||
}
|
||||
if v, ok := characters[c.pos+DIR_S]; ok && v != nil && v.tp != c.tp {
|
||||
if wrk == nil || v.health < wrk.health {
|
||||
wrk = v
|
||||
}
|
||||
}
|
||||
if wrk != nil {
|
||||
return wrk.pos, nil
|
||||
}
|
||||
return 0i, errors.New("No adjacent target")
|
||||
}
|
||||
|
||||
func (c *Character) isAdjacentTo(p complex64) bool {
|
||||
return c.pos+DIR_N == p || c.pos+DIR_E == p ||
|
||||
c.pos+DIR_S == p || c.pos+DIR_W == p
|
||||
}
|
||||
|
||||
func (c *Character) getOpenSides() []complex64 {
|
||||
var ret []complex64
|
||||
for _, d := range []complex64{DIR_N, DIR_W, DIR_E, DIR_S} {
|
||||
if getByte(c.pos+d) == '.' {
|
||||
ret = append(ret, c.pos+d)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Character) hasOpenFlank() bool {
|
||||
for _, d := range []complex64{DIR_N, DIR_E, DIR_S, DIR_W} {
|
||||
if getByte(c.pos+d) == '.' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Character) move(dir complex64) bool {
|
||||
if getByte(c.pos+dir) != '.' {
|
||||
return false
|
||||
}
|
||||
delete(characters, c.pos)
|
||||
setByte(c.pos, '.')
|
||||
c.pos += dir
|
||||
characters[c.pos] = c
|
||||
setByte(c.pos, c.tp)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Character) moveTo(pos complex64) bool {
|
||||
if getByte(pos) != '.' {
|
||||
return false
|
||||
}
|
||||
delete(characters, c.pos)
|
||||
setByte(c.pos, '.')
|
||||
c.pos = pos
|
||||
characters[c.pos] = c
|
||||
setByte(c.pos, c.tp)
|
||||
return true
|
||||
}
|
||||
|
||||
func printBattlefield() {
|
||||
//fmt.Print(ClearScreen)
|
||||
for i := 0; i < len(input)/width; i++ {
|
||||
fmt.Println(string(input[i*width : (i+1)*width]))
|
||||
}
|
||||
}
|
||||
|
||||
func setupBattle() {
|
||||
characters = make(map[complex64]*Character)
|
||||
for i := 0; i < len(input); i++ {
|
||||
pos := getPosFromInt(i)
|
||||
bt := getByte(pos)
|
||||
if bt == 'G' || bt == 'E' {
|
||||
characters[pos] = &Character{
|
||||
tp: bt,
|
||||
pos: pos,
|
||||
power: 3,
|
||||
health: 200,
|
||||
}
|
||||
charSlice = append(charSlice, &pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isInMap(pos complex64) bool {
|
||||
idx := int(real(pos)) + int(imag(pos))*width
|
||||
return idx >= 0 && idx < len(input)
|
||||
}
|
||||
|
||||
// getByte pulls a byte from the given position in the input
|
||||
func getByte(pos complex64) byte {
|
||||
return input[int(real(pos))+int(imag(pos))*width]
|
||||
}
|
||||
|
||||
// setByte sets a byte in the input
|
||||
func setByte(pos complex64, b byte) {
|
||||
input[int(real(pos))+int(imag(pos))*width] = b
|
||||
}
|
||||
|
||||
func getPosFromInt(i int) complex64 {
|
||||
return complex(float32(i%width), float32(i/width))
|
||||
}
|
||||
|
||||
func getCoordString(p complex64) string {
|
||||
return fmt.Sprintf("(%d,%d)", int(real(p)), int(imag(p)))
|
||||
}
|
||||
|
||||
func stdinToByteSlice() {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
data := scanner.Bytes()
|
||||
if width == 0 {
|
||||
width = len(data)
|
||||
}
|
||||
|
||||
input = append(input, data...)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the next move for the shortest path from p1 to p2
|
||||
type pathPoint struct {
|
||||
pos complex64
|
||||
count int
|
||||
}
|
||||
|
||||
func findMove(p1 complex64, paths []pathPoint) (complex64, int) {
|
||||
pathCount := len(paths)
|
||||
// First check if p1 has _any_ possible moves
|
||||
lowest := pathPoint{
|
||||
pos: 0 - 1i,
|
||||
count: MaxInt,
|
||||
}
|
||||
for _, v := range paths {
|
||||
for _, d := range []complex64{DIR_N, DIR_E, DIR_S, DIR_W} {
|
||||
wrkPt := pathPoint{
|
||||
pos: v.pos + d,
|
||||
count: v.count + 1,
|
||||
}
|
||||
if !isInMap(wrkPt.pos) {
|
||||
continue
|
||||
}
|
||||
if wrkPt.pos == p1 {
|
||||
if wrkPt.count < lowest.count {
|
||||
lowest.pos = v.pos
|
||||
lowest.count = v.count
|
||||
}
|
||||
}
|
||||
if getByte(wrkPt.pos) != '.' {
|
||||
continue
|
||||
}
|
||||
var skip bool
|
||||
for _, v2 := range paths {
|
||||
if v2.pos == wrkPt.pos && v2.count <= wrkPt.count {
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
paths = append(paths, wrkPt)
|
||||
}
|
||||
}
|
||||
if len(paths) != pathCount && lowest.count == MaxInt {
|
||||
return findMove(p1, paths)
|
||||
}
|
||||
// We hit the end, return the lowest part
|
||||
return lowest.pos, lowest.count
|
||||
}
|
||||
|
||||
// (Manhattan Distance, thanks earlier day)
|
||||
func distance(p1, p2 complex64) int {
|
||||
x1, y1, x2, y2 := real(p1), imag(p1), real(p2), imag(p2)
|
||||
return int(math.Abs(float64(x1)-float64(x2)) + math.Abs(float64(y1)-float64(y2)))
|
||||
}
|
||||
|
||||
type ByPos []*complex64
|
||||
|
||||
func (c ByPos) Len() int { return len(c) }
|
||||
func (c ByPos) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ByPos) Less(i, j int) bool {
|
||||
return imag(*c[i]) < imag(*c[j]) ||
|
||||
(imag(*c[i]) == imag(*c[j]) && real(*c[i]) < real(*c[j]))
|
||||
}
|
32
2018/day15/input
Normal file
32
2018/day15/input
Normal file
@ -0,0 +1,32 @@
|
||||
################################
|
||||
##########################..####
|
||||
#########################...####
|
||||
#########################..#####
|
||||
########################G..#####
|
||||
#####################.#.....##.#
|
||||
#####################..........#
|
||||
##############.#####...........#
|
||||
########G...G#.####............#
|
||||
#######......G....#.....#......#
|
||||
#######...G....GG.#............#
|
||||
#######G.G.............####....#
|
||||
#######.#.....#####....E.....###
|
||||
#######......#######.G.......###
|
||||
#..####..G..#########.###..#####
|
||||
#........G..#########.##########
|
||||
#..#..#G....#########.##########
|
||||
#.###...E...#########.##########
|
||||
#####...G.G.#########.##########
|
||||
########G....#######..##########
|
||||
####..........#####...##########
|
||||
####......E........G..##########
|
||||
#.G..................###########
|
||||
#G...................###########
|
||||
###.....##E.......E..###########
|
||||
###....#............############
|
||||
###.................############
|
||||
##G.....#.............##########
|
||||
###########...#E..##..##########
|
||||
###########.E...###.E.EE.#######
|
||||
###########......#.......#######
|
||||
################################
|
7
2018/day15/testinput1
Normal file
7
2018/day15/testinput1
Normal file
@ -0,0 +1,7 @@
|
||||
#######
|
||||
#G..#E#
|
||||
#E#E.E#
|
||||
#G.##.#
|
||||
#...#E#
|
||||
#...E.#
|
||||
#######
|
7
2018/day15/testinput2
Normal file
7
2018/day15/testinput2
Normal file
@ -0,0 +1,7 @@
|
||||
#######
|
||||
#E..EG#
|
||||
#.#G.E#
|
||||
#E.##E#
|
||||
#G..#.#
|
||||
#..E#.#
|
||||
#######
|
7
2018/day15/testinput3
Normal file
7
2018/day15/testinput3
Normal file
@ -0,0 +1,7 @@
|
||||
#######
|
||||
#E.G#.#
|
||||
#.#G..#
|
||||
#G.#.G#
|
||||
#G..#.#
|
||||
#...E.#
|
||||
#######
|
7
2018/day15/testinput4
Normal file
7
2018/day15/testinput4
Normal file
@ -0,0 +1,7 @@
|
||||
#######
|
||||
#.E...#
|
||||
#.#..G#
|
||||
#.###.#
|
||||
#E#G#G#
|
||||
#...#G#
|
||||
#######
|
9
2018/day15/testinput5
Normal file
9
2018/day15/testinput5
Normal file
@ -0,0 +1,9 @@
|
||||
#########
|
||||
#G......#
|
||||
#.E.#...#
|
||||
#..##..G#
|
||||
#...##..#
|
||||
#...#...#
|
||||
#.G...G.#
|
||||
#.....G.#
|
||||
#########
|
Loading…
Reference in New Issue
Block a user