Implement breadth-first search to solve vault
This commit is contained in:
parent
6c4b12435b
commit
b4e9e1295c
6
notes.md
6
notes.md
@ -47,7 +47,7 @@ At the foothills, the first area of the RPG:
|
||||
Required opcodes: All except `halt`
|
||||
|
||||
### Code 5 (Twisty passages)
|
||||
After falling down the bridge, there is an `empty lantern` to the `east`, which should be `take`n. To the `west`, there is a passage, where one can take the `ladder` down or venture into the `darkness`. Attempting to venture into the `darkness` at this point will result in being eaten by Grues.
|
||||
After falling down the bridge, there is an `empty lantern` to the `east`, which should be `take`n. To the `west`, there is a `passage`, where one can take the `ladder` down or venture into the `darkness`. Attempting to venture into the `darkness` at this point will result in being eaten by Grues.
|
||||
|
||||
Taking the `ladder` down, then traversing `west`, `south`, `north`, a code is obtained:
|
||||
|
||||
@ -60,7 +60,7 @@ Taking the `ladder` down, then traversing `west`, `south`, `north`, a code is ob
|
||||
There is also a can, which can be `take`n and `use`d to fill the lantern, which can then be `use`d to become lit, and which will keep away Grues.
|
||||
|
||||
### Code 6 (Dark passage, ruins and teleporter)
|
||||
Returning to the fork at the passage and venturing to the `darkness`, we now `continue` `west`ward to the ruins. The `north` door is locked, but dotted elsewhere around the ruins are a `red coin` (2 dots), `corroded coin` (triangle = 3 sides), `shiny coin` (pentagon = 5 sides), `concave coin` (7 dots) and `blue coin` (9 dots) which should be `take`n and `look`ed at, and the equation:
|
||||
Returning to the fork at the passage and venturing to the `darkness`, we now `continue` `west`ward several times, then `north`ward several times to the ruins. The `north` door is locked, but dotted elsewhere around the ruins are a `red coin` (2 dots), `corroded coin` (triangle = 3 sides), `shiny coin` (pentagon = 5 sides), `concave coin` (7 dots) and `blue coin` (9 dots) which should be `take`n and `look`ed at, and the equation:
|
||||
|
||||
_ + _ * _^2 + _^3 - _ = 399
|
||||
|
||||
@ -248,7 +248,7 @@ I've implemented this as a debug function to prepare the teleporter:
|
||||
### Code 8 (Beach and vault)
|
||||
Arriving at the beach, traverse `north`ward until you reach a fork. To the `east` lies a `journal` containing clues as to the upcoming puzzle.
|
||||
|
||||
With the ability to map the puzzle (possibly with the help of our knowledge of the map data format), and (unlike the adventurers, it seems) a grasp of basic arithmetic, this puzzle shouldn't be too difficult to solve. I couldn't do better than [paiv](https://github.com/paiv)'s [solution](https://paiv.github.io/blog/2016/04/24/synacor-challenge.html) ([code](https://github.com/paiv/synacor-challenge/tree/master/code/src/vault), [map](https://github.com/paiv/synacor-challenge/blob/master/notes/vault-locks.svg)), and it doesn't look like there's much parallelisation to do, so I'll leave you with those links.
|
||||
With the ability to map the puzzle (possibly with the help of our knowledge of the map data format), and (unlike the adventurers, it seems) a grasp of basic arithmetic, this puzzle shouldn't be too difficult to solve. Armed with our map data, we can now do a [breadth-first search to find the shortest solution](https://github.com/RunasSudo/synacor.py/blob/master/tools/bfs.py).
|
||||
|
||||
The solution is:
|
||||
|
||||
|
155
tools/bfs.py
Executable file
155
tools/bfs.py
Executable file
@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python3
|
||||
# synacor.py - An implementation of the Synacor Challenge
|
||||
# Copyright © 2017 RunasSudo
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import collections
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
rooms = {}
|
||||
|
||||
class Door:
|
||||
def __init__(self):
|
||||
self.name = None
|
||||
self.destination = None
|
||||
|
||||
class Room:
|
||||
def __init__(self):
|
||||
self.location = None
|
||||
self.name = None
|
||||
self.description = None
|
||||
self.doors = []
|
||||
self.callback = None
|
||||
|
||||
self.is_operator = False
|
||||
self.mutate_value = None
|
||||
|
||||
# Read code into memory
|
||||
SYN_MEM = [0] * 32768
|
||||
|
||||
with open('../dumps/init.raw', 'rb') as data:
|
||||
i = 0
|
||||
while True:
|
||||
byteData = data.read(2)
|
||||
if len(byteData) < 2:
|
||||
break
|
||||
SYN_MEM[i] = struct.unpack('<H', byteData)[0]
|
||||
i += 1
|
||||
|
||||
def traverse_room(location):
|
||||
if location in rooms:
|
||||
return
|
||||
|
||||
# Read data from initial struct
|
||||
ptr_name = SYN_MEM[location]
|
||||
ptr_description = SYN_MEM[location + 1]
|
||||
ptr_door_names = SYN_MEM[location + 2]
|
||||
ptr_door_dests = SYN_MEM[location + 3]
|
||||
ptr_callback = SYN_MEM[location + 4]
|
||||
|
||||
room = Room()
|
||||
room.location = location
|
||||
|
||||
# Name
|
||||
len_name = SYN_MEM[ptr_name]
|
||||
room.name = bytes(SYN_MEM[ptr_name+1:ptr_name+1+len_name]).decode('ascii')
|
||||
|
||||
if room.name.startswith('Vault '):
|
||||
rooms[location] = room
|
||||
else:
|
||||
return
|
||||
|
||||
# Description
|
||||
len_description = SYN_MEM[ptr_description]
|
||||
room.description = bytes(SYN_MEM[ptr_description+1:ptr_description+1+len_description]).decode('ascii')
|
||||
|
||||
# Callback
|
||||
room.callback = ptr_callback
|
||||
|
||||
# Door names
|
||||
door_name_ptrs = []
|
||||
len_door_name_ptrs = SYN_MEM[ptr_door_names]
|
||||
door_name_ptrs = SYN_MEM[ptr_door_names+1:ptr_door_names+1+len_door_name_ptrs]
|
||||
|
||||
# Door dests
|
||||
door_dests = []
|
||||
len_door_dest_ptrs = SYN_MEM[ptr_door_dests]
|
||||
door_dest_ptrs = SYN_MEM[ptr_door_dests+1:ptr_door_dests+1+len_door_dest_ptrs]
|
||||
|
||||
assert len(door_name_ptrs) == len(door_dest_ptrs)
|
||||
|
||||
# Process doors
|
||||
for i in range(len(door_name_ptrs)):
|
||||
door = Door()
|
||||
|
||||
len_door_name = SYN_MEM[door_name_ptrs[i]]
|
||||
door.name = bytes(SYN_MEM[door_name_ptrs[i]+1:door_name_ptrs[i]+1+len_door_name]).decode('ascii')
|
||||
|
||||
door.destination = door_dest_ptrs[i]
|
||||
|
||||
room.doors.append(door)
|
||||
|
||||
# Parse description
|
||||
if room.name == 'Vault Antechamber':
|
||||
room.mutate_value = lambda val: '22'
|
||||
else:
|
||||
match = re.search(r"mosaic depicting the number '(.*)'", room.description)
|
||||
if match:
|
||||
room_value = match.group(1)
|
||||
room.mutate_value = lambda val: val + room_value + ')'
|
||||
else:
|
||||
match = re.search(r"mosaic depicting a '(.*)' symbol", room.description)
|
||||
if match:
|
||||
room.is_operator = True
|
||||
room_operator = match.group(1)
|
||||
room.mutate_value = lambda val: '(' + val + room_operator
|
||||
else:
|
||||
raise Exception('Unable to parse description: {}'.format(room.description))
|
||||
|
||||
for door in room.doors:
|
||||
traverse_room(door.destination)
|
||||
|
||||
# Seed rooms
|
||||
traverse_room(0x0a3f)
|
||||
|
||||
# Breadth-first search
|
||||
|
||||
queue = collections.deque()
|
||||
initial_status = ([(None, 0x0a3f)], None) # current path, previous value
|
||||
queue.append(initial_status)
|
||||
|
||||
while True:
|
||||
current_status = queue.popleft()
|
||||
current_room = current_status[0][-1][1]
|
||||
current_value = rooms[current_room].mutate_value(current_status[1])
|
||||
|
||||
print(current_status)
|
||||
print(current_value)
|
||||
|
||||
if current_room == 0x0a12: # Vault Door
|
||||
if eval(current_value) == 30:
|
||||
# We have reached the goal
|
||||
sys.exit(0)
|
||||
else:
|
||||
# We must not enter the vault door unless we have the right number, else the orb will disappear
|
||||
# Do not continue this line of searching
|
||||
continue
|
||||
|
||||
for edge in rooms[current_room].doors:
|
||||
if edge.destination in rooms:
|
||||
new_status = (current_status[0] + [(edge.name, edge.destination)], current_value)
|
||||
queue.append(new_status)
|
@ -168,7 +168,7 @@ def traverse_item(location):
|
||||
# Seed rooms
|
||||
traverse_room(0x090d) # Foothills
|
||||
traverse_room(0x09b8) # Synacor Headquarters
|
||||
traverse_room(0x09cc) # Beachside
|
||||
traverse_room(0x09c2) # Beachside
|
||||
|
||||
# Exhaustive check
|
||||
for i in range(0x090d, 0x099e, 5):
|
||||
|
Reference in New Issue
Block a user