Table of Contents

2048 on LCD minigame

Introduction

Overview

The game will start with a simple hello menu. Then, the grid and score will be displayed on the LCD screen.

User input:

Any functionality of the game will be implemented both ways, either by interpreting screen touch or joystick input.

Modules used:

Components will be connected as in the below block diagram:

Hardware Design

Electrical scheme

Screen is using 3V3 for logical 1 while Arduino UNO uses 5V as pin output. Thus voltage dividers were added in order to prevent screen damage.

To obtain a good ration, two pairs of resistors were used:

Pin configurations

TFT Screen - Arduino

Arduino SPI interface is shared between display and touchscreen.

Dual axis Joystick Module - Arduino

From the module, only the left joystick is used, thus only a couple of pins are connected.

Bill of Materials

Component Shop Link Datasheet Link
TFT LCD 2.8” eMAG Product Page Datasheet & info
Arduino Uno eMAG Product Page Datasheet
Joystick Module eMAG Product Page Datasheet
Resistors eMAG Product Page Datasheet

Software Design

External libraries used

Code components

Code splits itself in two main components:

Game flow

→ At first, a starting menu is displayed and on button press, the game starts.


→ While playing, possible moves are LEFT,RIGHT, UP and DOWN and depending on the direction the grid changes.


→ If a tile with value 2048 is reached, the game is considered to be won and a win screen is displayed.
→ Best score is updated if necessary and start of a new game is possible on button push.


→ If no more possible moves are available, that is grid is full and no tiles can coalesce anymore, game is considered to be lost.
→ Best score is updated and lose screen is available.
→ However, user has possibility of performing an Undo, which could lead to possibility of playing longer if a convenient tile is generated.


→ At any point in the game, 3 undos are possible. If more are requested, state is not changed.
→ On every move, if grid has changed, a new tile appears in a random position.
→ Bear in mind that if grid does not change, no tile is added in order to avoid fake moves.

Implementation details

→ Value of new tiles is either 2 or 4, but they are added with different probabilities.
2 appears in 8 out of 10 case, while 4 otherwise.
→ This ensures a longer game.
→ However, this might mean more trials when performing an undo if game was lost.


random() function was used to generate both tile values and tile position.
→ Tile position is randomly chosen from list of free positions.
→ At game start, randomSeed is initialized with analog value of a floating pin in order to avoid predictibility.


→ The XPT2046 actually does not return pixel coordinates of touch.
→ Thus remap using map was needed in order to get from a scale of (0, 4095) to screen pixel dimensions.


→ For a cleaner code, Grid class was implemented as a wrapper over a simple 4×4 matrix.
→ This way, overloaded == operator and copy constructor made saving states for undos and performing undo moves easier.


User can choose to use touchscreen or joystick.

In loop function, both variants are polled. Interruptions could not be used because ISR function would have been too complex. Thus I chose polling approach, taking into consideration that playing implies rather continuous moves.



→ Information is not displayed ,on every frame'. Thus, throughout the code I used to display different screens once and maintain them unchanged as much as possible.


→ SPI protocol is fast, but clearing the whole screen and overwriting all pixels is by far visible with human eye. Thus animations that can be implemented cannot be too fast.

Optimizations

→ As mentioned above, rerendering is avoided as much as possible. Thus, when screen changes, tile are rendered only if they have different value than before.
→ When rendering tiles, color is chosen based on their value. In order to perform this efficiently, ILI9341 provided colors are stored in an array and bitwise dividing is used in order to find idx = log2(val).
EEPROM.update was used instead of EEPROM.write in order to preserve non-volatile memory lifetime.

Used labs

Results

GitHub repository

Demo video

Start screen image
Win screen image
Lose screen image
Game screen

Conclusions

This project was a good opportunity to see how theoretic notions work in real life, applying them the make something entertaining.

Bibliography & Other materials

Display interfacing
Interrupts discussions
Resistor color code calculator
Arduino electrical scheme designer