Things are getting tricky here now on Day 9, though not excessively hard. Just hard-ish. Today’s puzzle involves rope simulations. The data set consists of movement for the head of the rope and the end of the rope (and subsequent segments) follows rules that essentially keep the tail near the head. For example, if the head moves right 4 spaces, the tail follows, moving right 4 spaces. The tally was how many locations on a grid did the tail visit at least once.

The movement part was mostly simple, first create a function to move the head around. It wasn’t necessary, but I saved the head coordinates into a list. It was useful for verifying my code against the sample data. Moving the head was easy. Moving the tail was trickier. My movement function evolved quite a bit, it was originally quite a few messy if statements. But as i worked through the problem, it simplified down more and more until it was one movement function, called for the x or y if needed.

The diagonal movement was tricky, though, until I realized it was easiest to just, always align the opposing x or y coordinate. Let me try to clear up what I am saying. Straight Left, Right, Up, Down, movement is easy. When the head is opposite of the tail diagonally, by 1 each way, the tail won’t move. But if the head moves again, and now it’s 2 away along say, the Up direction, in addition to the tail moving up one, it also needs to move over left or right to align with the head.

To solve this, it just, does the alignment every time. In regular movement, this doesn’t do anything, since it’s already aligned, if it’s a diagonal movement, it doesn’t it’s job.

Each time the tail moves, it’s coordinates are also dropped into an array to track it’s movements. The tally needed only wants unique coordinates, so keeping track of where it’s been allows for easily checking for new coordinates and incrementing the counter.

Part 2 mixed things up a bit in a predictable way, multiple segments, before the tail. Same rules for movement. The code below could be simplified down to combine part 1 and 2, but I opted to just copy and paste for simplicity. The base loop is the same, though I removed the unneeded head tracking list. The main difference is that instead of a head and tail position, it gets fed a list of coordinates, for each segment, that it loops through every move of the head.

And it got close. Super close. Three away close!

the code worked for the sample given with an answer of 36. It worked for my original 1 segment rope, if i cut the starting array down to 2 coordinates. But not for the fill puzzle input.

I could see the problem, but not quite the solution. The issue was that sometimes, it was going to need to move diagonally. And it was fixed by adding a simple extra if statement.

if abs(hpos[0]-xpos) > 1 and abs(hpos[1]-ypos) > 1:
    xpos = move_pos(hpos[0], xpos)
    ypos = move_pos(hpos[1], ypos)

If BOTH coordinates are large, move them both, instead of one or the other. And the correct answer was revealed!

with open("Day09Input.txt") as file:
    data = file.read()

moves = data.split('\n')

positions = [[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]]
head_pos = [0,0]
tail_pos = [0,0]

head_moves = [head_pos]
tail_moves = [tail_pos]
long_tail_moves = [positions[len(positions)-1]]

tail_counter = 1
long_tail_counter = 1

def move_pos(a,b):
    if a - b > 0:
        return b+1
    if a - b < 0:
        return b-1
    return b

def move_head(dir,hpos):
    if dir == "R":
        return [hpos[0]+1,hpos[1]]
    if dir == "L":
        return [hpos[0]-1,hpos[1]]
    if dir == "U":
        return [hpos[0],hpos[1]+1]
    if dir == "D":
        return [hpos[0],hpos[1]-1]
    return pos

def move_tail(hpos,tpos):
    xpos = tpos[0]
    ypos = tpos[1]
    if abs(hpos[0]-xpos) > 1 and abs(hpos[1]-ypos) > 1:
        xpos = move_pos(hpos[0], xpos)
        ypos = move_pos(hpos[1], ypos)
    if abs(hpos[0]-xpos) > 1:
        ypos = hpos[1]
        xpos = move_pos(hpos[0], xpos)
    if abs(hpos[1]-ypos) > 1:
        xpos = hpos[0]
        ypos = move_pos(hpos[1], ypos)
    return [xpos, ypos]

# Part 1
for move in moves:
    turn = move.split(" ")
    for i in range(int(turn[1])):
        head_pos = move_head(turn[0], head_pos)
        head_moves.append(head_pos)
        tail_pos = move_tail(head_pos, tail_pos)
        if tail_pos not in tail_moves:
            tail_counter += 1
        tail_moves.append(tail_pos)

# Part 2
for move in moves:
    turn = move.split(" ")
    for i in range(int(turn[1])):
        positions[0] = move_head(turn[0], positions[0])
        for i in range(1,len(positions)):
            positions[i] = move_tail(positions[i-1], positions[i])
        if positions[len(positions)-1] not in long_tail_moves:
            long_tail_counter += 1
        long_tail_moves.append(positions[len(positions)-1])



# print(head_moves)
# print(tail_moves)
print(tail_counter)
#5902

#print(long_tail_moves)
print(long_tail_counter)
# high 11271
# high 2448
# low 2395

## 2445