Yesterday I was made a Nokia Developer Champion, and I have been working on this post on and off for a few days so the timing is perfect as a homage to Nokia.
This project definitely gave me a good helping of nostalgia, and I had more fun than any other project I’ve done.
The Objective was to remake the original snake game from 1997 that came with the monochrome Nokias, and display it on a Nokia 5110 LCD connected to a Netduino. Then have a Lumia 920 connected over Bluetooth as the controller.
A while back when looking around for display solutions for various projects I stumbled onto the 5110 LCD. I’m not sure *why* exactly they are so popular, but it turns out that you can find them easily online and they are dirt cheap! I bought a couple from DX recently and they work exactly how I expected, and are perfect for a lot of things. At the price, I highly doubt that the DX ones are real, but at less than $5 who’s going to complain?
Get them here: http://dx.com/p/replacement-1-6-lcd-screen-with-blue-backlight-for-nokia-5110-blue-145860 (they also have white), or from ebay etc.

What you need:
- Netduino (I recommend a Netduino 2)
- Bluetooth module
- A Windows Phone 8 (should be a Nokia for authenticity sakes
)
Netduino:
A Netduino community member, Omar, wrote an awesome library for using this particular LCD, which you can grab here: http://wiki.netduino.com/Nokia-5110-LCD.ashx
On that wiki the labels for wiring were in a different order and had slightly different names to my LCD, so if you have one from DX follow my wiring below, or use the wiki instructions.
Once you’ve downloaded the 5110 library, copy Nokia.cs out and paste it into your new Netduino project. You will notice a few errors in the code now because it was written for an older SDK. So we need to make two changes:
- Reference the SecretLabs.NETMF.Hardware.PWM assembly in your project, then add this using at the top of Nokia.cs:
1using NPWM = SecretLabs.NETMF.Hardware.PWM;
Then scroll down to line 115 and change the line to this:
1private NPWM backlight = null;
Then go down a few lines to 127 and change the line to this:
1this.backlight = new NPWM(backlight); - The next problem is the default parameters on the constructor (line 123), so you can safely just kill the defaults because you will be setting them anyway:
12public Nokia_5110(bool useBacklight, Cpu.Pin latch, Cpu.Pin backlight,Cpu.Pin reset, Cpu.Pin dataCommand)
OK, with the LCD setup, go ahead and implement the Bluetooth stuff as described here.
Now create a new class called Snake.cs which is where the snake game will go. I had never thought much about how Snake was written, but it was surprisingly easy to get the basic game up and running.
Here is a simple explanation of how the game works: We have an ArrayList (lack of generic lists in NETMF) which holds all points (simple class with X and Y) with the front of the snake at position 0. Each frame we remove the last item in the list (the end of the snake which will disappear because it is moving forward), then get the position of the first point and add/subtract either X or Y depending on the current direction, then insert that new point at position 0. So instead of moving every block to its new position we just delete the last block and then put a new one at the beginning.
Each frame we check that the new position is not touching the wall or itself (die if it is), and also check whether it touches a fruit (create a new fruit if it is, and increase score).
The whole game, which is mostly just properties and stuff, is only ~160 lines.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
public class Snake { private Random _rnd; private int _screenWidth, _screenHeight; private bool _paused = true; public delegate void SlimEventHandler(); public event SlimEventHandler Died; public event SlimEventHandler LeveledUp; private int _score = 0; public int Score { get { return _score; } set { _score = value; } } private Point _currentFruit; public Point CurrentFruit { get { return _currentFruit; } set { _currentFruit = value; } } private Direction _currentDirection = Direction.Right; public Direction CurrentDirection { get { return _currentDirection; } set { _currentDirection = value; _paused = false; } } public ArrayList _positions; public Snake(int screenWidth, int screenHeight) { _rnd = new Random(); _screenWidth = screenWidth; _screenHeight = screenHeight; _positions = new ArrayList(); _positions.Add(new Point { X = 2, Y = 2 }); for (int i = 0; i < 6; i++) { _positions.Add(new Point((Point)_positions[0])); } _currentFruit = GetRandomPosition(); } public int Step() { if (!_paused) { Point currentPoint = new Point((Point)_positions[0]); switch (CurrentDirection) { case Direction.Up: currentPoint.Y -= 3; break; case Direction.Down: currentPoint.Y += 3; break; case Direction.Left: currentPoint.X -= 3; break; case Direction.Right: currentPoint.X += 3; break; } if (currentPoint.X == _currentFruit.X && currentPoint.Y == _currentFruit.Y) { _positions.Add(new Point((Point)_positions[_positions.Count - 1])); LevelUp(); } if (currentPoint.X < 2 || currentPoint.X >= _screenWidth - 2 || currentPoint.Y < 2 || currentPoint.Y >= _screenHeight - 2) { Die(); } for (int i = 1; i < _positions.Count - 1; i++) { Point pt = (Point)_positions[i]; if (pt.X == currentPoint.X && pt.Y == currentPoint.Y) { Die(); } } _positions.RemoveAt(_positions.Count - 1); _positions.Insert(0, currentPoint); } return Clamp((400 - (Score * 30)), 50, 400); } private Point GetRandomPosition() { return new Point { X = (short)(_rnd.Next(_screenWidth / 3 - 1) * 3 + 2), Y = (short)(_rnd.Next(_screenHeight / 3 - 1) * 3 + 2) }; } private static int Clamp(int val, int min, int max) { return val < min ? min : val > max ? max : val; } private void Die() { if (Died != null) { Died(); } } private void LevelUp() { _currentFruit = GetRandomPosition(); Score++; if (LeveledUp != null) { LeveledUp(); } } } public class Point { private short _x; public short X { get { return _x; } set { _x = value; } } private short _y; public short Y { get { return _y; } set { _y = value; } } public Point() { } /// <summary> /// Use this if you want to clone an existing point /// </summary> /// <param name="existing"></param> public Point(Point existing) { X = existing.X; Y = existing.Y; } } public enum Direction { Up, Down, Left, Right } |
In Program.cs the main loop simply draws the blocks for the snake, walls, and fruit. I tried this game on both the Netduino and Netduino Plus 2 and the performance on the former wasn’t great. The drawing took a little too long which meant that the game never really sped up. So if you have a N2 then I recommend that you use that, however it does definitely work on the N1. Check the source for the rest of the code, but here is the main drawing loop on the Netduino:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
while (true) { int wait = game.Step(); Lcd.Clear(); Lcd.DrawRectangle(0, 0, 84, 48, true, false); foreach (Point position in game._positions) { Lcd.DrawRectangle((short)(position.X - 1), (short)(position.Y - 1), 3, 3, true, true); } Lcd.DrawRectangle((short)(game.CurrentFruit.X - 1), (short)(game.CurrentFruit.Y), 1, 1, true, true); Lcd.DrawRectangle((short)(game.CurrentFruit.X), (short)(game.CurrentFruit.Y - 1), 1, 1, true, true); Lcd.DrawRectangle((short)(game.CurrentFruit.X + 1), (short)(game.CurrentFruit.Y), 1, 1, true, true); Lcd.DrawRectangle((short)(game.CurrentFruit.X), (short)(game.CurrentFruit.Y + 1), 1, 1, true, true); Lcd.Refresh(); Thread.Sleep(wait); } |
Windows Phone:
The Windows Phone side is really simple, and is mainly just stuff to make it “pretty”.![]()
There is a transparent Border (could have been rectangle etc) over the 2, 4, 5, 6, and 8 buttons. Each has a LeftMouseButtonDown event which then sends the direction over Bluetooth to the Netduino.
The Netduino will send messages back to the phone when one of the following happens:
- Player dies
- Player scores (eats a fruit)
- The game is ready to start after dying
Each one of those is just a small message which the phone directly displays in a TextBlock on screen in an “LCD” font.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
private void HandleReceivedMessage(string _receivedBuffer) { screenLbl.Text = _receivedBuffer; if (_receivedBuffer.ToLower().Contains("level")) { scoreSound.Play(); } else if (_receivedBuffer.ToLower().Contains("game over")) { diedSound.Play(); } } |
There are 3 MediaElements on the UI (all are invisible). The first will play the beep sound when a button is pressed, the next will play a happy jingle when they score, and the last will play a dying sound when they die.
When the player presses a button to turn, a little arrow is animated on the Lumia screen as a visual cue. This is simply a vector path object, with a storyboard to animate it. The storyboard has two DoubleAnimations – one to make it fade, and the other to slide it up, left etc.
There are four arrows, each with different rotations:
|
1 2 3 4 5 |
<Path Opacity="0" x:Name="upArrow" Margin="0,100,0,0" VerticalAlignment="Top" Data="F1M753.644,-13.0589L753.736,-12.9639 753.557,-12.7816 732.137,8.63641 732.137,29.7119 756.445,5.40851 764.094,-2.24384 764.275,-2.42352 771.834,5.1286 796.137,29.4372 796.137,8.36163 774.722,-13.0589 764.181,-23.5967 753.644,-13.0589z" Stretch="Uniform" Fill="#5f5200" Width="68" Height="68"> <Path.RenderTransform> <CompositeTransform Rotation="0" CenterX="34" CenterY="34" x:Name="upTranslate"/> </Path.RenderTransform> </Path> |
And then each has a StoryBoard:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
<Storyboard x:Name="upSB"> <DoubleAnimation Storyboard.TargetName="upArrow" Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:00.4"> <DoubleAnimation.EasingFunction> <CubicEase EasingMode="EaseOut"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> <DoubleAnimation Storyboard.TargetName="upTranslate" Storyboard.TargetProperty="TranslateY" From="0" To="-90" Duration="0:0:00.4"> <DoubleAnimation.EasingFunction> <CubicEase EasingMode="EaseOut"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> |
Finally, pressing a button also uses the Windows Phone vibrate API to make a small vibration.
Pretty simple huh? A bunch of code wasn’t mentioned here, so make sure you download the source below:
Download the Netduino + WP8 Source Code (~1.6MB)
If you’ve got question you can ask me here or on the Twitter machine: @roguecode



