Snake and Tetris-like games with Sense HAT on a Raspberry Pi 2 loaded with Windows 10

tl;dr – me n33dz the codez => check here on the SenseHatGames folder

The Sense HAT is a small and inexpensive board  for Raspberry Pi. It sits on top of it and offers a variety of sensors and (most importantly), a 8×8 RGB LED matrix and a 5-direction joystick. More specifically, it offers access to these sensors

  • Gyroscope
  • Accelerometer
  • Magnetometer
  • Temperature
  • Barometric pressure
  • Humidity

IMG_20160125_234729929_TOP.jpgSense HAT on top of a Raspberry Pi 2. You can clearly see the 8×8 RGB LED matrix whereas the joystick is on the bottom right of the HAT.

Absolutely worth mentioning, this board has also gone to space in the Astro Pi mission as part of a competition in the UK! I recently purchased one and, apart from the fun I had interacting with the sensors, I really liked the LED matrix and the joystick. So, I thought, why don’t I try and create a couple of games on it?

Since my Raspberry Pi 2 has Windows IoT Core installed, I opted to create a new Universal Windows Platform app. Then, I had to see whether a C# library for the Sense HAT exists. I was lucky and discovered that Mattias Larsson has created a great library to access the Sense HAT, you can see the details here. I then had to decide what games I should create. Well, this was the easy part, as both Snake and a Tetris-like game ideas were screaming into my head, “develop me, develop me”. So, I opened my laptop and started coding these two projects. It took most of my weekend but they both work flawlessly (I hope!), here’s a video of these two games in action:

Best part is that, since this app was developed as a UWP project, it can work on any Windows 10-enabled device, provided you write input (and maybe, layout) code for each platform. Here are two screenshots of the two games running on my laptop

1
Snake game – the gray ellipse is the “head of the snake”
2
Tetris-like game

We won’t check the source code since it is fully commented, but we’ll mention the important details.

Object Pooler class

