F18: 2048

From Embedded Systems Learning Academy
Revision as of 05:19, 16 December 2018 by Proj user16 (talk | contribs) (Game Logic)

Jump to: navigation, search

2048

  • How well is Software & Hardware Design described?
  • How well can this report be used to reproduce this project?
  • Code Quality
  • Overall Report Quality:
    • Software Block Diagrams
    • Hardware Block Diagrams
      Schematic Quality
    • Quality of technical challenges and solutions adopted.

Abstract

Two Zero Four Eight (2048) is a game having a grid of numbered tiles (i.e., power of 2) to combine them to create a tile with the number 2048. It is usually played on a 4x4 grid with tiles that slide smoothly when a player moves them towards right, left, up or down. Every time a new tile will randomly appear in an empty spot on the display with a value of either 2 or 4. If two tiles of the same number collide then they will merge into one tile with value of sum of those two tiles. Game continues until no merge is possible.

Objectives & Introduction

Team Members & Responsibilities

Schedule

Week# Start Date Task Status Completion Date
1 09/18
  • Submission of Project Proposals
  • Completed
  • 09/25
2 10/09
  • High-level project design
  • Decide on the components and order them.
  • Completed
  • Completed
  • 10/16
  • 10/16
3 10/16
  • Divide Individual responsibilities and tasks
  • Hardware Schematic design
  • Completed
  • Completed
  • 10/20
  • 10/23
4 10/23
  • Initial PCB design
  • Joystick drivers
  • Completed
  • Completed
  • 11/20
  • 11/08
5 10/30
  • LED Matrix Display drivers
  • Implement basic Game-Play on Linux console
  • Completed
  • Completed
  • 11/16
  • 11/17
6 11/06
  • Display random numbers on LED Matrix using drivers
  • Review of Hardware design by team and ISA team/Professor
  • Completed
  • Completed
  • 11/16
  • 11/20
7 11/13
  • Control LED Matrix using Joystick
  • Finalize PCB design
  • Completed
  • Completed
  • 12/04
  • 11/24
8 11/20
  • Integrate all the Individual Software Modules: Device Drivers, Game-Logic and Tasks
  • Assemble all the components on PCB and test hardware: Connections, Voltage, Current.
  • Test Game-Play after assembly on PCB and make modifications if needed
  • Completed
  • In Progress
  • 12/04
9 11/27
  • Review complete work in detail; Refactor Code, Make Improvisations
  • Make project demo video and presentation.
10 12/03
  • Finalize Wiki Report

Parts List & Cost

Component Cost Quantity Seller
SJOne Board $80 1 Preetpal Kang
Adafruit RGB (64x64) LED Matrix Display $74.95 1 Adafruit
Adafruit Analog 2-axis thumb Joystick $10.18 1 Amazon
5V/4A Power Supply Adapter $15.95 1 Amazon
34 Pin Flat Ribbon Cable $6.12 1 Amazon
PCB $23 5 PCBWay
DC barrel Jack $6.70 1 Amazon
Terminal Blocks $8.70 1 Amazon
Sliding Switches $5.35 1 Amazon

Design & Implementation

The design section can go over your hardware and software design. Organize this section using sub-sections that go over your design and implementation.

Hardware Design

Discuss your hardware design here. Show detailed schematics, and the interface here.

Hardware Interface

In this section, you can describe how your hardware communicates, such as which BUSes used. You can discuss your driver implementation here, such that the Software Design section is isolated to talk about high level workings rather than inner working of your project.

Software Design

Software Design Block Diagram

There are four modules in the software design: Game Logic, Output Interface, Input Interface and the FreeRTOS tasks. When the board boots up, user is given option to select the Input type (Joystick/Accelerometer). An application interface to the selected input device is given to the Game Logic. Two FreeRTOS tasks are created to run the Game Logic and update the display. Game Logic manipulates the game grid, by taking direction as the input through the Input Interface. It invokes the Display Application to dump updated game grid onto the LED Matrix display.

Implementation

Display Driver

LED Matrix Display Front Side
LED Matrix Display Back Side

Purpose of the driver is to give capability to control the device. The LED Matrix display is interfaced over the GPIO pins on the SJOne board. So, the driver for LED matrix display is internally using the GPIO drivers. LED matrix display driver configures all the GPIO pins as output pins at start and sets their values to default. It gives capability to control every pixel on the display. It keeps a frame buffer in its memory and writes application data on the buffer. The main code creates a task to dump the frame buffer onto the Display Device.

