Tuesday, May 3, 2011
Metaprogramming in Piet
Quite a while ago I had the idea to implement a BrainF**k interpreter in Piet. I got halfway through a prototype before it got too complicated and I lost interest in the project for a while. Fast-Forward a few months and I find that it has now been done, no less than three times. I thought it would be a really cool way to show off the power of the Piet language. Seeing as that has now been done, I need to up my ante a bit.
What other crazy, esoteric language could I implement? What better choice than Piet itself? I can't think of one. I realized that npiet, the standard Piet interpreter, can handle ppm files with absolutely no problem. That essentially allows me to interact with images as strings of text, without having to muck about with decoding and encoding difficult image formats (although that is technically possible). In order to get to the point of a Piet interpreter written in Piet I need some more tools first, so I don't rip my brains out. Hopefully I can reach that lofty goal someday.
One discovery that helped me a lot was this blog post about QuickPiet. It is a language to create and debug algorithms on the Piet stack without having to worry about the spatial complications of actually laying out an image. I made a quick Piet interpreter, and made a few additions of my own, like modifying the way the goto command works. It is definitely possible to programatically transform a QuickPiet program into a working image, but I found the result looks mechanical, and is generally less artsy and less fun.
I decided a first step in exploring Piet meta-programming would be to try and write a Piet program that generates another Piet program. I decided to make a program that will accept any string as input, and output a Piet program that will also output that string. Rather worthless, I know, but valuable as a proof that it is possible.
So I coded it up in quick piet until I was fairly confident that it worked. The good thing about quick piet is that is removes all of the space and layout constraints that the image requires, and lets you just worry about the algorithms. So I had about 600 lines of quick piet that appears to work. I wrote a normalization engine to separate it into distinct labels, make all implicit jumps explicit, and generally clean it up. I took the normalized output, and a graph of which blocks lead to which other blocks, and I proceeded to lay it all out by hand (starting over twice when I got waay too lost to continue), and finally I got this little masterpiece:
That program will read all input until it receives a two quotation marks. It will then generate a valid Piet program (as a PPm image) that will in turn output all of the text between the quotation marks. So if I give it the input "Hello, World!", It will give me the following as a result:
Which will of course output "Hello, World!".
It seems to be pretty highly scalable, although a bit slow. For instance, here is a program that generates the declaration of independence:
And here is a real gem. This one outputs a piet program that will in turn output "Hello, World". I would try and wrap it one more level deeper, but I'm afraid the universe might explode if I do so. The image size is something like 55000x116, so it doesn't render very well in a blog. If you want to download it it is here.
Saturday, August 14, 2010
Monkey IslandTM Optimality Part 3
Last week I quite easily found an optimal path through the first part of Monkey IslandTM 2. Moving on the second episode, I ran into difficulties.
First off, the map is much expanded. I now have three islands available to me, so the search space has gotten much larger. The map, with my (somewhat arbitrary) travel costs now looks like this:
Furthermore, there are a ton more objectives:
My hopes of an easy solution were dashed when I looked at this last graph and found a chain that required six island changes to get through the graph. Hopefully we can stick fairly close to that.
I entered all of the objectives into my system as quickly as I could and hit some problems:
- It ran slow.
- It ran reaaaallly slow.
The first act ran in a matter of seconds, but the second one, with the increased complexity seemed to take forever. I got lazy in implementing the search. To choose a node to explore I was resorting the list of possibilities on every iteration. This caused the search to slow down the further it got into it, and once there were about 16,000 nodes under consideration it pretty much stopped cold.
Once I swapped out my crappy list for a proper priority queue, I got much better search speed, but still was far from a solution. After 15 minutes of running and almost 7 million nodes considered, I got an OutOfMemory exception, because I was storing a list of nodes I had already considered to avoid repetition. I got to implement one of the cooler data structures I know of (the Directed Acyclic Word Graph, and finally I received a solution.
This is guaranteed to be optimal if my map weights make any sense, and if I did not overly constrain the search by the way I entered in the objectives. To the best of my knowledge this is a fairly good path. I will let you know how it goes once I play through it completely.
Here it is:
- Get Parrot Chow
- Go to BoatPhatt
- Go to Wharf
- Get Arrested(Automatic)
- Pull Mattress
- Get Stick
- Get Bone
- Get Key
- Unlock Cell
- Get Gorilla Envelope
- Get Manilla Envelope
- Open Gorilla
- Open Manilla
- Go to Wharf
- Go to RouletteAlley
- Watch Roulette
- Go to Wharf
- Follow dude
- Open Peephole
- Get winning Number
- Go to Wharf
- Go to RouletteAlley
- Win Cash
- Go to Wharf
- Go to SecretAlley
- Get Number
- Go to Wharf
- Go to RouletteAlley
- Win Invitation
- Go to Wharf
- Go to Library
- Get Lens
- Remember Hex Book(r-recipes)
- Remember Shipwrecks(d-disasters)
- Check Out Books
- Go to Wharf
- Go to BoatPhatt
- Go to BoatBooty
- Go to Ville
- Get leaflet from Kate
- Go to CostumeShop
- Get Costume
- Go to Ville
- Go to Shop
- Buy Saw
- Buy Horn
- Buy Sign
- Talk to pawn owner about trades
- Use chow on hook
- Get Mirror
- Go to Ville
- Go to MarleyMansion
- Present Costume and Invitation
- Go to MarleyBack
- Push Trash Can
- Run around to kitchen
- Get Fish
- Run around to front
- Get Map Piece
- Go outside
- Sweet Talk Elaine
- Go to MarleyLounge
- Go to MarleyMansion
- Chase Map Piece
- Get Dog
- Go to MarleyLounge
- Go to MarleyUpstairs
- Get Oar
- Go to MarleyLounge
- Go to MarleyMansion
- Go to BigTree
- Attempt to climb tree
- Get broken oar
- Go to BoatBooty
- Go to BoatScabb
- Go to Bridge
- Go to carpenter
- Give oar to carpenter
- Go to Bar
- Use banana on metronome
- Get blue and yellow drinks
- Pick up monkey
- Go to Wally
- Give lens to wally
- Go to Cleaners
- Saw Pegleg
- Go to Carpenter
- Get Hammer
- Get Nails
- Go to BoatScabb
- Go to BoatPhatt
- Go to Wharf
- Put leaflet on wanted poster
- Get near-grog from envelope
- Free Kate
- Go to Wharf
- Go to Pier
- Challenge Fisherman
- Give fish and Win
- Go to Wharf
- Go to BoatPhatt
- Go to BoatBooty
- Go to Ville
- Go to Spit
- Switch Flags
- Win Spitting Contest
- Go to Ville
- Go to Shop
- Trade plaque
- Go to Ville
- Read coordinates for mad monkey
- Charter ship and get masthead
- Go to Stans
- Nail Stan in coffin
- Get Crypt key
- Go to Ville
- Go to Shop
- Trade masthead for map
- Go to Ville
- Go to Cliff
- Fish Up Map
- Go to Ville
- Go to BigTree
- Climb Tree
- Get Telescope
- Use Dog to find map
- Go to BoatBooty
- Go to BoatPhatt
- Go to Mansion
- Go to MansionLobby
- Trick Guard
- Go to GovernersRoom
- Swap Books
- Go to MansionLobby
- Go to Mansion
- Go to Waterfall
- Go to WaterfallTop
- Use monkey wrench on valve
- Go to Waterfall
- Go to Cottage
- Cheat at drinking contest
- Open Window
- Put mirror in frame
- Put telescope in statue
- Push correct brick
- Get Map Piece
- Go to Wharf
- Go to BoatPhatt
- Go to BoatScabb
- Go to Cemetery
- Use Key in Crypt
- Go to Crypt
- Read Pirate quotes
- Open Coffin
- Get Ashes
- Go to Cemetery
- Go to Swamp
- Go to VoodooShack
- Pick up ash2Life Jar
- Talk to voodooLady and give book
- Go to Swamp
- Go to Cemetery
- Go to Crypt
- Use ash2Life on ashes
- Go to Cemetery
- Go to Beach
- Go to WeenieHut
- Use key at weenie hut
- Turn Off Gas
- Go to Beach
- Go to Cemetery
- Go to Crypt
- Revive rapp and get mapp
- Go to Cemetery
- Go to Bridge
- Go to Wally
- Give 4 pieces to Wally
This path has 7 island changes in it, which is not too shabby. The "speed guide" I used to construct my objectives graph changed islands 8 times, so I may be on to something. I completed this part of the quest in only 46 minutes, bringing my total to this point up to just 58 minutes.
Saturday, August 7, 2010
Optimal According to What?
Last time, I introduced the problem of finding an optimal path through Monkey IslandTM 2. If there's one thing I learned in College, it is that "optimal" can mean many things, and it is often best to specify specifically what you wish to optimize. In this case I wish to minimize the time spent traveling from screen to screen. I especially don't want to go to the larger map screens very often, and I most definitely do not want to move between islands any more than is absolutely necessary.
In order to more precisely define what I wish to optimize, consider the following simple score system:
- It costs one point to complete any objective.
- To travel from one location to another adjacent location costs some number of points greater than or equal to one.
The first thing I needed is a discretization of the world to give values to the penalties you accrue for traveling. I painstakingly mapped out the world, and applied some serious guestimating to come up with this graph for the first act's locations:
This is not perfect by any means, but I feel it is a good approximation of the required times to move from place to place. It at least gives us some values to use while performing our search. Tweaking the criteria could potentially help us finish faster, but we'll just assume this is good enough for now.
The next thing I need to solve this problem is a list of the required objectives. This was created painstakingly by consulting a walkthrough and adding each step as an objective, making careful notes of all prerequisite objectives, and the locations where they occur. Once I have all this information I am ready to make a search.
I chose to implement a simple A* search because it is good for this job, and it has code readily available on wikipedia.
A search state can be identified as a location and a list of objectives that have already been completed. The cost function is calculated as described above (travel costs + objective costs), and the heuristic function is simply the number of uncompleted objectives. On each step I can either complete all available objectives at my current location, or if there are no available objectives, move to an adjacent location to my current one.
Using this method I am guaranteed optimality (according to my cost function above), and I get a fairly nice path:
- Get shovel
- Go to BarKitchen
- Get knife
- Go to Wally
- Get monocle
- Get paper
- Go to Cleaners
- Get bucket
- Go to Bar
- Talk to bartender about business
- Get spit with paper
- Go to Bridge
- Go to Beach
- Get stick
- Go to Swamp
- Get swampy water in bucket
- Go to Cemetery
- Go to CemeteryHill
- Dig up grave
- Go to Cemetery
- Go to Bridge
- Go to Hotel
- Use knife on rope
- Get cheese squiggles
- Go to LargosRoom
- Get Toupee
- Rig door with bucket of mud
- Go to Hotel
- Go to Cleaners
- Watch largo at dry cleaner
- Go to Hotel
- Go to LargosRoom
- Get Laundy ticket
- Go to Hotel
- Go to Cleaners
- Get Largo's Laundry
- Go to Bridge
- Go to Swamp
- Go to VoodooShack
- Get string
- Talk to voodoo lady about doll
- Give spit
- Give clothes
- Give toupee
- Give bone
- Go to Swamp
- Go to Bridge
- Go to Cleaners
- Open box
- Use stick and string with box
- Put cheese squiggle in box
- Pull string (wait for rat)
- Open box get rat
- Go to BarKitchen
- Put rat in soup
- Go to Bar
- Get a job (order stew)
- Go to Hotel
- Go to LargosRoom
- Stab largo
- Go to Swamp
- Go to BoatScabb
- Give Monocle and charter ship
This was calculated in around 15 seconds on a non-special computer, and it was completed in just over 13 minutes, with distractions. I can't see any way to improve on this path.
The next step will be to put in all the objectives for the next act (which has a considerably more complicated objectives graph) and see how it does. Wish me luck.
Friday, August 6, 2010
Optimal Path for Monkey IslandTM
You may or may not be aware that LucasArts has recently re-released the first two episodes in the Monkey IslandTM series. The special editions are complete renovations of the originals, with new graphics and voice-overs, but are otherwise identical. You can even switch back and forth between the original graphics and the new ones at any time with a single key-press, and it is absolutely seamless.
If you have enjoyed the series in the past, you would definitely enjoy the refreshed version. The graphics are smooth, and the experience is like what you remember. If you have never played, or heard of Monkey IslandTM, then shame on you. Get the special edition and play through it with the old graphics first.
One new thing that was added to Monkey IslandTM 2 was a set of Steam achievements. One of the harder ones to get is to complete the game in under three hours. I did not make it really even close on my first play-through, because I was trying to enjoy the game. Now, though, it's business time. This game is really quite large. There is a good deal of sailing from island to island, and the nerdmonger in me is constantly asking me to figure out a way to minimize all that.
Sure, I could easily do it all from memory in under 3 hours now, but that's no fun. I have enough deep cs background that I should be able to solve a simple problem like this fairly easily, shouldn't I? Is it harder than I thought? Its just a traveling salesman problem, isn't it?
Here's a teaser - A graph of all necessary objectives to complete Chapter One: The Largo Embargo:
Locations are not shown there, but I am trying to complete all objectives while minimizing time spent walking.
Stay tuned for the thrilling continuation.