If you haven’t heard about object pooling, you can check my blog post “Building a tower defense game in Unity” (check towards the bottom) where I explain and build a generic one. If you are the tl;dr type, an object pooler is an object that caches requested items and provides the user with a mechanism to request objects from this cache, instead of creating new ones all the time. This is especially useful in game development, since new instances of objects are needed all the time. Objects like “enemy”, “bullet”, “hitSound” are needed all the time, so its much more beneficial, performance and memory wise, to have them created beforehand and reused. Here is the part of the code that “gets” an item from our object pooler

        public static Ellipse GetEllipse()
        {
            //find the first non-null ellipse
            var nonNullEllipses = ellipseList.Where(x => isUsed[ellipseList.FindIndex(y => y == x)] == false);
            if(nonNullEllipses.Count() == 0)
            {
                //keep a reference to the old count
                int oldCount = ellipseList.Count();
                //expand the list
                CreateEllipses(2);
                var el = ellipseList.ToArray()[oldCount];  //get the first one that was created
                isUsed[GetEllipseIsUsedIndex(el)] = true; //mark it as used and return it
                return el;
            }
            else
            {
                //find the first one and return it
                var el = nonNullEllipses.First();
                isUsed[GetEllipseIsUsedIndex(el)] = true;
                return el;
            }

 

Snake game

Snake game creation was rather easy. The algorithm follows these steps
– start by moving the snake’s head left
– at any time, accept user’s input to move the head
– all the rest parts of the body follow the head (if you imagine the snake’s pieces belonging to an array, the i index piece will move to the i-1 index position)
– a fruit is popping once and then for the snake to eat (only 1 fruit can exist on the board)
– the snake can eat the fruit and have its tail grow by one item
– if the snake head gets out of bounds or tries to move into one of the pieces in its body, game is over

This is the class that handles the movement of the snake

        public MovementResult TryMove(SnakeMovement direction)
        {
            //get a reference to the head
            SnakePiece head = snake[0];
            RowColumn newHeadLocation = new RowColumn();
            //check if the movement is valid, depending on
            //1. out of bounds check
            //2. new place to move is not already occupied by a snake piece
            switch (direction)
            {
                case SnakeMovement.Left:
                    if (head.Column == 0 ||
                        (matrix[head.Row, head.Column - 1] != null && matrix[head.Row, head.Column - 1] is SnakePiece))
                        return MovementResult.GameOver;
                    else
                        newHeadLocation = new RowColumn(head.Row, head.Column - 1);
                    break;
                case SnakeMovement.Right:
                    if (head.Column == Constants.Columns - 1 ||
                        (matrix[head.Row, head.Column + 1] != null && matrix[head.Row, head.Column + 1] is SnakePiece))
                        return MovementResult.GameOver;
                    else
                        newHeadLocation = new RowColumn(head.Row, head.Column + 1);
                    break;
                case SnakeMovement.Top:
                    if (head.Row == 0 ||
                        (matrix[head.Row - 1, head.Column] != null && matrix[head.Row - 1, head.Column] is SnakePiece))
                        return MovementResult.GameOver;
                    else
                        newHeadLocation = new RowColumn(head.Row - 1, head.Column);
                    break;
                case SnakeMovement.Bottom:
                    if (head.Row == Constants.Rows - 1 ||
                        (matrix[head.Row + 1, head.Column] != null && matrix[head.Row + 1, head.Column] is SnakePiece))
                        return MovementResult.GameOver;
                    else
                        newHeadLocation = new RowColumn(head.Row + 1, head.Column);
                    break;
            }

            //if we have reached here, the move is allowed. We also check if there is a fruit piece in the new space
            MovementResult result = MovementResult.MoveAllowed;
            if (this[newHeadLocation.Row, newHeadLocation.Column] is FruitPiece)
                result = MovementResult.FruitEaten;

            //modify the Piece properties to reflect the new location
            var pieceLocation = head.RowColumn;
            MovePiece(head, newHeadLocation.Row, newHeadLocation.Column);

            //for all the rest snake pieces, move them
            //easy to do, the [i] piece is moved to the location of the [i-1] one
            for (int i = 1; i < snake.Count; i++)
            {
                //cache the item to be moved
                var cache = snake[i].RowColumn;
                //move it to the i-1 location
                MovePiece(snake[i], pieceLocation.Row, pieceLocation.Column);
                //cache the i location
                pieceLocation = cache;
            }
            //pieceLocation now has the previous location of the last snake item
            //no fruit eaten, so nullify it
            if (result == MovementResult.MoveAllowed)
                this[pieceLocation.Row, pieceLocation.Column] = null;
            //fruit eaten, so add another piece to the snake
            else if (result == MovementResult.FruitEaten)
            {
                this.AddSnakePiece(new SnakePiece(pieceLocation.Row, pieceLocation.Column, Colors.Navy));
                FruitExists = false;
                TimeFruitWasCreatedOrEaten = DateTime.Now;
            }

            return result;
        }

Tetris-like game

This game was a bit more complicated than the snake one. You most probably know the game’s flow, so we will not explain it here. Instead, we’ll focus on some of the methods used.

    private bool CanMovementCanBeMade(Movement movement)
        {
            //first we need to see if this can be moved
            //we get a copy of the item
            Shape localCopy = CurrentShape.Clone();
            //we get a reference to all its left,right or bottom pieces
            Piece[] pieces = localCopy.ToArray();
            switch (movement)
            {
                case Movement.Left:
                    for (int i = 0; i < pieces.Count(); i++)
                    {
                        pieces[i].Column--;
                    }
                    break;
                case Movement.Bottom:
                    //we increase their row property by one
                    for (int i = 0; i < pieces.Count(); i++)
                    {
                        pieces[i].Row++;
                    }
                    break;
                case Movement.Right:
                    for (int i = 0; i < pieces.Count(); i++)           
                    {                   
                        pieces[i].Column++;            
                    }                 
                    break;             
                default:            
                    break;        
            }
       //if any column is greater than 7, row less than 0 or greater than 7      
      //or there exists another item in the new positions       
      if (pieces.Any(x => x.Row >= Constants.Rows) || pieces.Any(x => x.Column >= Constants.Columns)
                || pieces.Any(x => x.Column < 0) ||          
         //we get the new position of each piece        
         //we check if already exists a piece in this position in the array    
         //and this position is *not* in the current shape :)      
           (pieces.Any(x => GameArray[x.Row, x.Column] != null
                && CurrentShape.Where(y => y.Row == x.Row && y.Column == x.Column).Count() == 0)))
            {
                //movement cannot be done
                return false;
            }
            else
            {
                //movement OK
                return true;
            }
        }

The CanMovementBeMade method checks if a shape’s movement can be made, i.e. if the shape is moved it will not a) get out of bounds and b) not have any of its pieces overlap another piece. To do this, the method gets a clone of the shape, transforms it (i.e. moves it) and compares the new coordinates. The reason we get a clone and not modify the original one is that we haven’t implemented any “Undo” functionality in the Shape object (even though we could certainly do it).

 

