★Advent of Code 202225 days solved.Source ↗
“Digressing, will come back with visualization.”Git commit.
from helpers import load_data_grouped
from visualize import pc

MOVEMENT_MASKS = {
	'U': 0 + 1j,
	'D': 0 - 1j,
	'L': -1 + 0j,
	'R': 1 + 0j
}

SIDES = {
	0: (50, 99, 150, 199),
	1: (100, 149, 150, 199),
	2: (50, 99, 100, 149),
	3: (50, 99, 50, 99),
	4: (0, 49, 50, 99),
	5: (0, 49, 0, 49)
}

SIDE_LENGTH = 50

def orient_right(relative_coord, x_coord):
	return complex(x_coord % SIDE_LENGTH, SIDE_LENGTH - relative_coord.real - 1)

def orient_left(relative_coord, y_coord):
	return complex(SIDE_LENGTH - relative_coord.imag - 1, y_coord % SIDE_LENGTH)

def orient_flip(relative_coord, x_coord):
	return complex(x_coord % SIDE_LENGTH, SIDE_LENGTH - relative_coord.imag - 1)

def translate_1_to_5(relative_coord, garbage):
	return complex(relative_coord.real % SIDE_LENGTH, 0)

def translate_5_to_1(relative_coord, garbage):
	return complex(relative_coord.real, SIDE_LENGTH - 1)

# if we go from side k to side j, find out what transition we need to make
transition_matrix = {
	# TODO could use partial function application here :)
	(0, 4): (orient_flip, 2, SIDES[4][0]), # flip orientation, increment direction index twice, use side 4 x_min
	(0, 5): (orient_right, 1, SIDES[5][0]),

	(1, 2): (orient_right, 1, SIDES[2][1]),
	(1, 3): (orient_flip, 2, SIDES[3][1]),

	(2, 1): (orient_left, -1, SIDES[1][2]),
	(2, 4): (orient_left, -1, SIDES[4][3]),

	(3, 1): (orient_flip, 2, SIDES[1][1]),
	(3, 5): (orient_right, 1, SIDES[5][1]),

	(4, 0): (orient_flip, 2, SIDES[0][0]),
	(4, 2): (orient_right, 1, SIDES[2][0]),

	(5, 0): (orient_left, -1, SIDES[0][3]),
	(5, 3): (orient_left, -1, SIDES[3][2]),

	# special cases?
	(1, 5): (translate_1_to_5, 0, None),
	(5, 1): (translate_5_to_1, 0, None)
}

# keep track of sides you transition to going out of current square left, up, right, or down
SIDE_TO_SIDE = {
	0: [4, 5, 1, 2],
	1: [0, 5, 3, 2],
	2: [4, 0, 1, 3],
	3: [4, 2, 1, 5],
	4: [0, 2, 3, 5],
	5: [0, 4, 3, 1],
}

def compute_destination_side(candidate_relative_coord, current_side):
	print(f'candidate rel: {candidate_relative_coord} : {current_side}')
	if candidate_relative_coord.real < 0:
		return SIDE_TO_SIDE[current_side][0]
	elif candidate_relative_coord.real == SIDE_LENGTH:
		return SIDE_TO_SIDE[current_side][2]
	elif candidate_relative_coord.imag < 0:
		return SIDE_TO_SIDE[current_side][3]
	elif candidate_relative_coord.imag == SIDE_LENGTH:
		return SIDE_TO_SIDE[current_side][1]
	else:
		return current_side

# TODO could cache this computation
def compute_limits(board, x, y):
	row_keys = {key for key in board.keys() if key.imag == y}
	col_keys = {key for key in board.keys() if key.real == x}

	min_x = int(min(row_keys, key=lambda x: x.real).real)
	max_x = int(max(row_keys, key=lambda x: x.real).real)
	min_y = int(min(col_keys, key=lambda x: x.imag).imag)
	max_y = int(max(col_keys, key=lambda x: x.imag).imag)

	return min_x, max_x, min_y, max_y

def get_new_rel_location(abs_coord, side):
	return complex(abs_coord.real - SIDES[side][0], abs_coord.imag - SIDES[side][2],)

def can_move_again(board, direction, current_abs_location, current_side, relative_location, direction_index):
	
	print(f'current abs: {current_abs_location}')
	print(f'direction: {direction}')
	print(f'current side: {current_side}')
	print(f'relative loc: {relative_location}')
	print(f'direction index: {direction_index}')

	test_relative_location = relative_location + MOVEMENT_MASKS[direction]

	test_destination_side = compute_destination_side(test_relative_location, current_side)
	print(f'test destination side: {test_destination_side}')

	print(f'tuple: {(current_side, test_destination_side)}')

	if (current_side, test_destination_side) not in transition_matrix:
		# no transformation needed
		direction_increment = 0
		test_transformed_relative_location = get_new_rel_location(current_abs_location + MOVEMENT_MASKS[direction], test_destination_side)

		print(f'Going from/to {(current_side, test_destination_side)} without transformation')
	else:
		print(f'Going from/to {(current_side, test_destination_side)}')
		orient_func, direction_increment, orient_arg_1 = transition_matrix[(current_side, test_destination_side)]
		print(f'orient func: {orient_func}')
		print(f'direction_increment: {direction_increment}')
		print(f'orient arg 1: {orient_arg_1}')
		test_transformed_relative_location = orient_func(test_relative_location, orient_arg_1)

	test_abs_location = complex(SIDES[test_destination_side][0], SIDES[test_destination_side][2])
	test_abs_location = test_abs_location + test_transformed_relative_location

	print(test_abs_location)
	print('-----')

	if board[test_abs_location] == '.':
		return test_abs_location, test_transformed_relative_location, direction_index + direction_increment, test_destination_side
	elif board[test_abs_location] == '#':
		return current_abs_location, relative_location, direction_index, current_side
	else:
		raise Exception('no bueno')

