Arduino Based Breakout Game
"Arduino Breakout" is a project that recreates the classic arcade game "Breakout" using an Arduino Nano microcontroller. The project utilizes various components including:
- Arduino Nano: The main microcontroller responsible for running the game logic and controlling other components.
- OLED Display: Used to visually represent the game elements such as the paddle, ball, and bricks.
- Push Buttons: Input devices used to control the paddle's movement, allowing players to interact with the game.
The game works by displaying a paddle, ball, and rows of bricks on the OLED display. The player controls the paddle using the push buttons to bounce the ball and break the bricks. The Arduino Nano manages the game's logic, including collision detection between the ball, paddle, and bricks. As the game progresses, the difficulty increases with faster ball speeds and more challenging brick patterns. The player's score is tracked and displayed on the screen, and high scores can be stored in the Arduino's EEPROM memory for future reference. Overall, "Arduino Breakout" provides an engaging and interactive gaming experience while also serving as an educational project for learning about microcontroller programming and hardware interfacing.
Supplies
THINGS WE USED
- Arduino Nano
- PCB board
- SH1106 I2C OLED display
- 6 push buttons
- Closer PCB
Some Supplies
copper wire
Soldering irons
Soldering wire
pla wire
Getting Into the Hardware
ARDUINO NANO
The Arduino Nano features an Atmel AVR microcontroller, specifically the ATmega328P, which is an 8-bit RISC-based processor. It operates at a clock speed of 16 MHz, providing a balance of performance and power efficiency suitable for a wide range of embedded applications.
The ATmega328P incorporates various peripherals and features essential for embedded system development, including digital input/output pins, analog-to-digital converters, timers/counters, serial communication interfaces (UART, SPI, and I2C), and pulse-width modulation (PWM) outputs. These capabilities enable the Arduino Nano to interface with sensors, actuators, displays, and other external devices, making it highly adaptable to diverse project requirements.
While the Arduino Nano's ATmega328P microcontroller is not based on ARM architecture, it offers sufficient computational power and flexibility for many embedded applications. Its 8-bit architecture may seem limited compared to modern 32-bit or 64-bit processors, but it remains widely used due to its simplicity, cost-effectiveness, and suitability for tasks that do not require extensive computational resources.
Overall, the ATmega328P microcontroller embedded within the Arduino Nano provides a reliable and accessible platform for prototyping and deploying embedded systems, offering a balance of performance
SH1106 I2C OLED
The SH1106 I2C OLED display interfaces efficiently with the Arduino Nano, leveraging its hardware capabilities and optimizing resource usage for seamless integration:
1 I2C Protocol: The SH1106 OLED display communicates with the Arduino Nano using the I2C (Inter-Integrated Circuit) protocol, requiring only two wires for data transfer. This simplicity minimizes the number of digital pins needed on the Arduino Nano for communication, preserving GPIO pins for other purposes. The Arduino Nano's ATmega328P microcontroller features dedicated hardware TWI (Two-Wire Interface) peripherals, allowing for efficient and reliable communication with I2C devices like the SH1106 display.
2 Low Resource Overhead: The SH1106 display offloads much of the display rendering and control tasks from the Arduino Nano's microcontroller. Once initialized, the display can independently handle refreshing and updating the screen content based on commands received over the I2C bus. This reduces the computational burden on the Arduino Nano, freeing up processing power and memory for other tasks within your project.
3 Optimized Libraries: The Arduino development environment offers optimized libraries and examples for interfacing with the SH1106 OLED display. These libraries are designed to be efficient in terms of both code size and execution speed, ensuring smooth operation even on the limited resources of the Arduino Nano. By leveraging these libraries, you can achieve fast and reliable communication with the display while maintaining compatibility with the Arduino programming ecosystem.
4 Power Efficiency: The SH1106 OLED display consumes minimal power, especially when compared to traditional LCD displays. This efficiency is particularly advantageous for battery-powered applications, where power consumption must be carefully managed to prolong battery life. By using the SH1106 display with the Arduino Nano, you can create energy-efficient projects that deliver vibrant visual feedback without draining the battery excessively.
In summary, the SH1106 I2C OLED display is highly efficient when paired with the Arduino Nano, thanks to its streamlined communication protocol, low resource overhead, optimized libraries, and power-efficient operation. This synergy enables you to create responsive and energy-efficient projects with rich visual displays using the Arduino Nano platform.
PCB and Push Buttons
The Zero PCB board significantly enhances the Arduino Nano project by providing structural reinforcement, streamlined integration, and customization options:
- Structural Reinforcement: The Zero PCB board offers a stable platform for mounting components like the Arduino Nano and SH1106 OLED display, ensuring their security and durability.
- Streamlined Integration: Its layout simplifies wiring, reducing clutter and potential points of failure, thus accelerating the assembly process.
- Customization Options: With customizable layouts, the Zero board allows tailoring to specific project requirements, ensuring a perfect fit and professional finish.
Push Buttons
Push buttons are basic electronic components used for user input in circuits. When pressed, they physically connect two electrical contacts inside the button, allowing current to flow. In microcontroller projects, push buttons are commonly connected to digital input pins. The microcontroller can detect the change in the pin's state when the button is pressed or released, enabling it to respond accordingly, such as triggering an action or changing a setting. Due to their simplicity and reliability, push buttons are widely used for tasks like menu navigation, mode selection, and user interaction in electronic projects.
Buzzer
Push buttons are basic electronic components used for user input in circuits. When pressed, they physically connect two electrical contacts inside the button, allowing current to flow. In microcontroller projects, push buttons are commonly connected to digital input pins. The microcontroller can detect the change in the pin's state when the button is pressed or released, enabling it to respond accordingly, such as triggering an action or changing a setting. Due to their simplicity and reliability, push buttons are widely used for tasks like menu navigation, mode selection, and user interaction in electronic projects.
Circuit Diagram
This circuit diagram presents the components comprising an Arduino Nano, six push buttons, an OLED SH1106 display, a buzzer, and connecting wires. Crafted using Fritzing, the diagram elucidates the intricate connections among the components. Observing closely, one can discern the meticulous arrangement of wires facilitating the seamless operation of the circuit. Notably, the OLED display stands as the focal point, with its connections intricately integrated into the overall design, ensuring clear communication of data and feedback within the circuit
Arduino Nano +-----------------------+ +-------------------+
| | | |
| I2C SDA (A4) ----+-->| SDA (Data) |
| | | |
| I2C SCL (A5) ----+-->| SCL (Clock) |
| | | |
| D2 --------------+-->| Push Button 1 |
| | | |
| D3 --------------+-->| Push Button 2 |
| | | |
| D4 --------------+-->| Push Button 3 |
| | | |
| D5 --------------+-->| Push Button 4 |
| | | |
| D6 --------------+-->| Push Button 5 |
| | | |
| D7 --------------+-->| Push Button 6 |
| | | |
| D8 --------------+-->| Buzzer |
| | | |
| VCC --------------+-->| VCC |
| | | |
| GND --------------+-->| GND |
| | | |
+-----------------------+ +-------------------+
The Code
The Arduboy Library
The Arduboy2 library is a software library specifically designed for use with the Arduboy and Arduboy-compatible handheld gaming consoles. It provides a comprehensive set of functions and utilities for developing and running games and applications on these devices.
Here's an overview of what the Arduboy2 library offers:
- Graphics and Display: The library includes functions for drawing graphics, text, and sprites on the Arduboy's monochrome OLED display. It provides efficient routines for tasks such as clearing the screen, drawing lines and shapes, displaying text with custom fonts, and managing sprites with collision detection capabilities.
- Input Handling: Arduboy2 simplifies the management of user input, including button presses and releases. It offers functions to detect and respond to button inputs, enabling developers to create responsive and interactive experiences for players.
- Sound Generation: The library supports sound generation on the Arduboy's built-in speaker. It provides functions for generating tones, melodies, and sound effects, allowing developers to enhance gameplay with auditory feedback.
- Game State Management: Arduboy2 facilitates the implementation of game states and logic. It offers utilities for managing game states, updating game logic, and handling transitions between different gameplay modes or screens.
- Optimization and Performance: The library is optimized for performance and memory efficiency, making it suitable for running games and applications on the limited hardware resources of the Arduboy platform. It includes features such as display buffer management and memory allocation optimizations to maximize performance and minimize memory usage.
- Compatibility: Arduboy2 is designed to be compatible with existing Arduboy games and libraries, ensuring seamless integration with the Arduboy ecosystem. It maintains compatibility with the original Arduboy library while introducing new features and improvements for enhanced functionality and performance.
- Frame Management: Arduboy2 manages frame rendering and timing, ensuring smooth and consistent gameplay experiences. It provides functions for controlling frame rate, updating game logic, and synchronizing display refresh cycles, allowing developers to maintain optimal performance and visual quality.
- Memory Management: The library includes utilities for efficient memory management, essential for optimizing resource usage on the Arduboy's limited hardware. It offers features such as PROGMEM support for storing data in program memory, EEPROM utilities for saving game progress and settings, and memory allocation routines for dynamic memory management.
- Advanced Features: Arduboy2 extends the capabilities of the Arduboy platform with advanced features and enhancements. These include support for custom boot logos, display flipping for landscape orientation games, and advanced collision detection algorithms for precise gameplay mechanics.
- Community Support: The Arduboy2 library benefits from a vibrant community of developers and enthusiasts who contribute to its ongoing development and improvement. Community-driven initiatives, such as online forums, documentation, tutorials, and example projects, provide valuable resources and support for developers at all skill levels.
- Cross-Platform Compatibility: While primarily designed for the Arduboy platform, the Arduboy2 library is adaptable for use in other Arduino-based projects and compatible devices. Its modular design and well-defined APIs enable easy integration into custom hardware configurations, opening up possibilities for cross-platform development and experimentation.
- Open-Source Nature: As an open-source project, the Arduboy2 library encourages collaboration, innovation, and knowledge sharing within the developer community. Developers can contribute bug fixes, optimizations, and new features to the library, ensuring its continued evolution and relevance in the ever-changing landscape of game development on embedded platforms.
Using Arduboy 2 in This Code
1 initialization: The Arduboy2 library is initialized at the beginning of the setup() function using arduboy.begin(). This sets up the Arduboy hardware and prepares it for use in the game
.
2 Display Management: Throughout the code, various functions from the Arduboy2 library are used to draw graphics on the screen, clear the screen, and update the display. Functions like arduboy.clear() and arduboy.display() are used to manage the display buffer and update the screen content.
3 Input Handling: The code checks for button presses using functions like arduboy.pressed() to detect player input. Button presses are used to control the paddle's movement and to pause/unpause the game.
4 Timing and Frame Rate Control: The Arduboy2 library's arduboy.nextFrame() function is used to control the frame rate and timing of the game loop. It ensures that the game runs at a consistent frame rate, providing smooth gameplay.
5 Random Number Generation: The Arduboy2 library's arduboy.initRandomSeed() function is used to initialize the random number generator, enabling the generation of random values for various game elements, such as the initial direction of the ball.
6 EEPROM Usage: The code utilizes the EEPROM to store and retrieve high scores. Functions like EEPROM.read() and EEPROM.update() are used to read from and write to EEPROM memory.
7 Sound Generation: The Arduboy2 library's BeepPin1 class is used to generate sound effects during gameplay. Functions like beep.tone() and beep.noTone() are used to produce tones of specific frequencies and durations.
Using EEPROM (Electrically Erasable Programmable Read-Only Memory)
In the provided code, EEPROM (Electrically Erasable Programmable Read-Only Memory) is utilized to store and retrieve high scores achieved in the game. Here's how EEPROM is used and implemented:
1 Initialization: The code defines a constant EE_FILE which specifies the EEPROM memory block used to store high scores. In this case, EE_FILE is set to 2.
2 Reading High Scores: When the game is started, the displayHighScores() function is called to display the high scores stored in EEPROM. This function iterates through the EEPROM memory block specified by EE_FILE, reads the high scores stored in it, and displays them on the screen.
3 Storing High Scores: When a game ends and the player achieves a high score, the enterHighScore() function is called to prompt the player to enter their initials for the high score. Once the initials are entered, the function writes the new high score along with the player's initials to EEPROM.
4 EEPROM Address Calculation: The address in EEPROM where high scores are stored is calculated based on the value of EE_FILE and the structure of the high score data. Each high score entry consists of 5 bytes: 3 bytes for the player's initials and 2 bytes for the score value. The address calculation ensures that high scores are stored sequentially in EEPROM without overwriting existing data.
5 EEPROM Reading and Writing: Reading from and writing to EEPROM is done using the EEPROM.read() and EEPROM.update() functions, respectively. These functions allow the program to access the non-volatile memory provided by EEPROM and perform read and write operations.
6 Handling Uninitialized Data: Before reading high scores from EEPROM, the code checks if the EEPROM contains uninitialized data (0xFF). If uninitialized data is found, it treats the high score as 0, indicating that no high score has been achieved yet.
Overall, EEPROM serves as a persistent storage mechanism for high scores in the game. It allows players to track their performance over multiple gaming sessions, even when the Arduboy device is powered off or restarted. By leveraging EEPROM, the game provides a more engaging and rewarding experience for players, encouraging them to strive for higher scores and compete with others.
Interfacing the SH1106 Oled I^2C
In the provided code, the SH1106 OLED display is utilized to render graphics and text for the Breakout-style game.
Here's how the SH1106 display is used and programmed:
- Library Inclusion: The code includes the Arduboy2 library, which provides an abstraction layer for interacting with the SH1106 OLED display. This library contains functions for drawing shapes, text, and images on the display.
- Display Initialization: At the beginning of the setup() function, the Arduboy2 library is initialized using arduboy.begin(). This initializes the SH1106 OLED display and prepares it for rendering graphics and text.
- Graphics Rendering: Throughout the code, various functions from the Arduboy2 library are used to render graphics on the SH1106 display. Functions like arduboy.drawPixel(), arduboy.drawRect(), and arduboy.clear() are used to draw individual pixels, rectangles, and clear the display buffer, respectively.
- Text Display: The code also includes functions to display text on the SH1106 OLED display. Functions like arduboy.setCursor() and arduboy.print() are used to set the cursor position and print text strings on the display.
- Drawing Game Elements: Game elements such as the paddle, ball, bricks, and game over screen are drawn using a combination of pixel rendering and shape drawing functions provided by the Arduboy2 library. For example, the drawBall(), drawPaddle(), and drawGameOver() functions contain code to render these game elements on the SH1106 display.
- Graphics Manipulation: The code manipulates graphics elements on the display buffer to create animations, update game state, and provide feedback to the player. For example, the moveBall() function updates the position of the ball on the display buffer based on its movement and collision with other game elements.
- Display Update: The Arduboy2 library's arduboy.display() function is called periodically to update the SH1106 display with the contents of the display buffer. This function transfers the contents of the display buffer to the SH1106 OLED display, refreshing the screen and making the rendered graphics and text visible to the player.
Overall, the SH1106 OLED display is integral to the gameplay experience of the Breakout-style game, providing visual feedback to the player and rendering the game's graphics and text elements. Through the Arduboy2 library's functions, the code interacts with the SH1106 display, enabling the creation of dynamic and engaging visuals for the game.
Logic From Buttons
- Initialization: Pins connected to each button are configured as inputs during setup() using the pinMode() function.
- Polling: In the loop(), the Arduino continuously checks the state of each button using digitalRead().
- State Detection: When digitalRead() detects a button press (returns LOW), the Arduino identifies the pressed button based on the corresponding pin.
- Action Execution: Conditional statements, such as if-else blocks or switch-case structures, determine the action associated with each button. For instance, specific functions or blocks of code are executed depending on which button is pressed.
- Handling Multiple Buttons: The code includes logic to manage simultaneous button presses or long-press actions. This might involve tracking button states over multiple iterations or implementing a state machine to distinguish between different button combinations and durations.
Here's how the logic for handling input from six buttons is implemented in the Code
// Initialization: Configure button pins as inputs
void setup() {
pinMode(A_BUTTON_PIN, INPUT);
pinMode(B_BUTTON_PIN, INPUT);
// Pins for other buttons initialized here...
}
// Polling: Continuously check button states
void loop() {
// Check state of each button
if (digitalRead(A_BUTTON_PIN) == LOW) {
// Action for button A
// Code related to button A press goes here...
}
else if (digitalRead(B_BUTTON_PIN) == LOW) {
// Action for button B
// Code related to button B press goes here...
}
// Conditions for other buttons checked similarly...
}
Using of Buzzer
- Initialization: The buzzer is initialized in the setup() function, ensuring it's ready to produce tones when required.
- Tone Generation: The buzzer generates tones of specific frequencies and durations using the beep library functions beep.tone() and beep.noTone().
- Auditory Feedback: The buzzer produces sound effects to enhance the gaming experience. For example:
- Collision Events: When the ball collides with objects such as the paddle or bricks, the buzzer emits a tone to indicate the impact.
- Game Over: Upon game over, the buzzer produces a distinctive sound to signify the end of the game session.
- Enhanced User Experience: By incorporating auditory feedback, the game becomes more engaging and immersive for players. Sound effects provide immediate feedback on game events, enhancing the overall gaming experience.
- Code Integration: The buzzer functionality is seamlessly integrated into the game logic, triggered by specific game events or conditions.
The Code Explained
EXPLAINATION
- #include <Arduboy2.h>: This line includes the Arduboy2 library, which provides functions and definitions for programming the Arduboy handheld game console. Including this library allows access to Arduboy-specific features and functionalities in the code.
- // block in EEPROM to save high scores: This is a comment that explains the purpose of the EE_FILE constant, which is used to specify the EEPROM block where high scores are saved. EEPROM (Electrically Erasable Programmable Read-Only Memory) is non-volatile memory that can store data even when the power is turned off.
- #define EE_FILE 2: This line defines a preprocessor macro named EE_FILE, which is set to the value 2. This macro is used to specify the EEPROM block number where high scores will be stored. By using a macro, it becomes easier to change the EEPROM block number if needed, as it only needs to be modified in one place.
- Arduboy2 arduboy;: This line declares an object named arduboy of the class Arduboy2. This object is used to interact with the Arduboy hardware, such as displaying graphics, reading button inputs, controlling the display frame rate, and more.
- BeepPin1 beep;: This line declares an object named beep of the class BeepPin1. This object is used to control the speaker or generate tones on the Arduboy. It allows the game to produce sound effects or music during gameplay.
2 PART
- const unsigned int FRAME_RATE = 40; // Frame rate in frames per second: This line defines a constant named FRAME_RATE with a value of 40. It represents the desired frame rate for the game, indicating how many frames should be displayed per second. In this case, the frame rate is set to 40 frames per second.
- const unsigned int COLUMNS = 13; //Columns of bricks: This line defines a constant named COLUMNS with a value of 13. It specifies the number of columns in the brick layout of the game. Each column represents a vertical arrangement of bricks in the game environment.
- const unsigned int ROWS = 4; //Rows of bricks: This line defines a constant named ROWS with a value of 4. It specifies the number of rows in the brick layout of the game. Each row represents a horizontal arrangement of bricks in the game environment.
- int dx = -1; //Initial movement of ball: This line declares a variable named dx of type int and initializes it with the value -1. It represents the initial movement direction of the ball along the x-axis. A value of -1 indicates that the ball initially moves towards the left side of the screen.
- int dy = -1; //Initial movement of ball: This line declares a variable named dy of type int and initializes it with the value -1. It represents the initial movement direction of the ball along the y-axis. A value of -1 indicates that the ball initially moves upwards on the screen.
- int xb; //Balls starting position: This line declares a variable named xb of type int without initializing it. It represents the x-coordinate of the ball's starting position on the screen.
- int yb; //Balls starting position: This line declares a variable named yb of type int without initializing it. It represents the y-coordinate of the ball's starting position on the screen
3 PART
- boolean released; // If the ball has been released by the player: This declares a boolean variable named released. It's used to track whether the ball has been released by the player, typically triggered by pressing a button to start the game or launch the ball.
- boolean paused = false; // If the game has been paused: This declares a boolean variable named paused and initializes it to false. It's used to determine whether the game is currently paused. When paused is true, the game stops updating, and the player typically sees a "PAUSE" message on the screen.
- byte xPaddle; // X position of paddle: This declares a byte variable named xPaddle. It represents the x-coordinate of the paddle's position on the screen. The paddle is the object controlled by the player to deflect the ball.
- boolean isHit[ROWS][COLUMNS]; // Array of if bricks are hit or not: This declares a 2D boolean array named isHit. It represents the state of each brick in the game, indicating whether it has been hit by the ball (true) or not (false). The array size is determined by the number of rows and columns of bricks.
- boolean bounced=false; // Used to fix double bounce glitch: This declares a boolean variable named bounced and initializes it to false. It's used to prevent a double bounce glitch that may occur when the ball collides simultaneously with multiple objects, such as the paddle and a brick.
- byte lives = 3; // Amount of lives: This declares a byte variable named lives and initializes it to 3. It represents the number of lives or attempts the player has remaining before the game ends. Losing a life typically occurs when the ball falls off the bottom of the screen.
- byte level = 1; // Current level: This declares a byte variable named level and initializes it to 1. It represents the current level of the game. The level may increase as the player progresses through the game by clearing all bricks on the screen.
- unsigned int score=0; // Score for the game: This declares an unsigned integer variable named score and initializes it to 0. It represents the player's score in the game, which typically increases as the player successfully hits and destroys bricks.
- unsigned int brickCount; // Amount of bricks hit: This declares an unsigned integer variable named brickCount. It represents the total number of bricks hit and destroyed by the player during the game.
- boolean pad, pad2, pad3; // Button press buffer used to stop pause repeating: These lines declare boolean variables pad, pad2, and pad3. They are used as button press buffers to prevent the game from registering multiple button presses in quick succession, which could lead to unintended behavior, such as rapid pausing and unpausing.
- boolean oldpad, oldpad2, oldpad3;: These lines declare boolean variables oldpad, oldpad2, and oldpad3. They are used to store the previous state of the button press buffers, allowing the game to detect when a button press has changed from not pressed to pressed.
- char text_buffer[16]; // General string buffer: This declares a character array named text_buffer with a size of 16 characters. It's used as a general-purpose string buffer for storing and manipulating text data during the game.
- boolean start=false; // If in menu or in game: This declares a boolean variable named start and initializes it to false. It's used to determine whether the game is currently in the menu state or in the gameplay state.
- boolean initialDraw=false; // If the initial draw has happened: This declares a boolean variable named initialDraw and initializes it to false. It's used to track whether the initial drawing of game elements, such as bricks and the paddle, has occurred. This typically happens once at the start of each level.
- char initials[3]; // Initials used in high score: This declares a character array named initials with a size of 3 characters. It's used to store the player's initials when entering a high score into the game's leaderboard.
4 PART
- byte leftBall;: This variable represents the left boundary of the ball used in collision detection. It determines the leftmost x-coordinate of the ball's bounding box.
- byte rightBall;: This variable represents the right boundary of the ball used in collision detection. It determines the rightmost x-coordinate of the ball's bounding box.
- byte topBall;: This variable represents the top boundary of the ball used in collision detection. It determines the highest y-coordinate of the ball's bounding box.
- byte bottomBall;: This variable represents the bottom boundary of the ball used in collision detection. It determines the lowest y-coordinate of the ball's bounding box.
- byte leftBrick;: This variable represents the left boundary of a brick used in collision detection. It determines the leftmost x-coordinate of the brick's bounding box.
- byte rightBrick;: This variable represents the right boundary of a brick used in collision detection. It determines the rightmost x-coordinate of the brick's bounding box.
- byte topBrick;: This variable represents the top boundary of a brick used in collision detection. It determines the highest y-coordinate of the brick's bounding box.
- byte bottomBrick;: This variable represents the bottom boundary of a brick used in collision detection. It determines the lowest y-coordinate of the brick's bounding box.
- byte tick;: This variable is used to track time or the number of frames elapsed. It may be utilized for timing purposes within the game logic, such as controlling the movement speed of objects or triggering events at specific intervals.
5 PART
This setup() function is part of the Arduino sketch and is executed once when the program starts. Here's a breakdown of each line:
- arduboy.begin();: This initializes the Arduboy library, setting up the display, buttons, and other necessary components for interacting with the Arduboy device.
- beep.begin();: This initializes the BeepPin1 object, presumably used for generating sound effects in the game. It sets up the necessary hardware or software components for producing sound.
- arduboy.setFrameRate(FRAME_RATE);: This sets the frame rate of the game to the value specified by the FRAME_RATE constant. The frame rate determines how many frames per second the game will attempt to render.
- arduboy.initRandomSeed();: This initializes the random number generator with a seed value based on some internal entropy source. It ensures that random numbers generated during the game will be different each time the program runs, providing variety and unpredictability in gameplay.
6 PART
- void loop(): This line defines the beginning of the loop() function, which is the main program loop in Arduino sketches. It runs repeatedly after the setup() function finishes executing.
- if (!(arduboy.nextFrame())) return;: This line checks if it's time to render the next frame. If not, it pauses execution until the next frame is due. It prevents the game from updating too quickly, helping maintain a consistent frame rate.
- beep.timer();: This line handles the timing and stopping of tones, likely for generating sound effects in the game.
- while (!start) { start = titleScreen(); if (!start) { start = displayHighScores(EE_FILE); } }: This loop displays the title screen and high scores alternately until the player presses the FIRE button to start the game (start becomes true).
- if (!initialDraw) { /* code omitted for brevity */ }: This condition checks if the initial draw of the game has occurred. If not, it clears the screen, sets up the initial level, resets the score, and marks the initial draw as completed.
- if (lives > 0) { /* code omitted for brevity */ } else { /* code omitted for brevity */ }: This condition checks if the player has remaining lives. If they do, it executes the game logic (drawing paddle, checking for pause, updating ball position, etc.). If not, it handles the game over scenario.
- arduboy.display();: This line displays the rendered frame on the Arduboy screen, showing the current state of the game to the player.
7 PART
- void movePaddle(): This line defines the beginning of the movePaddle() function, indicating that it does not return any value (void).
- if(xPaddle < WIDTH - 12): This line checks if the current position of the paddle (xPaddle) is less than the width of the screen minus 12 pixels. This condition ensures that the paddle does not move beyond the right boundary of the screen.
- if (arduboy.pressed(RIGHT_BUTTON)): This line checks if the RIGHT button is pressed on the Arduboy. If the button is pressed and the paddle can still move to the right (based on the previous condition), the following code block will execute.
- xPaddle += 2;: This line increments the xPaddle variable by 2, moving the paddle two pixels to the right.
- if(xPaddle > 0): This line checks if the current position of the paddle (xPaddle) is greater than 0, ensuring that the paddle does not move beyond the left boundary of the screen.
- if (arduboy.pressed(LEFT_BUTTON)): This line checks if the LEFT button is pressed on the Arduboy. If the button is pressed and the paddle can still move to the left (based on the previous condition), the following code block will execute.
- xPaddle -= 2;: This line decrements the xPaddle variable by 2, moving the paddle two pixels to the left.
8 PART
- Void moveBall(): This line declares the beginning of the moveBall() function, which is responsible for moving the ball and handling collisions in the game.
- tick++: This line increments the tick variable by 1, which is used to control the speed of the ball movement.
- if(released): This line checks if the ball has been released, meaning it is in motion.
- if (abs(dx)==2): This line checks if the absolute value of the horizontal movement of the ball (dx) is equal to 2, indicating a higher speed.
- xb += dx/2;: This line updates the horizontal position of the ball by adding half of the current horizontal movement (dx) to its current position, effectively reducing the speed by half.
- if (tick%2==0): This line checks if the tick count is divisible by 2 without a remainder, effectively reducing the speed of the ball by half.
- xb += dx/2;: This line further updates the horizontal position of the ball by adding half of the current horizontal movement (dx) to its current position.
- else: This line specifies the code block to execute if the condition in the previous line is not met, indicating a regular movement.
- xb += dx;: This line updates the horizontal position of the ball by adding the current horizontal movement (dx) to its current position.
- yb=yb + dy;: This line updates the vertical position of the ball by adding the current vertical movement (dy) to its current position.
- leftBall = xb; rightBall = xb + 2; topBall = yb; bottomBall = yb + 2;: These lines set the boundaries of the ball based on its current position, allowing collision detection.
- if (yb <= 0): This line checks if the ball has hit the top edge of the screen.
- yb = 2; dy = -dy; playTone(523, 250);: This line adjusts the ball's position, changes its vertical direction, and plays a tone to indicate a bounce off the top edge.
- if (yb >= 64): This line checks if the ball has hit the bottom edge of the screen.
- arduboy.drawRect(xPaddle, 63, 11, 1, 0); xPaddle = 54; yb=60; released = false; lives--; playToneTimed(175, 500);: This line handles the scenario where the ball hits the bottom edge, adjusts the paddle's position, decrements a life, and plays a tone.
- if (xb <= 0): This line checks if the ball has hit the left edge of the screen.
- xb = 2; dx = -dx; playTone(523, 250);: This line adjusts the ball's position, changes its horizontal direction, and plays a tone to indicate a bounce off the left edge.
- if (xb >= WIDTH - 2): This line checks if the ball has hit the right edge of the screen.
- xb = WIDTH - 4; dx = -dx; playTone(523, 250);: This line adjusts the ball's position, changes its horizontal direction, and plays a tone to indicate a bounce off the right edge.
- if (xb+1>=xPaddle && xb<=xPaddle+12 && yb+2>=63 && yb<=64): This line checks if the ball has collided with the paddle.
- dy = -dy; dx = ((xb-(xPaddle+6))/3); playTone(200, 250);: This line adjusts the vertical direction of the ball, applies spin based on its collision with the paddle, and plays a tone.
- for (byte row = 0; row < ROWS; row++): This line initiates a loop through the rows of bricks.
- for (byte column = 0; column < COLUMNS; column++): This line initiates a loop through the columns of bricks.
- if (!isHit[row][column]): This line checks if the current brick has not been hit yet.
- leftBrick = 10 * column; rightBrick = 10 * column + 10; topBrick = 6 * row + 1; bottomBrick = 6 * row + 7;: These lines set the boundaries of the current brick.
- if (topBall <= bottomBrick && bottomBall >= topBrick && leftBall <= rightBrick && rightBall >= leftBrick): This line checks if a collision has occurred between the ball and the current brick.
- Score(); brickCount++; isHit[row][column] = true; arduboy.drawRect(10column, 2+6row, 8, 4, 0);: This line updates the score, increments the count of bricks hit, marks the current brick as hit, and updates the display to remove the hit brick.
- if (bottomBall > bottomBrick || topBall < topBrick): This line checks if the collision is vertical.
- dy =- dy; yb += dy; bounced = true; playTone(261, 250);: This line adjusts the vertical direction of the ball, updates its position, sets a flag to indicate a bounce, and plays a tone.
- if (leftBall < leftBrick || rightBall > rightBrick): This line checks if the collision is horizontal.
- dx =- dx; xb += dx; bounced = true; playTone(261, 250);: This line adjusts the horizontal direction of the ball, updates its position, sets a flag to indicate a bounce, and plays a tone.
- bounced = false;: This line resets the bounce flag.
- else: This line specifies the code block to execute if the condition in the initial if statement is not met.
- xb=xPaddle + 5;: This line sets the horizontal position of the ball to be centered on the paddle.
- pad3 = arduboy.pressed(A_BUTTON) || arduboy.pressed(B_BUTTON);: This line checks if button A or B is pressed on the Arduboy.
- if (pad3 == true && oldpad3 == false): This line checks if button A or B has been pressed for the first time since the last check.
- released = true;: This line sets the released flag to true, indicating that the ball is released.
- if (random(0, 2) == 0): This line generates a random number between 0 and 1. If it's 0, the ball will move leftward; otherwise, it will move rightward.
9 PART
- void drawBall(): This line indicates the start of the drawBall() function, which is responsible for drawing the ball on the screen.
- arduboy.drawPixel(xb, yb, 0);: This line draws a pixel at the coordinates specified by xb and yb, which represent the horizontal and vertical positions of the ball respectively. The color of the pixel is specified by the value 0, indicating black or off.
- arduboy.drawPixel(xb+1, yb, 0);: This line draws a pixel adjacent to the previous one, one pixel to the right, at coordinates (xb+1, yb). This completes the first row of pixels for the ball.
- arduboy.drawPixel(xb, yb+1, 0);: This line draws a pixel below the first pixel in the first row, at coordinates (xb, yb+1). This starts the second row of pixels for the ball.
- arduboy.drawPixel(xb+1, yb+1, 0);: This line draws a pixel adjacent to the previous one, one pixel to the right, and one pixel down, at coordinates (xb+1, yb+1). This completes the second row of pixels for the ball.
10 PART
- moveBall(): This line indicates the start of the moveBall() function.
- arduboy.drawPixel(xb, yb, 1);: This line draws a pixel at the current coordinates specified by xb and yb, with the color 1, representing white or on.
- arduboy.drawPixel(xb+1, yb, 1);: This line draws a pixel to the right of the previous one, one pixel over, at coordinates (xb+1, yb).
- arduboy.drawPixel(xb, yb+1, 1);: This line draws a pixel below the first pixel in the first row, at coordinates (xb, yb+1).
- arduboy.drawPixel(xb+1, yb+1, 1);: This line draws a pixel to the right and one down from the previous one, at coordinates (xb+1, yb+1).
11 PART
- void drawPaddle(): This line indicates the start of the drawPaddle() function.
- arduboy.drawRect(xPaddle, 63, 11, 1, 0);: This line draws a rectangle representing the paddle at the current position specified by the xPaddle variable. The rectangle is 11 pixels wide, 1 pixel tall, and has a color of 0, representing black or off.
- movePaddle();: This line calls the movePaddle() function, which presumably updates the position of the paddle based on player input.
- arduboy.drawRect(xPaddle, 63, 11, 1, 1);: This line draws the paddle again after it has been moved. The parameters are the same as the previous arduboy.drawRect() function call, except the color is 1, representing white or on.
12 PART
- void drawGameOver(): This line indicates the start of the drawGameOver() function.
- arduboy.drawPixel(xb, yb, 0);
- arduboy.drawPixel(xb+1, yb, 0);
- arduboy.drawPixel(xb, yb+1, 0);
- arduboy.drawPixel(xb+1, yb+1, 0);
- These lines draw a small black rectangle (2x2 pixels) at the position specified by xb and yb, which presumably represents the position of the ball.
- arduboy.setCursor(37, 42);: This line sets the cursor position to (37, 42) on the Arduboy screen, indicating where the text "Game Over" will be printed.
- arduboy.print("Game Over");: This line prints the text "Game Over" at the cursor position set in the previous line.
- arduboy.setCursor(31, 56);: This line sets the cursor position to (31, 56) on the Arduboy screen, indicating where the text "Score: " will be printed.
- arduboy.print("Score: ");: This line prints the text "Score: " at the cursor position set in the previous line.
- arduboy.print(score);: This line prints the value of the variable score, which presumably represents the player's score.
- arduboy.display();: This line updates the display to show the changes made by the drawing functions.
- arduboy.delayShort(4000);: This line introduces a short delay of 4000 milliseconds (4 seconds), presumably to allow the player to view the "Game Over" screen before the game proceeds or restarts.
13 PART
- void pause(): This line indicates the start of the pause() function.
- paused = true;: This line sets the paused variable to true, indicating that the game is paused.
- arduboy.setCursor(52, 45);: This line sets the cursor position to (52, 45) on the Arduboy screen, where the text "PAUSE" will be printed.
- arduboy.print("PAUSE");: This line prints the text "PAUSE" at the cursor position set in the previous line.
- arduboy.display();: This line updates the display to show the changes made by the drawing functions.
- while (paused): This line initiates a while loop that continues as long as the game is paused.
- arduboy.delayShort(150);: This line introduces a short delay of 150 milliseconds to control the loop speed.
- pad2 = arduboy.pressed(A_BUTTON) || arduboy.pressed(B_BUTTON);: This line checks if either button A or B is pressed on the Arduboy.
- if (pad2 == true && oldpad2 == false && released): This line checks if either button A or B has been pressed for the first time since the last check and if the ball has been released.
- arduboy.fillRect(52, 45, 30, 11, 0);: This line clears the "PAUSE" text from the screen.
- paused=false;: This line sets the paused variable to false, indicating that the game should resume.
- oldpad2 = pad2;: This line updates the oldpad2 variable for the next iteration of the loop.
14 PART
- void Score(): This line indicates the start of the Score() function.
- score += (level*10);: This line increments the score variable by a value determined by the level.
15 PART
- void newLevel(): This line indicates the start of the newLevel() function.
- arduboy.drawRect(xPaddle, 63, 11, 1, 0);: This line undraws the paddle by drawing a black rectangle over it.
- arduboy.drawPixel(xb, yb, 0);: These lines undraw the ball by setting the pixels representing the ball to black (0).
- xPaddle = 54; yb = 60; brickCount = 0; released = false;: These lines reset various game variables to their initial values for a new level.
- for (byte row = 0; row < 4; row++) { ... }: This block of code iterates over the rows and columns of bricks, resetting their status and redrawing them.
16 PART
- boolean pollFireButton(int n): This line indicates the start of the pollFireButton() function, which takes an integer argument n.
- for(int i = 0; i < n; i++) { ... }: This block of code introduces a loop that runs n times, introducing a short delay and checking if either button A or B is pressed.
- return false;: If no button press is detected after the loop, this line returns false.
17 PART
- boolean displayHighScores(byte file): This line indicates the start of the displayHighScores() function, which takes a byte argument file.
- for(int i = 0; i < 7; i++) { ... }: This block of code iterates over the high score entries stored in EEPROM, reading and displaying them on the screen.
- if (pollFireButton(300)) { return true; }: This line checks if either button A or B is pressed within 300 iterations of the pollFireButton() function, returning true if a button press is detected.
- return false;: If no button press is detected, this line returns false.
18 PART
- boolean titleScreen(): This line indicates the start of the titleScreen() function.
- arduboy.clear();: This line clears the screen.
- arduboy.setCursor(16,22);: This line sets the cursor position to (16,22) on the Arduboy screen.
- arduboy.setTextSize(2);: This line sets the text size to 2, making the text larger.
- arduboy.print("BREAKOUT");: This line prints the text "BREAKOUT" at the cursor position set in the previous line.
- arduboy.setTextSize(1);: This line resets the text size to 1.
- arduboy.display();: This line updates the display to show the changes made by the drawing functions.
- if (pollFireButton(25)) { return true; }: This line checks if either button A or B is pressed within 25 iterations of the pollFireButton() function, returning true if a button press is detected.
- for(byte i = 0; i < 5; i++) { ... }: This block of code introduces a loop that runs 5 times, flashing the text "PRESS FIRE!" on the screen.
- arduboy.print("PRESS FIRE!");: This line prints the text "PRESS FIRE!" at the specified cursor position.
- if (pollFireButton(50)) { return true; }: This line checks if either button A or B is pressed within 50 iterations of the pollFireButton() function, returning true if a button press is detected.
- arduboy.print(" ");: This line clears the "PRESS FIRE!" text from the screen.
19 PART
- void enterInitials(): This line indicates the start of the enterInitials() function.
- arduboy.clear();: This line clears the screen.
- initials[0] = ' '; initials[1] = ' '; initials[2] = ' ';: This line initializes the initials array with empty spaces.
- while (true) { ... }: This block of code introduces an infinite loop for entering initials.
- arduboy.display(); arduboy.clear();: This line updates the display and clears the screen for each iteration of the loop.
- if (arduboy.pressed(LEFT_BUTTON) || arduboy.pressed(B_BUTTON)) { ... }: This block of code checks if the left button or button B is pressed, allowing the player to move the cursor to the left to select initials.
- if (arduboy.pressed(RIGHT_BUTTON)) { ... }: This block of code checks if the right button is pressed, allowing the player to move the cursor to the right to select initials.
- if (arduboy.pressed(UP_BUTTON)) { ... }: This block of code checks if the up button is pressed, allowing the player to increment the selected initial.
- if (arduboy.pressed(DOWN_BUTTON)) { ... }: This block of code checks if the down button is pressed, allowing the player to decrement the selected initial.
- if (arduboy.pressed(A_BUTTON)) { ... }: This block of code checks if button A is pressed, allowing the player to confirm the current initial and move to the next position
20 PART
- void enterHighScore(byte file): This line indicates the start of the enterHighScore() function, which takes a byte parameter file.
- int address = file * 7 * 5 + EEPROM_STORAGE_SPACE_START;: This line calculates the EEPROM address where high scores are stored based on the file number.
- for(byte i = 0; i < 7; i++) { ... }: This loop iterates over the high score slots stored in EEPROM.
- if ((hi == 0xFF) && (lo == 0xFF)) { ... } else { ... }: This conditional block checks if the current high score slot is empty or contains a score.
- if (score > tmpScore) { ... }: This line checks if the current score is higher than the temporary score, indicating a new high score.
- enterInitials();: This line calls the enterInitials() function to allow the player to input their initials for the new high score.
- for(byte j = i; j < 7; j++) { ... }: This nested loop shifts the existing high scores down to make room for the new high score.
- EEPROM.update(...): This line updates the EEPROM with the new high score and initials.
- score = 0; initials[0] = ' '; initials[1] = ' '; initials[2] = ' ';: This line resets the score and initials after updating the EEPROM.
21 PART
- void playTone(unsigned int frequency, unsigned int duration): This line indicates the start of the playTone() function, which plays a tone.
- beep.tone(beep.freq(frequency), duration / (1000 / FRAME_RATE));: This line generates a tone with the specified frequency and duration using the BeepPin1 library.
22 PART
- void playToneTimed(unsigned int frequency, unsigned int duration): This line indicates the start of the playToneTimed() function, which plays a timed tone.
- beep.tone(beep.freq(frequency));: This line generates a tone with the specified frequency using the BeepPin1 library.
- arduboy.delayShort(duration);: This line introduces a delay for the specified duration.
- beep.noTone();: This line stops the tone generation after the specified duration.
Programming the MCU
- Arduino IDE: First, we downloaded and installed the Arduino IDE from the official Arduino website. We made sure to choose the appropriate version for our operating system.
- Connect Arduino Nano: Using a USB cable, we connected our Arduino Nano to our computer.
- Board Selection: In the Arduino IDE, we navigated to Tools > Board and selected "Arduino Nano" as our board.
- Processor Selection: Under Tools > Processor, we selected the appropriate processor. In the case of the Arduino Nano, it usually uses the ATmega328P processor. However, for newer Nano variants like the Nano 33 IoT, the processor may differ.
- Driver Installation: Since we're using Windows, we needed to install drivers for the USB-to-serial converter chip on the Arduino Nano. Typically, Nanos use chips like CH340 or CP210x. We installed the CH340 drivers from the manufacturer's website.
- Port Selection: After connecting the Nano, we went to Tools > Port and selected the port to which the Nano was connected. To identify the correct port, we checked before and after connecting the Nano to see which port appeared.
- Writing and Uploading Code: Next, we wrote our code in the Arduino IDE. Once ready, we clicked the "Upload" button (right arrow) in the IDE toolbar. The IDE compiled the code and uploaded it to the Arduino Nano. We monitored the progress and status of the upload in the console.
- Verification: Upon successful upload, we received a message indicating the successful completion. At this point, our code was running on the Arduino Nano.
- Debugging : our code didn't work as expected, we used techniques like serial output (Serial.print) to debug. This involved printing debug messages to the Arduino IDE's Serial Monitor to identify and resolve issues in our code.
Code
#include <Arduboy2.h>
// block in EEPROM to save high scores
#define EE_FILE 2
Arduboy2 arduboy;
BeepPin1 beep;
const unsigned int FRAME_RATE = 40; // Frame rate in frames per second
const unsigned int COLUMNS = 13; //Columns of bricks
const unsigned int ROWS = 4; //Rows of bricks
int dx = -1; //Initial movement of ball
int dy = -1; //Initial movement of ball
int xb; //Balls starting possition
int yb; //Balls starting possition
boolean released; //If the ball has been released by the player
boolean paused = false; //If the game has been paused
byte xPaddle; //X position of paddle
boolean isHit[ROWS][COLUMNS]; //Array of if bricks are hit or not
boolean bounced=false; //Used to fix double bounce glitch
byte lives = 3; //Amount of lives
byte level = 1; //Current level
unsigned int score=0; //Score for the game
unsigned int brickCount; //Amount of bricks hit
boolean pad, pad2, pad3; //Button press buffer used to stop pause repeating
boolean oldpad, oldpad2, oldpad3;
char text_buffer[16]; //General string buffer
boolean start=false; //If in menu or in game
boolean initialDraw=false;//If the inital draw has happened
char initials[3]; //Initials used in high score
//Ball Bounds used in collision detection
byte leftBall;
byte rightBall;
byte topBall;
byte bottomBall;
//Brick Bounds used in collision detection
byte leftBrick;
byte rightBrick;
byte topBrick;
byte bottomBrick;
byte tick;
void setup()
{
arduboy.begin();
beep.begin();
arduboy.setFrameRate(FRAME_RATE);
arduboy.initRandomSeed();
}
void loop()
{
// pause render until it's time for the next frame
if (!(arduboy.nextFrame()))
return;
// Handle the timing and stopping of tones
beep.timer();
//Title screen loop switches from title screen
//and high scores until FIRE is pressed
while (!start)
{
start = titleScreen();
if (!start)
{
start = displayHighScores(EE_FILE);
}
}
//Initial level draw
if (!initialDraw)
{
//Clears the screen
arduboy.clear();
//Selects Font
//Draws the new level
level = 1;
newLevel();
score = 0;
initialDraw=true;
}
if (lives>0)
{
drawPaddle();
//Pause game if FIRE pressed
pad = arduboy.pressed(A_BUTTON) || arduboy.pressed(B_BUTTON);
if(pad == true && oldpad == false && released)
{
oldpad2 = false; //Forces pad loop 2 to run once
pause();
}
oldpad = pad;
drawBall();
if(brickCount == ROWS * COLUMNS)
{
level++;
newLevel();
}
}
else
{
drawGameOver();
if (score > 0)
{
enterHighScore(EE_FILE);
}
arduboy.clear();
initialDraw=false;
start=false;
lives=3;
newLevel();
}
arduboy.display();
}
void movePaddle()
{
//Move right
if(xPaddle < WIDTH - 12)
{
if (arduboy.pressed(RIGHT_BUTTON))
{
xPaddle+=2;
}
}
//Move left
if(xPaddle > 0)
{
if (arduboy.pressed(LEFT_BUTTON))
{
xPaddle-=2;
}
}
}
void moveBall()
{
tick++;
if(released)
{
//Move ball
if (abs(dx)==2) {
xb += dx/2;
// 2x speed is really 1.5 speed
if (tick%2==0)
xb += dx/2;
} else {
xb += dx;
}
yb=yb + dy;
//Set bounds
leftBall = xb;
rightBall = xb + 2;
topBall = yb;
bottomBall = yb + 2;
//Bounce off top edge
if (yb <= 0)
{
yb = 2;
dy = -dy;
playTone(523, 250);
}
//Lose a life if bottom edge hit
if (yb >= 64)
{
arduboy.drawRect(xPaddle, 63, 11, 1, 0);
xPaddle = 54;
yb=60;
released = false;
lives--;
playToneTimed(175, 500);
if (random(0, 2) == 0)
{
dx = 1;
}
else
{
dx = -1;
}
}
//Bounce off left side
if (xb <= 0)
{
xb = 2;
dx = -dx;
playTone(523, 250);
}
//Bounce off right side
if (xb >= WIDTH - 2)
{
xb = WIDTH - 4;
dx = -dx;
playTone(523, 250);
}
//Bounce off paddle
if (xb+1>=xPaddle && xb<=xPaddle+12 && yb+2>=63 && yb<=64)
{
dy = -dy;
dx = ((xb-(xPaddle+6))/3); //Applies spin on the ball
// prevent straight bounce
if (dx == 0) {
dx = (random(0,2) == 1) ? 1 : -1;
}
playTone(200, 250);
}
//Bounce off Bricks
for (byte row = 0; row < ROWS; row++)
{
for (byte column = 0; column < COLUMNS; column++)
{
if (!isHit[row][column])
{
//Sets Brick bounds
leftBrick = 10 * column;
rightBrick = 10 * column + 10;
topBrick = 6 * row + 1;
bottomBrick = 6 * row + 7;
//If A collison has occured
if (topBall <= bottomBrick && bottomBall >= topBrick &&
leftBall <= rightBrick && rightBall >= leftBrick)
{
Score();
brickCount++;
isHit[row][column] = true;
arduboy.drawRect(10*column, 2+6*row, 8, 4, 0);
//Vertical collision
if (bottomBall > bottomBrick || topBall < topBrick)
{
//Only bounce once each ball move
if(!bounced)
{
dy =- dy;
yb += dy;
bounced = true;
playTone(261, 250);
}
}
//Hoizontal collision
if (leftBall < leftBrick || rightBall > rightBrick)
{
//Only bounce once brick each ball move
if(!bounced)
{
dx =- dx;
xb += dx;
bounced = true;
playTone(261, 250);
}
}
}
}
}
}
//Reset Bounce
bounced = false;
}
else
{
//Ball follows paddle
xb=xPaddle + 5;
//Release ball if FIRE pressed
pad3 = arduboy.pressed(A_BUTTON) || arduboy.pressed(B_BUTTON);
if (pad3 == true && oldpad3 == false)
{
released = true;
//Apply random direction to ball on release
if (random(0, 2) == 0)
{
dx = 1;
}
else
{
dx = -1;
}
//Makes sure the ball heads upwards
dy = -1;
}
oldpad3 = pad3;
}
}
void drawBall()
{
// arduboy.setCursor(0,0);
// arduboy.print(arduboy.cpuLoad());
// arduboy.print(" ");
arduboy.drawPixel(xb, yb, 0);
arduboy.drawPixel(xb+1, yb, 0);
arduboy.drawPixel(xb, yb+1, 0);
arduboy.drawPixel(xb+1, yb+1, 0);
moveBall();
arduboy.drawPixel(xb, yb, 1);
arduboy.drawPixel(xb+1, yb, 1);
arduboy.drawPixel(xb, yb+1, 1);
arduboy.drawPixel(xb+1, yb+1, 1);
}
void drawPaddle()
{
arduboy.drawRect(xPaddle, 63, 11, 1, 0);
movePaddle();
arduboy.drawRect(xPaddle, 63, 11, 1, 1);
}
void drawGameOver()
{
arduboy.drawPixel(xb, yb, 0);
arduboy.drawPixel(xb+1, yb, 0);
arduboy.drawPixel(xb, yb+1, 0);
arduboy.drawPixel(xb+1, yb+1, 0);
arduboy.setCursor(37, 42);
arduboy.print("Game Over");
arduboy.setCursor(31, 56);
arduboy.print("Score: ");
arduboy.print(score);
arduboy.display();
arduboy.delayShort(4000);
}
void pause()
{
paused = true;
//Draw pause to the screen
arduboy.setCursor(52, 45);
arduboy.print("PAUSE");
arduboy.display();
while (paused)
{
arduboy.delayShort(150);
//Unpause if FIRE is pressed
pad2 = arduboy.pressed(A_BUTTON) || arduboy.pressed(B_BUTTON);
if (pad2 == true && oldpad2 == false && released)
{
arduboy.fillRect(52, 45, 30, 11, 0);
paused=false;
}
oldpad2 = pad2;
}
}
void Score()
{
score += (level*10);
}
void newLevel(){
//Undraw paddle
arduboy.drawRect(xPaddle, 63, 11, 1, 0);
//Undraw ball
arduboy.drawPixel(xb, yb, 0);
arduboy.drawPixel(xb+1, yb, 0);
arduboy.drawPixel(xb, yb+1, 0);
arduboy.drawPixel(xb+1, yb+1, 0);
//Alter various variables to reset the game
xPaddle = 54;
yb = 60;
brickCount = 0;
released = false;
//Draws new bricks and resets their values
for (byte row = 0; row < 4; row++) {
for (byte column = 0; column < 13; column++)
{
isHit[row][column] = false;
arduboy.drawRect(10*column, 2+6*row, 8, 4, 1);
}
}
arduboy.display();
}
//Used to delay images while reading button input
boolean pollFireButton(int n)
{
for(int i = 0; i < n; i++)
{
arduboy.delayShort(15);
pad = arduboy.pressed(A_BUTTON) || arduboy.pressed(B_BUTTON);
if(pad == true && oldpad == false)
{
oldpad3 = true; //Forces pad loop 3 to run once
return true;
}
oldpad = pad;
}
return false;
}
//Function by nootropic design to display highscores
boolean displayHighScores(byte file)
{
byte y = 8;
byte x = 24;
// Each block of EEPROM has 7 high scores, and each high score entry
// is 5 bytes long: 3 bytes for initials and two bytes for score.
int address = file * 7 * 5 + EEPROM_STORAGE_SPACE_START;
byte hi, lo;
arduboy.clear();
arduboy.setCursor(32, 0);
arduboy.print("HIGH SCORES");
arduboy.display();
for(int i = 0; i < 7; i++)
{
sprintf(text_buffer, "%2d", i+1);
arduboy.setCursor(x,y+(i*8));
arduboy.print(text_buffer);
arduboy.display();
hi = EEPROM.read(address + (5*i));
lo = EEPROM.read(address + (5*i) + 1);
if ((hi == 0xFF) && (lo == 0xFF))
{
score = 0;
}
else
{
score = (hi << 8) | lo;
}
initials[0] = (char)EEPROM.read(address + (5*i) + 2);
initials[1] = (char)EEPROM.read(address + (5*i) + 3);
initials[2] = (char)EEPROM.read(address + (5*i) + 4);
if (score > 0)
{
sprintf(text_buffer, "%c%c%c %u", initials[0], initials[1], initials[2], score);
arduboy.setCursor(x + 24, y + (i*8));
arduboy.print(text_buffer);
arduboy.display();
}
}
if (pollFireButton(300))
{
return true;
}
return false;
arduboy.display();
}
boolean titleScreen()
{
//Clears the screen
arduboy.clear();
arduboy.setCursor(16,22);
arduboy.setTextSize(2);
arduboy.print("BREAKOUT");
arduboy.setTextSize(1);
arduboy.display();
if (pollFireButton(25))
{
return true;
}
//Flash "Press FIRE" 5 times
for(byte i = 0; i < 5; i++)
{
//Draws "Press FIRE"
arduboy.setCursor(31, 53);
arduboy.print("PRESS FIRE!");
arduboy.display();
if (pollFireButton(50))
{
return true;
}
//Removes "Press FIRE"
arduboy.setCursor(31, 53);
arduboy.print(" ");
arduboy.display();
if (pollFireButton(25))
{
return true;
}
}
return false;
}
//Function by nootropic design to add high scores
void enterInitials()
{
byte index = 0;
arduboy.clear();
initials[0] = ' ';
initials[1] = ' ';
initials[2] = ' ';
while (true)
{
arduboy.display();
arduboy.clear();
arduboy.setCursor(16,0);
arduboy.print("HIGH SCORE");
sprintf(text_buffer, "%u", score);
arduboy.setCursor(88, 0);
arduboy.print(text_buffer);
arduboy.setCursor(56, 20);
arduboy.print(initials[0]);
arduboy.setCursor(64, 20);
arduboy.print(initials[1]);
arduboy.setCursor(72, 20);
arduboy.print(initials[2]);
for(byte i = 0; i < 3; i++)
{
arduboy.drawLine(56 + (i*8), 27, 56 + (i*8) + 6, 27, 1);
}
arduboy.drawLine(56, 28, 88, 28, 0);
arduboy.drawLine(56 + (index*8), 28, 56 + (index*8) + 6, 28, 1);
arduboy.delayShort(70);
if (arduboy.pressed(LEFT_BUTTON) || arduboy.pressed(B_BUTTON))
{
if (index > 0)
{
index--;
playToneTimed(1046, 80);
}
}
if (arduboy.pressed(RIGHT_BUTTON))
{
if (index < 2)
{
index++;
playToneTimed(1046, 80);
}
}
if (arduboy.pressed(UP_BUTTON))
{
initials[index]++;
playToneTimed(523, 80);
// A-Z 0-9 :-? !-/ ' '
if (initials[index] == '0')
{
initials[index] = ' ';
}
if (initials[index] == '!')
{
initials[index] = 'A';
}
if (initials[index] == '[')
{
initials[index] = '0';
}
if (initials[index] == '@')
{
initials[index] = '!';
}
}
if (arduboy.pressed(DOWN_BUTTON))
{
initials[index]--;
playToneTimed(523, 80);
if (initials[index] == ' ') {
initials[index] = '?';
}
if (initials[index] == '/') {
initials[index] = 'Z';
}
if (initials[index] == 31) {
initials[index] = '/';
}
if (initials[index] == '@') {
initials[index] = ' ';
}
}
if (arduboy.pressed(A_BUTTON))
{
playToneTimed(1046, 80);
if (index < 2)
{
index++;
} else {
return;
}
}
}
}
void enterHighScore(byte file)
{
// Each block of EEPROM has 7 high scores, and each high score entry
// is 5 bytes long: 3 bytes for initials and two bytes for score.
int address = file * 7 * 5 + EEPROM_STORAGE_SPACE_START;
byte hi, lo;
char tmpInitials[3];
unsigned int tmpScore = 0;
// High score processing
for(byte i = 0; i < 7; i++)
{
hi = EEPROM.read(address + (5*i));
lo = EEPROM.read(address + (5*i) + 1);
if ((hi == 0xFF) && (lo == 0xFF))
{
// The values are uninitialized, so treat this entry
// as a score of 0.
tmpScore = 0;
} else
{
tmpScore = (hi << 8) | lo;
}
if (score > tmpScore)
{
enterInitials();
for(byte j = i; j < 7; j++)
{
hi = EEPROM.read(address + (5*j));
lo = EEPROM.read(address + (5*j) + 1);
if ((hi == 0xFF) && (lo == 0xFF))
{
tmpScore = 0;
}
else
{
tmpScore = (hi << 8) | lo;
}
tmpInitials[0] = (char)EEPROM.read(address + (5*j) + 2);
tmpInitials[1] = (char)EEPROM.read(address + (5*j) + 3);
tmpInitials[2] = (char)EEPROM.read(address + (5*j) + 4);
// write score and initials to current slot
EEPROM.update(address + (5*j), ((score >> 8) & 0xFF));
EEPROM.update(address + (5*j) + 1, (score & 0xFF));
EEPROM.update(address + (5*j) + 2, initials[0]);
EEPROM.update(address + (5*j) + 3, initials[1]);
EEPROM.update(address + (5*j) + 4, initials[2]);
// tmpScore and tmpInitials now hold what we want to
//write in the next slot.
score = tmpScore;
initials[0] = tmpInitials[0];
initials[1] = tmpInitials[1];
initials[2] = tmpInitials[2];
}
score = 0;
initials[0] = ' ';
initials[1] = ' ';
initials[2] = ' ';
return;
}
}
}
// Play a tone at the specified frequency for the specified duration.
void playTone(unsigned int frequency, unsigned int duration)
{
beep.tone(beep.freq(frequency), duration / (1000 / FRAME_RATE));
}
// Play a tone at the specified frequency for the specified duration using
// a delay to time the tone.
// Used when beep.timer() isn't being called.
void playToneTimed(unsigned int frequency, unsigned int duration)
{
beep.tone(beep.freq(frequency));
arduboy.delayShort(duration);
beep.noTone();
}