public void Move(Movement movement)
        {
            if (CurrentShape == null) return;

            //&quot;sensitive&quot; area
            lock (lockerObject)
            {
                //check if shape can be moved
                if (CanMovementCanBeMade(movement))
                {
                    //nullify its previous position in the array
                    ClearPreviousCurrentShapePosition();
                    //update all pieces' row or column information according to the movement requested
                    switch (movement)
                    {
                        case Movement.Left:
                            for (int i = 0; i < CurrentShape.Count; i++)
                            {
                                CurrentShape[i].Column--;
                            }
                            break;
                        case Movement.Bottom:
                            for (int i = 0; i < CurrentShape.Count; i++)
                            {
                                CurrentShape[i].Row++;
                            }
                            break;
                        case Movement.Right:
                            for (int i = 0; i < CurrentShape.Count; i++)
                            {
                                CurrentShape[i].Column++;
                            }
                            break;
                        default:
                            break;
                    }
                    //move the current shape in the array
                    PlaceCurrentShape();
                    GameUpdated?.Invoke(this, EventArgs.Empty);
                }
                else//movement cannot be made
                {
                    //item cannot be moved
                    //if the requested movement is bottom, this means that the shape cannot move even further
                    //so we need to 1. check if any row(s) are full of pieces, i.e. there exists a horizontal line
                    //2. remove these lines
                    //3. move all the rest lines towards the bottom of the array
                    //4. request another shape
                    if (movement == Movement.Bottom)
                    {
                        CurrentShape = null;
                        //check and clear lines
                        //move pieces below
                        ClearLinesAndMovePiecesBelow();
                        //create new shape
                        CreateNewShape();
                    }
                }
            }
        }

The Move method a) checks if the movement can be made b)moves the shape by modifying row or column properties and raises the GameUpdated event to notify interested parties that game has been updated. If a bottom movement cannot be made, this means that the item has reached the lowest possible level, so we need to create another item.

 public static class ShapeFactory
    {
        private static List<List<RowColumn>> shapeAModifiers = new List<List<RowColumn>>();
        private static List<List<RowColumn>> shapeBModifiers = new List<List<RowColumn>>()
        {
            new List<RowColumn>(){new RowColumn(0,0),new RowColumn(-1,1), new RowColumn(-2,2)},
            new List<RowColumn>(){ new RowColumn(0, 0), new RowColumn(1, -1), new RowColumn(2, -2) }
        };
        private static List<List<RowColumn>> shapeCModifiers = new List<List<RowColumn>>()
        {
            new List<RowColumn>(){new RowColumn(0,-1),new RowColumn(1,0), new RowColumn(0,1)},
            new List<RowColumn>(){ new RowColumn(1, 0), new RowColumn(0, 1), new RowColumn(-1, 0) },
             new List<RowColumn>(){new RowColumn(0,1),new RowColumn(-1,0), new RowColumn(0,-1)},
            new List<RowColumn>(){ new RowColumn(-1, 0), new RowColumn(0, -1), new RowColumn(1, 0) }
        };

The ShapeFactory class contains information about the 3 available shapes’ initial position (not depicted in the code part shown above) as well as the “rotation modifiers”, the values that are added to a shape pieces’ columns and rows in order to rotate.

IMG_20160126_180137859_TOP.jpg
The three available shapes, along with their available rotations.

That’s it! SenseHAT is an awesome piece of hardware and I’m looking forward to create more stuff with it. If you’re looking for the source code, check here on GitHub (SenseHatGames folder): https://github.com/dgkanatsios/Raspberry

2 thoughts on “Snake and Tetris-like games with Sense HAT on a Raspberry Pi 2 loaded with Windows 10

Leave a comment