F#, Minecraft and a Raspberry Pi

MerryChristmasFromFSharp

This post is part of the F# Advent Calendar in English 2015 check out the other posts…

If you, or someone you know, woke up yesterday to find a Raspberry Pi under the Christmas tree you’ll probably have made it around to running Minecraft – my nephews certainly did.  You may also have found the Minecraft API that allows you to use Python or Java to play around in the Minecraft world.

‘WHAT NO F#’ I hear you cry.  Well it turns out that it works by sending commands over TCP so there’s no reason we can’t use F# which is lucky otherwise this could have been a very short blog post!

Firstly we’re going to need to have Mono and F# installed on the Pi – there are a few posts showing how to do this and I used this one,  I also installed MonoDevelop.  Once everything’s installed run Minecraft and execute the following code in fsi:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
    open System.Net.Sockets
    open System.Text

    let client = new TcpClient()
    client.Connect("127.0.0.1", 4711)

    let stream = client.GetStream()
    let msg = Encoding.UTF8.GetBytes("chat.post(Merry Christmas from F#)")
    stream.Write(msg, 0, msg.Length)  

you should get a Merry Christmas message posted to the Minecraft window looking something like the picture at the top of this post.

The main types we’re going to need are blocks and position in the Minecraft world.  The position is nice and simple it’s a three dimensional position that can look like this:

1: 
2: 
3: 
4: 
5: 
    type PositionInWorld = {
        X : float
        Y : float
        Z : float
    }

Blocks each have an integer id and optional data which is an integer between 0 and 15 that means something different depending on the block type – for instance if the block is wool the data number represents the colour whereas for TNT the data number represents the number of seconds until it detonates.  Block ids can be found here.  Fortunately the HTML Type Provider saves a lot of typing as it’s much simpler to write a quick script that will produce an enum of blocks that looks something like this:

1: 
2: 
3: 
4: 
5: 
    type Block =
        | Air = 0
        | Stone = 1
        | Grass = 2
        | Dirt = 3

and so on.  For the extra data I just added a type for colour and create a discriminated union to describe a block to be added to the world:

1: 
2: 
3: 
4: 
    type BlockDescription =
        | SimpleBlock of Block
        | BlockOfColour of Block * BlockColour
        | BlockWithData of Block * int

So now a block can be described we need a way of adding it to the Minecraft world.  Commands are sent as text via TCP some, such as adding a block, do not return anything and others, get player position and get block at position, return  text over the TCP connection.  I used another discriminated union to define the commands:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
    type internal Command =
        | Say of string
        | SetBlock of BlockDescription * PositionInWorld
        | SetBlocks of BlockDescription * PositionInWorld * PositionInWorld
        | GetBlock of PositionInWorld
        | MovePlayer of PositionInWorld
        | GetPlayerPos

Along with a pattern match to get the string for a command:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
    let internal commandString =
        function 
            | Say(message) -> 
                sprintf "chat.post(%s)" message
            | SetBlock(block, position)  -> 
                sprintf "world.setBlock(%s,%s)" 
                    (worldPositionParameters position) 
                    (blockParameters block)
            | SetBlocks(block, fromPosition, toPosition) ->
                sprintf "world.setBlocks(%s, %s, %s)"
                    (worldPositionParameters fromPosition) 
                    (worldPositionParameters toPosition) 
                    (blockParameters block)
            | GetBlock(position) ->
                sprintf "world.getBlock(%s)" 
                    (worldPositionParameters position)
            | MovePlayer(position) ->
                sprintf "player.setPos(%s)" 
                    (playerPositionParameters position)
            | GetPlayerPos -> "player.getPos()"

These strings can now be sent to the TCP socket and any return values parsed into the appropriate data types.

All that’s needed now is a simple API to put it all together.  The API I defined looks like this:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
    let Chat msg = executeCommand (Say(msg))

    let Block block pos = 
        executeCommand (SetBlock(SimpleBlock(block), pos))
    let Blocks block startPos endPos = 
        executeCommand (SetBlocks(SimpleBlock(block), startPos, endPos))

    let ColourBlock block colour pos = 
        executeCommand (SetBlock(BlockOfColour(block, colour), pos))
    let ColourBlocks block colour startPos endPos = 
        executeCommand (SetBlocks(BlockOfColour(block, colour), startPos, endPos))

    let DataBlock block data pos = 
        executeCommand (SetBlock(BlockWithData(block, data), pos))
    let DataBlocks block data startPos endPos = 
        executeCommand (SetBlocks(BlockWithData(block, data), startPos, endPos))

    let GetBlock pos = 
        executeCommandAndReturn (GetBlock(pos)) stringToBlock

    let MovePlayer pos = executeCommand (MovePlayer(pos))

    let PlayerPosition() =
        executeCommandAndReturn (GetPlayerPos) stringToPlayerPositionInWorld

So to post a chat message:

1: 
    Minecraft.Chat "Merry Christmas from F#"

Find out the type of block the player is standing on:

1: 
    Minecraft.PlayerPosition() |> Minecraft.GetBlock

Or set the block next to a player to purple coloured wool:

1: 
2: 
    let pos = Minecraft.PlayerPosition()
    { pos with X = pos.X + 1.} |> Minecraft.ColourBlock Block.Wool BlockColour.Purple

And build a very poor attempt at a Christmas tree!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
    let pos = Minecraft.PlayerPosition()
    let treePos = {pos with X = pos.X + 10.}

    Minecraft.Block Block.Wood treePos

    let ys = [1.;2.;3.;4.]
    let zs = [3.;2.;1.;0.]

    List.zip ys zs
        |> List.iter 
            (fun (yoffset,zoffset) -> 
                Minecraft.Blocks Block.Leaves 
                    {treePos with Y = treePos.Y + yoffset;Z = treePos.Z - zoffset}
                    {treePos with Y = treePos.Y + yoffset;Z = treePos.Z + zoffset})

ChristmasTree

And that’s about it – the code is all here on Github

Hope you’ve had a great Christmas and that 2016 is all you wish for – happy building……

Leave a Reply

Your email address will not be published. Required fields are marked *