I had Nokia LCD screen for some time, and few days ago, I decided to create functionally legendary Snake game with Arduino Uno, few buttons and piezo speaker for sounds.
Functionalities:
- Main menu with 3 options: Play, Settings and High score
- Settingsmenu: control contrast, backlight and game sounds
- Sounds - play tones on Game over and eating
- High score - preserve high score
There are only few parts in this project, so hardware part is very easy to build.
- 4 push buttons (smaller would be better, but I already had those) for controling
- Nokia 5110 LCD (there are different versions of this module - backlight in different colors: Red, Green, Blue & White)
- Piezo speaker
- Arduino Uno
I used Adafruit’s PCD8544 Nokia 5110 LCD library. You can install it from Library Manager. Also, it needs to be paired with Adafruit GFX Library, so install this library as well.
SpeakerSimple piezo speaker would do the job. It doesn't need any library.
Function tone()
is used for playing notes.
It uses 2 Arduino pins: Ground and one Digital pin.
tone(pin, frequency, duration)
Pin is digital pin that is used, frequency is frequency of the tone in hertz, and duration is optional. For more explanation and notes, go here, on official Arduino website.
In my project, tone is playing when snake eat food and on game over screen.
Buttons- Left - move snake left and move back in game menu
- Right - move snake right and select in menu
- Right - move snake up, up in menu and control contrast
- Down- move snake down, down in menu and control contrast
High score is preserved in Arduino's memory (EEPROM), so it will be there even if Arduino runs out of power.
It is very simple to read and write from it.
// R/W EEPROM
void writeIntIntoEEPROM(int address, int number){
EEPROM.write(address, number >> 8);
EEPROM.write(address + 1, number & 0xFF);
}
int readIntFromEEPROM(int address){
byte byte1 = EEPROM.read(address);
byte byte2 = EEPROM.read(address + 1);
return (byte1 << 8) + byte2;
}
Game logicGameobjects:
- Block - represents part of snake
- Snake
- Food
- GameManager
I will explain here important parts of code from these classes. Rest of the code is in Code section.
The idea for moving a snake is to move the first element in the sequence (snake's tail) to the last (snake's head), and move all the others backwards. In other words, tail gets new coordinates and becomes a head of the snake.
Snake can be moved across the entire screen and will not die if it hits the edges. If this happens, the snake continues on the other side of the screen in the same direction.
void Snake::execute(){
Block* tail = _blocks[0];
//SWITCH CASE HAVE BUG!!!!
if(_direction == UP)
{
moveUp(tail);
}
else if(_direction == DOWN)
{
moveDown(tail);
}
else if(_direction == RIGHT)
{
moveRight(tail);
}
else if(_direction == LEFT)
{
moveLeft(tail);
}
for(int j = 0;j<_size;j++)
{
_blocks[j]=_blocks[j+1];
}
_blocks[_size-1] = tail;
_headX = tail->_x;
_headY = tail->_y;
for(int j = 0;j<_size-1;j++)
{
if(_blocks[j]->_x == _headX &&
_blocks[j]->_y == _headY)
{
_selfTouch=true;
}
}
draw();
}
void Snake::moveRight(Block* tail){
if(_blocks[_size-1]->_x + WEIGHT != WIDTH)
{
tail->_x=_blocks[_size-1]->_x + WEIGHT;
tail->_y=_blocks[_size-1]->_y;
}
else
{
tail->_x=0;
tail->_y=_blocks[_size-1]->_y;
}
}
void Snake::moveUp(Block* tail){
if(_blocks[_size-1]->_y != 0)
{
tail->_y=_blocks[_size-1]->_y - WEIGHT;
tail->_x = _blocks[_size-1]->_x;
}
else
{
tail->_y = HEIGHT - WEIGHT;
tail->_x = _blocks[_size-1]->_x;
}
}
void Snake::moveDown(Block* tail){
if(_blocks[_size-1]->_y + WEIGHT != HEIGHT)
{
tail->_y = _blocks[_size-1]->_y + WEIGHT;
tail->_x = _blocks[_size-1]->_x;
}
else
{
tail->_y = 0;
tail->_x = _blocks[_size-1]->_x;
}
}
void Snake::moveLeft(Block* tail){
if(_blocks[_size-1]->_x != 0)
{
tail->_x=_blocks[_size-1]->_x - WEIGHT;
tail->_y=_blocks[_size-1]->_y;
}
else
{
tail->_x = WIDTH - WEIGHT;
tail->_y = _blocks[_size-1]->_y;
}
}
The snake is drawn in a loop, where one block is drawn in each iteration.
void Snake::draw(){
for (byte i = 0; i < _size; i ++) {
_display->fillRect(_blocks[i]->_x, _blocks[i]->_y, _weight, _weight, BLACK);
}
}
Collision with food is checked in GameManager object. After the snake eats the food, a new block with food coordinates is added to blocks array of snake.
void GameManager::checkForCollision()
{
//check collision with food
if(_food->_x == _snake->_headX &&
_food->_y == _snake->_headY)
{
if(_sound)
{
tone(SPEAKER, NOTE_A7, TONE_FOOD_DURATION);
}
score+=_food->points;
_snake->addBlock(_food->_x, _food->_y);
_food->randomize(WIDTH - WEIGHT, HEIGHT - WEIGHT, WEIGHT);
}
}
The coordinates of the food are determined as follows.
void Food::randomize(byte width, byte height, byte weight){
_x = random(weight,width - weight);
if(_x % weight != 0)
_x+=1;
_y = random(2,height -weight);
if(_y % weight != 0)
_y+=1;
}
Rest of the code is in Code section.
SnakeGame.h
contains all public constants, so you can change snake's max length, initial contrast, width and height of screen etc..
Snake contains array of blocks. Every block have its own x and y coordinate.
Food also its own coordinates. For randomizing food coordinates, I used Arduino's function random(),
with randomSeed(analogRead(0))
in setup. Here is explanation for randomSeed.
GameManager is responsible for game logic (drawing game objects, checking for collision...). It's declared in SnakeGame.ino file.
Finished project
Comments