typedef struct {
    LPC1758_GPIO_Type A;
    LPC1758_GPIO_Type B;
    LPC1758_GPIO_Type C;
    LPC1758_GPIO_Type D;
    LPC1758_GPIO_Type E;

    LPC1758_GPIO_Type latch;
    LPC1758_GPIO_Type oe;
    LPC1758_GPIO_Type clk;

    LPC1758_GPIO_Type r1;
    LPC1758_GPIO_Type g1;
    LPC1758_GPIO_Type b1;
    LPC1758_GPIO_Type r2;
    LPC1758_GPIO_Type g2;
    LPC1758_GPIO_Type b2;
}LEDMatrixDisplayPincon;

enum Color { //RGB (000 => Off)
    Off,
    Blue,
    Green,
    Cyan,
    Red,
    Pink,
    Lime,
    White
};

enum ColorPlane {
    RedPlane,
    GreenPlane,
    BluePlane
};

class LEDMatrix : public SingletonTemplate<LEDMatrix> {
private:
    friend class SingletonTemplate<LEDMatrix>;
    GPIO *A, *B, *C, *D, *E, *latch, *oe, *clk, *r1, *g1, *b1, *r2, *g2, *b2;
    uint64_t farmeBuffer[64][3];  //64 rows of 3 64 bit integers, for RGB color control of each row

public:
    void init(LEDMatrixDisplayPincon &);
    void enableDisplay();
    void disableDisplay();
    void enableLatch();
    void disableLatch();
    void clearFrameBuffer();
    void updateDisplay();
    void selectRow(int row);
    void clearPixel(int row, int col);
    void setPixel(int row, int col, Color color);
    void setRowData(int row, Color color, uint64_t = 0xFFFFFFFFFFFFFFFF);
    void setRowDataRaw(int row, ColorPlane plane, uint64_t data); //If you know what you're doing!
    void fillFrameBuffer(uint64_t data, Color color);
};

Display Application

The display application is the middle-ware between the Game Logic and the Display Drivers. It is responsible for translating the 4x4 matrix of integers into the 64x64 data to display on the LED matrix. The display application refers to the digit designs that are in a lookup table and sets or clears the pixels according to this lookup table. There are two different font sizes in this application; One which uses 5x3 matrix of pixels and another, which is relatively bigger, using 7x5 matrix of pixels. The numbers in the grid are displayed using the smaller font size and all the remaining data is displayed in the bigger font.

using namespace std;
typedef std::map<int, Color> ColorMap;

void getDigitsFromNumber(int num, std::vector<int>&digits);

enum FontType {
    SmallFont,
    BigFont
};

class DisplayApp {
private:
    LEDMatrix display = LEDMatrix::getInstance();

public:
    void initDisplay(LEDMatrixDisplayPincon &pincon);
    void updateDisplay();
    void displayGrid(int (*grid)[4], ColorMap &colorMap);
    void displayGridBorders(Color color);
    void displayNumber(int num, Color color, int start_row, int start_pixel, FontType font);
    void displayDigit(int digit, Color color, int start_row, int start_pixel, FontType font);
    void displayString(string s, Color color, int start_row, int start_pixel);
    void displayCharacter(char c, Color color, int start_row, int start_pixel);
    void displayNumberSmallFont(int num, Color color, int start_row, int start_pixel);
    void displayNumberBigFont(int num, Color color, int start_row, int start_pixel);
    void displayDigitSmallFont(int digit, Color color, int start_row, int start_pixel);
    void displayDigitBigFont(int digit, Color color, int start_row, int start_pixel);
    void drawBox(int xMin, int yMin, int xMax, int yMax, Color color);
};

Game Logic

Game Start Screen
Game End Screen
  • Start screen and Game Over screen pictures

The game logic has interface to the input and output devices. It takes direction as the input from the input device and runs move logic on the game grid. The game algorithm iterates over the 4x4 matrix of integers and merges tiles if there are two same value tiles, adjacent to each other in the direction of the input. The game score is cumulative sum of all the merges that happen in the grid. Once the game grid and scores are updated, display application is used to update the screen.