def in_range(complex_point, limits):
	result = True
	result = result and complex_point.real >= limits[0]
	result = result and complex_point.real <= limits[1]
	result = result and complex_point.imag >= limits[2]
	result = result and complex_point.imag <= limits[3]
	return result

def print_board(x_board, current_location, arrow, side_number):
	min_x = int(min(x_board.keys(), key=lambda x: x.real).real)
	max_x = int(max(x_board.keys(), key=lambda x: x.real).real)
	min_y = int(min(x_board.keys(), key=lambda x: x.imag).imag)
	max_y = int(max(x_board.keys(), key=lambda x: x.imag).imag)

	# # view window of y's
	# min_y = max(min_y, int(current_location.imag) - 20)
	# max_y = min(max_y, int(current_location.imag) + 20)

	print(f'current location while printing board: {current_location}')
	for y in reversed(range(min_y, max_y + 1)):
		print(f'{(max_y - y + 1):03} {y:03}', end='')
		for x in range(min_x - 1, max_x + 1):
			if complex(x, y) == current_location:
				pc(arrow, 'green', end='')
			elif in_range(complex(x, y), SIDES[side_number]):
				pc(x_board[complex(x, y)], 'blue', end='')
			elif complex(x, y) in x_board.keys():
				pc(x_board[complex(x, y)], 'yellow', end='')
			else:
				print(' ', end='')
		print()


if __name__ == "__main__":
	data, instructions = load_data_grouped("day-22-input.txt")
	instructions = instructions[0]

	# TODO can we re-use common "board looking input" logic?
	row_count = len(data)
	col_count = len(data[0])
	board = {}
	for y in range(row_count):
		data_y_index = row_count - 1 - y
		for x in range(len(data[data_y_index])):
			if data[data_y_index][x] != ' ':
				board[complex(x, y)] = data[data_y_index][x]

	# parse instructions in to ['R42', 'L43', ...]
	results = ['R' + instructions[:2]]
	instructions = instructions[2:]
	previous_index = 0
	for i, v in enumerate(instructions[1:]):
		if not v.isdigit():
			results.append(instructions[previous_index:i + 1])
			previous_index = i + 1
	results.append(instructions[previous_index:])

	# TODO move into function (used in compute... function above)
	y_max = max(board.keys(), key=lambda x: x.imag).imag
	x_min = min({x for x in board.keys() if x.imag == y_max}, key=lambda x: x.real).real
	current_abs_location = complex(x_min, y_max)
	print(current_abs_location)

	# TODO also maintain relative position
	current_side = 0
	relative_location = complex(0, SIDE_LENGTH - 1)

	display_board = board.copy()


	direction_index = -1 # initialize to U since first instruction is to turn to R
	directions = ['R', 'D', 'L', 'U']
	arrows = ['>', 'V', '<', '^']
	for counter, instruction in enumerate(results):

		temp = list(instruction)
		direction = temp[0]
		if direction == 'R':
			direction_index += 1
		else:
			direction_index -= 1
		steps = int(''.join(temp[1:]))

		for i in range(steps):
			display_board[current_abs_location] = arrows[direction_index % 4]

			# TODO may need to get back the new direction index and current side
			new_abs_position, new_rel_position, new_direction_index, new_side = can_move_again(board, directions[direction_index % 4], current_abs_location, current_side, relative_location, direction_index)
			if new_abs_position == current_abs_location:
				break
			else:
				current_abs_location = new_abs_position
				relative_location = new_rel_position
				direction_index = new_direction_index
				current_side = new_side
			
		# print_board(display_board, current_abs_location, arrows[direction_index % 4], current_side)

		# if counter == 600:
		# 	break


	# TODO use the nice printing function for an animation
	# TODO make solution generic so both part 1 and part 2 can be solved
	
	# part 1 (answer: 165094)
	# part 2 (answer: 95316)
	print(int(1000 * (row_count - (current_abs_location.imag)) + 4 * (current_abs_location.real + 1) + (direction_index % 4)))



	

No writeup for this day yet.

✎ worked out on paper
Handwritten notes for day 22
Every cube face, every rotation, every edge crossing, worked out on paper before any code was written.