Autor: Basarâm Ștefan
Grupa: 333CD
This project consists of a game console featuring popular retro games such as Snake, Pong and Asteroids. It uses as control inputs a button and a joystick, with an ultra-wide display composed of 2 LED 8×8 matrices and a buzzer for audio feedback.
The purpose of this device is to facilitate a unique gaming experience in a portable package which aims to capture the golden period of arcade games from the 1980s. This console offers a unique handling to players today who are used to playing games on their phones and desktops/laptops.
My inspiration comes mainly from my existing passion for graphic and game design. Having limited resources, I decided examples from the first generation of video games are the most suited.
The biggest audience for this device are the teens who have an affinity for old-school video games who would thoroughly enjoy a similar gaming experience to the one provided by arcade cabinets, all available in a neat and portable package similar to actual game consoles on the market.
For all devices, the VCC pins are connected to 5V and GND to a GND pin on the host (Arduino).
The Host ↔ Device column presents which pins are used, as well as the direction that information flows in.
Name | Type | Host ↔ Device | Reason |
---|---|---|---|
Module button | Digital | D2 ← out | Binary press state of button is read via INT0 interruption |
LED Matrix (A) | D8 → DIN | Data input, sets the state of any LEDs on the matrix | |
D9 → CS | Chip select | ||
D10 → CLK | Clock signal | ||
LED Matrix (B) | D5 → DIN | Data input, sets the state of any LEDs on the matrix | |
D6 → CS | Chip select | ||
D7 → CLK | Clock signal | ||
Red LED | D13 → LED | Pins power the LED ON/OFF | |
Yellow LED | D12 → LED | ||
Green LED | D11 → LED | ||
Joystick | Analog | A0 ← VRx | Continous input from a potentiometer, representing 2D movement |
A1 ← VRy | |||
Digital | D4 ← SW | Button press state can be read digitally | |
Passive buzzer | Digital (PWM) | D3 → Buzzer | The use of PWM enables adjusting the volume of the buzzer, along with its tone |
Source code available on Github.
Development includes the use of both .ino files and C++ files, as such I used two IDEs
For 3rd party libraries I used LedControl which was a necessary addition in order to use the LED matrices of type MAX7219 which have a unique hardware interface composed of 5 pins.
The code is divided into 3 parts:
It contains the main loop and has the role of managing and updating the engine and the active game as well as cycling the games. The main functions are setup() and loop(), with the auxiliary functions: switchGame(), handleGames(float deltaTime) and startupSequence()
void setup()
void loop()
void handleGames(float deltaTime)
void switchGame()
Acts as the bridge between the games and Console.ino which handles the hardware. Responsible for reading user input and implementing the system which updates the screen.
The drawing system stores 2 arrays of 8 bytes, with one bit for each LED. Each array is associated with an LED matrix and also each LED matrix gets its own controller of type LedControl provided by the 3rd library mentioned above. When setting a pixel on the screen, it doesn't immediately update the LED matrices. It first determines which LED matrix is affected and updates the correspondent array of bytes using bitwise operations. When the game state is updated, the arrays of bytes are sent row by row to the LED controllers which change the LEDs matrices.
For performance reasons, we cannot update each LED individually. Also, because the actual LED matrices composing the physical screen have different orientations, we must change how each LED matrix is updated.
void updateLoop(float deltaTime)
void playSound(int frequency, int duration)
void clearScreen()
void setPixel(int x, int y)
void setPixelToValue(int x, int y, bool on)
void drawToDisplay()
void setDisplayBrightness(int brightness)
I took an approach inspired by OOP design principles and implemented the games using polymorphism and inheritance. I made a Game interface with an updateLoop() function which must be implemented by any game which wishes to be added. Console.ino holds a reference to the current game and calls the updateLoop() function which calls the correct derivative function depending on the underlying game class.
Each game holds a reference to the Engine, fact enforced by the Game interface, which allows it to read the current state of user input, play sounds and update the screen as well as access details about the engine such as the delta time and the time since the last move has occurred.
There are 3 games implemented:
Classic snake, but the walls are portals. Pac-Man style.
In the updateLoop() function, the snake is moved according to its current direction and updates it according to the joystick. If the snake head touches an apple, it plays a sound and the snake's size is increased and it calls placeFood() to choose a new apple placement without clipping the snake, using the built-in function random(). In this game implementation, the snake is not killed if it exits the bounds of the display, instead it is transported to the other side.
If the head of the snake occupies the same position as one of its body, the snake is killed and one of the player's lives is taken. If the player has not run out their 3 lives, they may continue playing from the point just before the collision occured, with the game resuming upon the player's next input. When all lives are exausted, an animation is played which shows how large the snake had grown before it died.
The player encompasses a spaceship on the left side of the screen and must shoot incoming asteroids (presumably, since the display resolution allows room for creative interpretation).
In updateLoop(), the spaceship is moved up or down with the joystick and fires with a normal button press. Asteroids are spawned randomly on right side of the screen and approach the player's ship. Firing makes a noise.
If the player is clipped by an asteroid, their ship is destroyed. This game does not have lives and the player will simply be prompted to try again or move to the next game.
Hang on, isn't pong a 2 player game? Or played with an A.I?
In my version of Pong, the player controls BOTH paddles, one at a time. After a paddle reflects the ball, control is immediately switched to the other paddle and the player must react fast enough to continue to reflect the ball. The game starts with an active paddle and a random ball trajectory.
In updateLoop(), the currently active paddle is moved by the joystick. The paddle is not terribly big, however it moves very fast. If the ball collides with the paddle, the ball is reflected and control is passed to the other paddle. The speed of the ball increases over time.
If the ball goes past the paddle, the player loses a life. If the player has not run out their 3 lives, they get to keep playing with the last speed of the ball preserved and starting from the losing paddle.