class Game {
private:
    int grid[4][4];
    int score;
    bool moved;
    DisplayApp *displayApp;
    Input *input;

public:
    Game(DisplayApp *displayApp, Input *gameInput);
    void updateScreen();
    void run();
    void generateNewTile();
    bool moveTiles(Direction inputDirection);
    void displayScore();
    void rotate();
    void moveUp();
    void moveDown();
    void moveLeft();
    void moveRight();
    bool gridIsFull();
    bool anyPairsPresent();
    bool gameEnded();
};

Input Interface

The input for this game is just the direction. So, we can use any device that can give us the direction input. We are letting the user chose between the on-board accelerometer and a thumb joystick. In future we might also want to update the project by integrating a different input device such as touchscreen. But, adding a different device must not change anything in the Game Logic class. This idea can be implemented using an interface class, which defines the input interface. The application code for each of the input devices must implement this interface. This way game logic will only talk to the interface and not to the input device applications directly.

enum Direction {
        Still,
        Up,
        Down,
        Right,
        Left
};

class Input {
public:
    virtual Direction getDirection() = 0;
    virtual bool selectPressed() = 0;
};

Joystick Application

Joystick application implements the Input interface class. The Joystick driver gives analog values as the input when it is moved. Joystick application takes the analog value and maps it to direction value.

class JoystickApp : public Input {
private:
    Joystick *JS;

public:
    virtual ~JoystickApp();
    JoystickApp(Pin pinX, Pin pinY);
    Direction getDirection();
    bool selectPressed();
};

Accelerometer Application

Accelerometer application implements the Input interface class. We are using the existing Accelerometer drivers from the SJOne development package. The Accelerometer driver gives analog values as the input when it is moved. The accelerometer application takes the analog value and maps it to direction value.

class AccelerometerApp : public Input {
private:
    Acceleration_Sensor acc_sense = Acceleration_Sensor::getInstance();

public:
    Direction getDirection();
    bool selectPressed();
    virtual ~AccelerometerApp();
};

Joystick Driver

Thumb Joystick
Joystick Pin Connections

Thumb Joystick is an analog device. It gives the analog X-axis and Y-axis values whenever it is moved. So, input from joystick is read through the ADC channels of lpc1758. We are using the existing ADC driver available in our SJOne development package and building the joystick driver on top of it.

enum Pin {
        k0_25,       // AD0.2 <-- Light Sensor -->
        k0_26,       // AD0.3
        k1_30,       // AD0.4
        k1_31,       // AD0.5
};

class Joystick {
private:
    uint8_t channelX, channelY;

public:
    Joystick(Pin pinX, Pin pinY);
    void selectPin(Pin pin);
    uint8_t getChannelNumberOfPin(Pin pin);
    uint16_t getX();
    uint16_t getY();
};

FreeRTOS Tasks

The entire application being very simple, does not need too many tasks. The game grid is updated only when there is any input from the user. So, there must be a task that can sample the input at regular intervals and run the game algorithm based on the input. Display has to be updated very frequently for the output to seem stable on the display. We could use either a Repetitive Software Interrupt or a task that runs at right frequency. We have chosen to use a task for updating display since an interrupt based design would be less predictable.

The main code initiates the display and takes user selection of input device. It then creates tasks to start the game.

displayApp.initDisplay(displayPincon);
xTaskCreate(displayTask, "Display", STACK_SIZE, 0, PRIORITY_HIGH, NULL);
xTaskCreate(gameLogicTask, "Game", STACK_SIZE, 0, PRIORITY_MEDIUM, NULL);

void displayTask(void *p)
{
    while(1) {
        displayApp.updateDisplay();
        vTaskDelay(7);
    }
}

void gameLogicTask(void *p)
{
    game.generateNewTile();
    game.generateNewTile();
    while(1) {
        game.run();
        vTaskDelay(150);
    }
}

Testing & Technical Challenges

The challenges in the implementation of this project were as follows.

  • Understanding LED Matrix Display


Include sub-sections that list out a problem and solution, such as:

<Bug/issue name>

Discuss the issue and resolution.

Conclusion

Conclude your project here. You can recap your testing and problems. You should address the "so what" part here to indicate what you ultimately learnt from this project. How has this project increased your knowledge?

Project Video

Upload a video of your project and post the link here.

Project Source Code

References

Acknowledgement

Any acknowledgement that you may wish to provide can be included here.

References Used

List any references used in project.

Appendix

You can list the references you used.

Philosophy

Following are some philosophical renditions of the concepts we have learned in this wonderful class. This is partly inspired by The Zen of Python and mostly by our constant quest for knowledge.

  • Priorities are very important; set them correctly.