After spending a bit over three months at LShift, I am proud to leave LShift’s mark in the Minecraft Universe.
Frolicking over Minecraft’s cubic pastures and passing by interesting arrangements of hovering dirt blocks suspended in mid-air is all in a Minecrafter’s day’s work. But if you ever see light-blue wool blocks hanging around in the air, you can be sure that someone’s been . . . Shifty . . .
The ones you see in the picture above, in fact, have been put into the Minecraft world by a tool I wrote in Haskell. In this multi-part series, I want to share with you how I did it.
An Executive Summary
What does it take to carry an idea from conception to realisation in Haskell?
And here are the requirements I worked with. The task is to write a Haskell program that takes
- a path to a PNG image
- path to a Minecraft saved world
and then it
- quantises the pixel data fron the PNG image into Minecraft coloured wool blocks (to a palette of a dazzling 16 colours!!)
- inserts these coloured wool blocks into the world at a height 5 blocks above the player’s head
Simple? Not quite. As with any software project even one as small as this the devil is in the details. The Minecraft saved game format turns out to have an interesting structure involving offsets, some compression and even some odd (even some odd? :/) ‘nybble endianness’ in places.
In case you are wondering, there are already many really good libraries for editing and manipulating Minecraft saved worlds (and no, I’m not talking about the diamond axe), so the concept of my tool is nothing new.
To use those tools directly for my aims presents little challenge. Being a bit of a Haskell enthusiast, this project presented itself as a fun way to try Haskell out for size on problems resembling those you see in the real-world, such as dealing with and manipulating binary data.
The Road Ahead
Throughout the series, I aim to uncover details as we require them, just as I did while I explored the problem space. At a glance:
- Haskell stubbing for fun and profit: We (pretend!) to practise test-driven development by making the success of reading and writing Minecraft saved world files (called Region files) a test property
- Dealing with compression using Functors and Phantom Types: We design the main data types for maximum programmer comfort, paving the road for operations on arrays of block data.
- Serious Binary Serialisation: We implement code to read and write Region files using the Get and Put monads from Data.Binary, and ensure a roundtrip succeeds
- Update your Chunks everywhere using SYB and SYZ: Time to change the world! We write code to perform somewhat troublesome chunk updates. We also extract the player’s current coordinates in the world from Level data
- Fun turning Pixel Data into Wool using Codec.DevIL: We read in image data using Codec.DevIL, write a simple quantisation function, and build the final executable
I must admit I didn’t actually practise TDD, but the way I went about it actually more in line with the strong-typing mantra:
If it compiles, your code’s probably doing something useful.
Though whenever I found that my code didn’t work immediately after making it compile, the fall-back is simply to write some tests to help debug the problem.
And that, dear reader, is the principal matter of the next post. I hope you’ll enjoy it!