Sunday, 10 March 2013

Raspberry Pi - Minecraft - Snake

A post on the MCPi sub-reddit asked if someone could re-create the snake game of which there was a  picture for, but no code.

I was prepared to take up the challenge but I didn't want to re-create this, I fancied creating my own snake game, so with the wife away visiting her mother, what else was there to do with my Saturday night!

The biggest challenge was devising a way of the player controlling the snake, I tried out hitting blocks and capturing events, but it was a bit weird so I settled on having 4 blocks on the floor (up, down, left right) which the player stands on to control the snake.

I had to put glass blocks at head height around the player as well, otherwise it was just to difficult to gauge your movements accurately enough to make it work.

Other than that its pretty simple, I clear some space before I create a great big playing board and controls, I then wrote a class to control the snake and a bit of code which checks the position of the player and changes the direction of the snake accordingly.

The only thing I'm not sure about is the gameplay, I'm not 100% sure I got it right, and without a serious hunt I wouldn't have been able to find a Nokia phone to test it (!), if you know different, put a comment and I'll make some changes or even better take the code and have a go yourself.

If you want to play snake in Minecraft (and quite frankly who wouldn't!):


Download and run
You can download the code direct from git-hub, so run minecraft, open/create a world and follow the instructions:

sudo apt-get install git-core
cd ~
git clone https://github.com/martinohanlon/minecraft-snake.git
cd minecraft-snake
python minecraft-snake.py


The code
If you want learn and have a go yourself, here's how:


Create a directory for the program

mkdir ~/minecraft-snake

Copy the python api class library from minecraft to the programs directory

cp -r ~/mcpi/api/python/mcpi ~/minecraft-snake/minecraft

Create minecraft-snake.py python program

nano ~/minecraft-snake/minecraft-snake.py

or open Idle and save minecraft-snake.py to the minecraft-snake directory

Code

#www.stuffaboutcode.com
#Raspberry Pi, Minecraft Snake

#import the minecraft.py module from the minecraft directory
import minecraft.minecraft as minecraft
#import minecraft block module
import minecraft.block as block
#import time, so delays can be used
import time
#import random module to create random number
import random

#snake class which controls the whole game
class Snake:
    def __init__(self, mc, startVec3, playingBottomLeft, playingTopRight):
        self.mc = mc
        self.direction = "up"
        self.lenght = 5
        self.tail = []
        self.tail.insert(0, startVec3)
        self.playingBottomLeft = playingBottomLeft
        self.playingTopRight = playingTopRight
        self.createApple()
        self.score = 0

    #draw's the whole snake
    def draw(self):
        for segment in self.tail:
            self.mc.setBlock(segment.x, segment.y, segment.z, block.DIAMOND_BLOCK)

    #add's one segment to the snake
    def addSegment(self, segment):
        self.mc.setBlock(segment.x, segment.y, segment.z, block.DIAMOND_BLOCK)
        self.tail.insert(0, segment)
        #do I need to clear the last segment
        if (len(self.tail) > self.lenght):
            lastSegment = self.tail[len(self.tail)-1]
            self.mc.setBlock(lastSegment.x, lastSegment.y, lastSegment.z, block.AIR)
            #pop the last segment off the tail
            self.tail.pop()

    #moves the snake, if it cant it returns false (i.e. game over)
    def move(self):
        newSegment = minecraft.Vec3(self.tail[0].x, self.tail[0].y, self.tail[0].z)
        if self.direction == "up":
            newSegment.y = newSegment.y + 1
        elif self.direction == "down":
            newSegment.y = newSegment.y - 1
        elif self.direction == "left":
            newSegment.x = newSegment.x - 1
        elif self.direction == "right":
            newSegment.x = newSegment.x + 1
        if (self.checkCollision(newSegment) == False):
            self.addSegment(newSegment)
            #have I eaten the apple?
            if (matchVec3(newSegment, self.apple) == True):
                #increase my lenght
                self.lenght = self.lenght + 2
                #increase my score
                self.score = self.score + 10
                #create a new apple
                self.createApple()
            return True
        else:
            #game over
            #flash snake head
            mc.setBlock(self.tail[0].x, self.tail[0].y, self.tail[0].z, block.AIR)
            time.sleep(0.3)
            mc.setBlock(self.tail[0].x, self.tail[0].y, self.tail[0].z, block.DIAMOND_BLOCK)
            time.sleep(0.3)
            mc.setBlock(self.tail[0].x, self.tail[0].y, self.tail[0].z, block.AIR)
            time.sleep(0.3)
            mc.setBlock(self.tail[0].x, self.tail[0].y, self.tail[0].z, block.DIAMOND_BLOCK)
            time.sleep(0.3)
            #show score
            mc.postToChat("Game over - score = " + str(self.score))
            time.sleep(5)
            mc.postToChat("www.stuffaboutcode.com")
            return False

    #function to check if a new segment (or apple) can go there
    def checkCollision(self, newSegment):
        #am I going the boundary
        if ((newSegment.x == playingBottomLeft.x) or (newSegment.y == playingBottomLeft.y) or (newSegment.x == playingTopRight.x) or (newSegment.y == playingTopRight.y)):
            return True
        else:
            #or my own tail
            hitTail = False
            for segment in self.tail:
                if (matchVec3(segment, newSegment) == True):
                    hitTail = True
            return hitTail

    #function to change the snake's direction
    def changeDirection(self, newDirection):
        #code to make sure user doesnt try and make the snake move back on itself
        if (newDirection == "up"):
            if (self.direction != "down"): self.direction = newDirection
        elif (newDirection == "down"):
            if (self.direction != "up"): self.direction = newDirection
        elif (newDirection == "left"):
            if (self.direction != "right"): self.direction = newDirection
        elif (newDirection == "right"):
            if (self.direction != "left"): self.direction = newDirection

    #create the apple at a random position on the board
    def createApple(self):
        badApple = True
        #loop until an apple is created which doesnt collide with the boundary or the snake
        while (badApple == True):
            x = random.randrange(playingBottomLeft.x, playingTopRight.x)
            y = random.randrange(playingBottomLeft.y, playingTopRight.y)
            z = playingBottomLeft.z
            newApple = minecraft.Vec3(x, y, z)
            badApple = self.checkCollision(newApple)
        self.apple = newApple
        self.mc.setBlock(self.apple.x, self.apple.y, self.apple.z, block.GLOWING_OBSIDIAN)

#Compares vec3 objects, if they are the same returns true
def matchVec3(vec1, vec2):
    if ((vec1.x == vec2.x) and (vec1.y == vec2.y) and (vec1.z == vec2.z)):
        return True
    else:
        return False

#draws a vertical outline
def drawVerticalOutline(mc, x0, y0, x1, y1, z, blockType, blockData=0):
    mc.setBlocks(x0, y0, z, x0, y1, z, blockType, blockData)
    mc.setBlocks(x0, y1, z, x1, y1, z, blockType, blockData)
    mc.setBlocks(x1, y1, z, x1, y0, z, blockType, blockData)
    mc.setBlocks(x1, y0, z, x0, y0, z, blockType, blockData)

#main program
if __name__ == "__main__":

    #constants
    screenBottomLeft = minecraft.Vec3(-10,4,15)
    screenTopRight = minecraft.Vec3(10,24,15)
    playingBottomLeft = minecraft.Vec3(-10, 4, 14)
    playingTopRight = minecraft.Vec3(10, 24, 14)
    snakeStart = minecraft.Vec3(0, 5, 14)
    upControl = minecraft.Vec3(0, -1, 1)
    downControl = minecraft.Vec3(0, -1, -1)
    leftControl = minecraft.Vec3(-1, -1, 0)
    rightControl = minecraft.Vec3(1, -1, 0)
    middleControl = minecraft.Vec3(0, 0, 0)
    
    #Connect to minecraft by creating the minecraft object
    # - minecraft needs to be running and in a game
    mc = minecraft.Minecraft.create()

    #Post a message to the minecraft chat window 
    mc.postToChat("Hi, Minecraft Snake, www.stuffaboutcode.com")
    
    #Build game board
    # clear a suitably large area
    mc.setBlocks(-10, 0, -5, 10, 25, 16, block.AIR)
    # create playing board
    mc.setBlocks(screenBottomLeft.x, screenBottomLeft.y, screenBottomLeft.z, screenTopRight.x, screenTopRight.y, screenTopRight.z, block.STONE)
    drawVerticalOutline(mc, playingBottomLeft.x, playingBottomLeft.y, playingTopRight.x, playingTopRight.y, playingTopRight.z, block.OBSIDIAN)
    
    # create control buttons
    mc.setBlock(upControl.x, upControl.y, upControl.z, block.DIAMOND_BLOCK)
    mc.setBlock(downControl.x, downControl.y, downControl.z, block.DIAMOND_BLOCK)
    mc.setBlock(leftControl.x, leftControl.y, leftControl.z, block.DIAMOND_BLOCK)
    mc.setBlock(rightControl.x, rightControl.y, rightControl.z, block.DIAMOND_BLOCK)
    # blocks around control buttons, to stop player moving off buttons
    mc.setBlock(middleControl.x + 2,middleControl.y + 1,middleControl.z, block.GLASS)
    mc.setBlock(middleControl.x - 2,middleControl.y + 1,middleControl.z, block.GLASS)
    mc.setBlock(middleControl.x,middleControl.y + 1,middleControl.z + 2, block.GLASS)
    mc.setBlock(middleControl.x,middleControl.y + 1,middleControl.z - 2, block.GLASS)
    mc.setBlock(middleControl.x - 1,middleControl.y + 1,middleControl.z - 1, block.GLASS)
    mc.setBlock(middleControl.x - 1,middleControl.y + 1,middleControl.z + 1, block.GLASS)
    mc.setBlock(middleControl.x + 1,middleControl.y + 1,middleControl.z + 1, block.GLASS)
    mc.setBlock(middleControl.x + 1,middleControl.y + 1,middleControl.z - 1, block.GLASS)
    mc.setBlock(middleControl.x,middleControl.y - 1,middleControl.z, block.STONE)
    # put player in the middle of the control
    mc.player.setPos(middleControl.x + 0.5,middleControl.y,middleControl.z + 0,5)

    #time for minecraft to catchup
    time.sleep(3)

    mc.postToChat("Walk forward, backward, left, right to control the snake")
    time.sleep(3)

    #create snake
    snake = Snake(mc, snakeStart, playingBottomLeft, playingTopRight)
    snake.draw()

    playing = True
    
    try:
        #loop until game over
        while playing == True:
            #sleep otherwise the snake moves WAY too fast
            time.sleep(0.3)
            #get players position - are they on a control tile, if so change snake's direction
            playerTilePos = mc.player.getTilePos()
            playerTilePos.y = playerTilePos.y - 1
            if matchVec3(playerTilePos, upControl) == True: snake.changeDirection("up")
            elif matchVec3(playerTilePos, downControl) == True: snake.changeDirection("down")
            elif matchVec3(playerTilePos, leftControl) == True: snake.changeDirection("left")
            elif matchVec3(playerTilePos, rightControl) == True: snake.changeDirection("right")
            #move the snake
            playing = snake.move()
    except KeyboardInterrupt:
        print "stopped"



12 comments:

  1. I updated my Pi and changed SD card. Now I can't start MCPi. LXTerminal says "Permission denied".

    ReplyDelete
  2. issue this command first:
    chmod 755 minecraft-pi

    then
    ./minecraft-pi

    to run it.

    ReplyDelete
  3. hi, when i try to run the command like import minecraft.minecraft as minecraft system gives an error saying no such module present ?
    what can be the issue. I am able to run minecraft. Also i have copied the minecraft python api to my desktop folder which contains the minecraft so what can be the issue

    ReplyDelete
    Replies
    1. I would try creating a separate directory for your program and copying the python api library to it using the instructions on the post. Or download the code direct from Github.

      Delete
  4. Hi Martin,

    I just wanted to tell you how much fun we had with your programs in our school. I`m a teacher in Germany and we recently bought a few Raspberrys and started programming.

    Of course Minecraft was the most inetresting part for my pupils. They love firing the cannon :)

    Now we bought a little joystick, placed in inside a nwazet Pi Head-Case and wanted to build a little Minecraft Arcade running your great Snake-game.

    Although the program is very well documented, I couldn`t figure out how to make it automaticaly run again after it stopped so that we could plug out keyboard and mouse and make it run over and over again.

    Could you please give as a hint how to do it? If you want to know how the case looks like, have a look at: www.medienistik.de/case.jpg

    Thanks for all your effort - Cheers!

    

Tobi

    ReplyDelete
    Replies
    1. Hi Tobi,

      The simplest way to get the game to restart is to put a loop around the whole program, its written so that it restarts the game each time its run. So you should be able to put a while(True): after the mc = minecraft.Minecraft.create() line and indent all the code below it. This way the program will keep on restarting after it finishes.

      If you want to do it in a better / more efficient way, you could create a loop just before the #create snake code and then write some code which clears the game board at the end of each game.

      Delete
  5. The first method did it for me. My pupils simply love your programs - thanks for publishing them for free!

    ReplyDelete
    Replies
    1. Great news, no worries, Im always amazed that anyone reads the stuff I write.

      Delete
  6. It says error on line 15 when I try to run I couldn't download it so I just copy pasted it.

    ReplyDelete
    Replies
    1. I suspect you have got some 'odd' invisible characters in your code from copying it out of the browser. This seems to be common problems with Midori on the Pi. See if you have better results with NetBrowser (or whatever its called).

      Why couldnt you download it?

      Delete
  7. How do I get the program to run after I type it into my Pi?

    ReplyDelete
    Replies
    1. You can run it using:

      python minecraft-snake.py

      But please, dont type it all in, just download it from my github, the instructions are above.

      Delete