summary refs log blame commit diff stats
path: root/day23.py
blob: 204ba21dc58dc29dbe07c0fdc3d59ec516fb336a (plain) (tree)





















































































































                                                                                                
#!/usr/bin/env python

goals = {
    'A': 2,
    'B': 4,
    'C': 6,
    'D': 8
}

costs = {
    'A': 1,
    'B': 10,
    'C': 100,
    'D': 1000
}

def reachable(board, pos, dest):
    a, b = min(pos, dest), max(pos, dest)
    for i in range(a, b+1):
        if i == pos or i in goals.values():
            continue
        elif board[i] != '.':
            return False
    return True

def roomContainsGoal(board, piece, dest):
    inRoom = board[dest]
    return len(inRoom) == inRoom.count('.') + inRoom.count(piece)

def getPieceFromRoom(room):
    for c in room:
        if c != '.':
            return c

def possibleMoves(board, pos):
    piece = board[pos]
    if pos not in goals.values():
        if reachable(board, pos, goals[piece]) and roomContainsGoal(board, piece, goals[piece]):
            return [goals[piece]]
        return []

    letter = getPieceFromRoom(piece)
    if pos == goals[letter] and roomContainsGoal(board, letter, pos):
        return []

    possible = []
    for dest in range(len(board)):
        if dest == pos or (dest in goals.values() and goals[letter] != dest):
            continue
        elif goals[letter] == dest and not roomContainsGoal(board,letter,dest):
            continue
        elif reachable(board, pos, dest):
            possible.append(dest)
    return possible

def addToRoom(letter, room):
    room = list(room)
    dist = list(room).count('.')
    assert dist != 0
    room[dist-1] = letter
    return ''.join(room), dist

def move(board, pos, dest):
    new_board = board[:]
    dist = 0
    letter = getPieceFromRoom(board[pos])
    if len(board[pos]) == 1:
        new_board[pos] = '.'
    else:
        new_room = ''
        found = False
        for c in board[pos]:
            if c == '.':
                dist += 1
                new_room += c
            elif not found:
                new_room += '.'
                dist += 1
                found = True
            else:
                new_room += c
        new_board[pos] = new_room
    dist += abs(pos - dest)

    if len(board[dest]) == 1:
        new_board[dest] = letter
        return new_board, dist*costs[letter]
    else:
        new_board[dest], addl_dist = addToRoom(letter, board[dest])
        dist += addl_dist
        return new_board, dist*costs[letter]

def solve(board):
    states = {tuple(board): 0}
    queue = [board]
    while queue:
        board = queue.pop()
        for pos, piece in enumerate(board):
            if getPieceFromRoom(piece) is None:
                continue
            dests = possibleMoves(board, pos)
            for dest in dests:
                new_board, addl_cost = move(board, pos, dest)
                new_cost = states[tuple(board)] + addl_cost
                new_board_tuple = tuple(new_board)
                cost = states.get(new_board_tuple, 9999999)
                if new_cost < cost:
                    states[new_board_tuple] = new_cost
                    queue.append(new_board)
    return states

board = ['.', '.', 'DD', '.', 'AC', '.', 'CB', '.', 'AB', '.', '.']
states = solve(board)
print(states[('.', '.', 'AA', '.', 'BB', '.', 'CC', '.', 'DD', '.', '.')])

board = ['.', '.', 'DDDD', '.', 'ACBC', '.', 'CBAB', '.', 'AACB', '.', '.']
states = solve(board)
print(states[('.', '.', 'AAAA', '.', 'BBBB', '.', 'CCCC', '.', 'DDDD', '.', '.')])