Adafruit_Learning_System_Gu.../PyGamer_Bounce_Game/bounce/bounce.ino
2021-10-18 13:33:39 -04:00

636 lines
17 KiB
C++

// Adafruit Arcada based Level Bounce Game
//
// Anne Barela, June 3, 2019 for Adafruit Industries
//
// Mod of game by R0D0T posted on http://r0d0t.tumblr.com/post/29641975900 and
// Hackaday http://hackaday.com/2012/10/01/fantastic-programming-makes-this-arduino-gaming-device-something-special/
// which was published for Arduino Esplora by Anne Barela 2013
#include <Adafruit_Arcada.h>
#include <Adafruit_Arcada_Def.h>
#include <Adafruit_NeoPixel.h>
#include "logo.h"
Adafruit_Arcada arcada; // Set up the Arcada code
Adafruit_NeoPixel strip(5, 8, NEO_GRB + NEO_KHZ800); // 5 NeoPixels are on D8
// SCREEN
#define s_width 128
#define s_height 139
#define textline1 (s_height + 3)
#define textline2 (s_height + 13)
unsigned int background = ARCADA_BLACK;
long lastMillis;
// SOUND
#define sound_pin A0 // Direct sound
uint8_t sound_on = 1; // start with sound on
char speaker_icon = 0xEC; // icon to show sound off
// PLAYER
float p_X = s_width/2;
float p_Y = s_height/2;
float p_lastX, p_vX;
float p_lastY, p_vY;
//#define p_vmaxY 6
//#define p_vmaxX 6
#define gravity_default 0.2
float gravity = 0.2;
#define p_bounceSpeed_default 4
byte p_bounceSpeed = p_bounceSpeed_default;
#define p_width_default 4
byte p_width = p_width_default;
#define p_height_default 4
byte p_height = p_height_default;
#define p_color_default ARCADA_WHITE
#define p_color_dead ARCADA_RED
int p_color = p_color_default;
// SCORE & BONUS
unsigned int score = 0;
unsigned int highscore = 0;
byte nextBonus;
boolean b_pendingPlatform = false;
boolean b_used;
byte b_platformID;
byte b_pendingID;
byte b_onUseID;
byte b_remaining = 0;
byte b_max;
#define b_number 4
int b_colors[b_number] = {
ARCADA_ORANGE, 0x0F00, 0xF000, 0x00FF};
byte b_lives = 0;
byte num_lives = 5;
// WORLD
#define w_size 8
byte world[w_size][6]; // contains platforms data
int w_color = ARCADA_YELLOW; // Platform default color
byte w_speed = 1;
byte w_widthMin = 4;
byte w_widthMax = 8;
// ACCELEROMETER
int acc_avgX, acc_avgY, acc_avgZ;
sensors_event_t event;
#define acc_pinX 1
#define acc_pinY 2
#define acc_pinZ 3
// NeoPixels
#define RED 0xFF0000
#define YELLOW 0xFFFF00
#define GREEN 0x00FF00
#define BLACK 0x000000
void setup() {
Serial.begin(9600);
// initialize
Serial.println("Hello! Arcada version of game");
if (!arcada.arcadaBegin()) {
Serial.print("Failed to begin");
strip.fill(RED);
strip.show();
while (1);
}
arcada.displayBegin(); // Initialize display code
arcada.setBacklight(0); // Initial display off
arcada.display->setRotation(0); // Rotate to portrait
arcada.display->fillScreen(background); // Clear screen if necessary
arcada.display->setTextColor(ARCADA_GREEN, ARCADA_BLACK);
arcada.display->setTextSize(2);
arcada.display->println(" ");
arcada.display->println(" Bounce!");
if( !arcada.hasAccel() ) {
strip.fill(YELLOW);
strip.show();
arcada.haltBox("An accelerometer is required for this gamne");
}
// Set up the logo bitmap
int logo_origin_x = (128 - 2*LOGO_WIDTH ) / 2;
int logo_origin_y = (160 - 2*LOGO_HEIGHT) / 2;
for(int y=0; y<LOGO_HEIGHT; y++) {
for(int x=0; x<LOGO_WIDTH; x++) {
uint8_t c = logo_mask[y][x / 8];
if(c & (0x80 >> (x & 7))) {
int xx = logo_origin_x+2*x;
int yy = logo_origin_y+2*y;
arcada.display->drawPixel(xx, yy, ARCADA_WHITE);
arcada.display->drawPixel(xx+1, yy, ARCADA_WHITE);
arcada.display->drawPixel(xx, yy+1, ARCADA_WHITE);
arcada.display->drawPixel(xx+1, yy+1, ARCADA_WHITE);
}
}
}
arcada.display->println(" ");
arcada.display->println(" ");
arcada.display->println(" ");
arcada.display->println(" ");
arcada.display->println(" ");
arcada.display->setTextColor(ARCADA_ORANGE, ARCADA_BLACK);
arcada.display->setTextSize(1);
arcada.display->println(" ");
arcada.display->println(" Adafruit");
arcada.display->println(" Industries");
for (int i=0; i<220; i++) { // Display initial text
arcada.setBacklight(i);
delay(14);
}
arcada.setBacklight(250);
arcada.display->setTextColor(ST7735_WHITE, ST7735_BLACK);
arcada.display->println(" ");
arcada.display->println(" ");
arcada.display->println(" Press Start");
while(!(arcada.readButtons() & ARCADA_BUTTONMASK_START)) ; // wait for start button
arcada.display->fillScreen(background); // Clear screen
arcada.enableSpeaker(1); // Enable the speaker and play opening tones
pinMode(sound_pin, OUTPUT);
beep(sound_pin, 880, 100);
delay(100);
beep(sound_pin, 1060, 120);
delay(100);
beep(sound_pin, 1800, 100);
// NeoPixel setup
strip.begin();
strip.setBrightness(40); // not too bright
strip.show();
// Game setup
randomSeed(millis());
FillWorld();
// Get average accelerations
acc_avgX = acc_avg(acc_pinX);
acc_avgY = acc_avg(acc_pinY);
acc_avgZ = acc_avg(acc_pinZ);
ScoreSetup();
}
void loop() {
if(millis()-lastMillis > 20){
lastMillis = millis();
CheckButtons();
MovePlayer();
ScrollWorld();
CollideBorders();
CollideWorld();
DrawWorld();
DrawPlayer();
}
}
int acc_avg(int pin) {
int avg = 0;
arcada.accel->getEvent(&event);
switch (pin) {
case acc_pinX:
for(int i = 0; i < 50; i++) {
avg += event.acceleration.x;
// delay(10);
}
break;
case acc_pinY:
for(int i = 0; i < 50; i++) {
avg += event.acceleration.y;
// delay(10);
}
break;
case acc_pinZ:
for(int i = 0; i < 50; i++) {
avg += event.acceleration.z;
// delay(10);
}
break;
}
avg /= 50;
return avg;
}
int acc_readX() {
int val;
arcada.accel->getEvent(&event);
val = -1 * constrain((event.acceleration.y - acc_avgY)*7, -64, 64); // use Y for orientation
// Serial.print("Acceleration: "); // joystick is at top
// Serial.println(val);
return(val);
}
void CheckBonus(byte thisPlatform){
if(b_pendingPlatform && (b_platformID == thisPlatform)){
b_pendingPlatform = false;
}
if(nextBonus ){
nextBonus--;
}
else {
nextBonus = random(30, 60);
b_pendingID = random(0, b_number);
b_pendingPlatform = true;
b_platformID = thisPlatform;
}
}
void BonusReset(){
nextBonus = random(30, 60);
b_pendingPlatform = false;
b_remaining = 0;
arcada.display->fillRect(0, s_height+2, 96, 10, background);
arcada.display->fillRect(0, s_height+1, 96, 1, w_color);
EndUseBonus();
}
void ClearBonus(){
EndUseBonus();
b_remaining = 0;
p_color = p_color_default;
arcada.display->fillRect(0, s_height+2, 96, 10, background);
arcada.display->fillRect(0, s_height+1, s_width-1, 1, w_color);
b_used = false;
}
void GetBonus(){
ClearBonus();
b_onUseID = b_pendingID;
b_pendingPlatform = false;
beep(sound_pin, 3500, 50); // SOUND
p_color = b_colors[b_onUseID];
arcada.display->fillRect(0, s_height+2, 96, 10, background);
switch (b_onUseID){
case 0:
drawString(1, textline1, "Jet pack", b_colors[b_onUseID], 1);
b_max = 100;
b_remaining = b_max;
break;
case 1:
drawString(1, textline1, "Frog legs", b_colors[b_onUseID], 1);
b_max = 100;
b_remaining = b_max;
break;
case 2:
drawString(1, textline1, "Boost", b_colors[b_onUseID], 1);
b_max = 1;
b_remaining = b_max;
break;
case 3:
drawString(1, textline1, "Low gravity", b_colors[b_onUseID], 1);
b_max = 255;
b_remaining = b_max;
break;
}
if(b_remaining > 1) {
arcada.display->fillRect(95, s_height+2, 1, 10, b_colors[b_onUseID]);
arcada.display->fillRect( 0, s_height+1, 95, 1, b_colors[b_onUseID]);
}
}
void UseBonus(){
if(b_remaining){
b_used = true;
arcada.display->fillRect(b_remaining*95/b_max, s_height+2, 1, 10, background);
arcada.display->drawPixel(b_remaining*95/b_max, s_height+1, w_color);
b_remaining --;
arcada.display->fillRect(b_remaining*95/b_max, s_height+2, 1, 10, b_colors[b_onUseID]);
arcada.display->drawPixel(b_remaining*95/b_max, s_height+1, b_colors[b_onUseID]);
switch (b_onUseID){
case 0:
if(p_vY > -1){
p_vY -= 0.3;
}
beep(sound_pin, 100, 3 + abs(10 * p_vY)); // SOUND
break;
case 1:
p_bounceSpeed = p_bounceSpeed_default * 1.5;
break;
case 2:
p_width = min(p_width+1, p_width_default+12);
p_height = min(p_height+1, p_height_default+12);
b_lives = min(b_lives+1, 12);
beep(sound_pin, 50+150*b_lives, 100); // SOUND
break;
case 3:
gravity = gravity_default * 0.3;
break;
}
if(!b_remaining){
ClearBonus();
}
}
}
void EndUseBonus(){
b_used = false;
switch (b_onUseID){
case 0:
break;
case 1:
p_bounceSpeed = p_bounceSpeed_default;
break;
case 2:
break;
case 3:
gravity = gravity_default;
break;
}
}
void CheckButtons(){
uint32_t pressed_buttons;
pressed_buttons = arcada.readButtons();
if(pressed_buttons & ARCADA_BUTTONMASK_START) { // pause
Serial.print("START");
delay(500);
while(!(arcada.readButtons())) { // wait until any button is pressed again
strip.setPixelColor(0, GREEN);
strip.show();
delay(100);
strip.setPixelColor(0, BLACK);
strip.show();
delay(100);
}
pressed_buttons = 0;
return;
}
if(pressed_buttons & ARCADA_BUTTONMASK_B) { // use bonus
Serial.print("B");
if(b_used = true) {
EndUseBonus();
} else {
UseBonus();
}
return;
}
if(pressed_buttons & ARCADA_BUTTONMASK_SELECT) { // SELECT toggles sound on/off
Serial.print("SELECT");
if(sound_on) {
sound_on = 0;
drawChar(s_width-6, textline2, speaker_icon, ARCADA_RED); // Put character in lower right corner to indicate speaker off
} else {
sound_on = 1;
arcada.display->fillRect( s_width-6, textline2, s_width-1, textline2+9, background); // Blank lower right character = sound on
}
}
}
void DrawPlayer(){
CheckButtons(); // check more frequently
arcada.display->fillRect(p_lastX, p_lastY, p_width, p_height, background); // erase previous pos
if(p_lastX > (s_width-1 - p_width)) // if across the edge of the screen
arcada.display->fillRect(0, p_lastY, p_lastX + p_width - s_width, p_height, background);
arcada.display->fillRect(p_X, p_Y, p_width, p_height, p_color); // draw new pos
if(p_X > (s_width-1 - p_width)) // if across the edge of the screen
arcada.display->fillRect(0, p_Y, p_X + p_width - s_width, p_height, p_color);
}
void MovePlayer(){
p_lastX = p_X;
p_lastY = p_Y;
p_vX = (p_vX + acc_readX()) / 20;
p_vY = (p_vY + gravity);
p_X = p_X + p_vX;
if(p_X > s_width){
p_X = 0;
}
if(p_X < 0){
p_X = s_width - p_X;
}
p_Y = p_Y + p_vY;
if((p_vY < -4) && (p_color != p_color_dead)){
beep(sound_pin, 600+p_vY*100, 25); // SOUND
}
if(b_used && (b_onUseID == 3)) { // if low gravity is on use
beep(sound_pin, 300-abs(p_vY*100), 25); // SOUND
}
}
void CollideBorders() {
if(p_Y > s_height - p_height){ // touch the floor
p_vY = -p_bounceSpeed;
p_Y = s_height - p_height;
if(!b_lives){
if(p_color != p_color_dead){
BonusReset();
ScoreReset();
}
}
else {
arcada.display->fillRect(p_lastX, p_lastY, p_width, p_height, background);
p_width--;
p_height--;
b_lives--;
beep(sound_pin, 100+150*b_lives, 100); // SOUND
}
}
}
void ScoreSetup(){
arcada.display->fillRect(0, s_height+2, 128, 10, background);
arcada.display->fillRect(0, s_height+1, 128, 1, w_color);
// highscore = EEPROM.read(0);
// highscore += (EEPROM.read(1)<<8);
if(highscore > 64000){
highscore = 0;
}
drawString(1, textline2, "High score:", w_color, 1);
drawInt(highscore, 66, textline2, w_color, background);
}
void ScoreReset(){
if(p_color != p_color_dead) {
p_color = p_color_dead;
beep(sound_pin, 50, 500); // SOUND
if(score > highscore){
if(score < 64000) {
// EEPROM.write(0, score);
// EEPROM.write(1, score>>8);
}
else { // bad value, zero out
// EEPROM.write(0,0);
// EEPROM.write(1,0);
}
highscore = score;
flashMessage();
// drawInt(score, 66, textline2, p_color_default, background);
}
else {
drawString(1, textline1, "Last score:", w_color, 1);
drawInt(score, 66, s_height+3, w_color, background);
}
score = 0;
drawInt(score, 97, textline1, w_color, background);
}
}
void flashMessage() {
uint8_t i;
arcada.display->fillRect( 0, textline2, s_width-10, textline2+9, background);
for(i=0; i<5; i++) {
drawString(1, textline2, "HIGHSCORE!", p_color_default, 1);
delay(400);
arcada.display->fillRect( 0, textline2, s_width-10, textline2+9, background);
delay(400);
}
drawString(1, textline2, "High score:", w_color, 1);
drawInt(highscore, 66, textline2, w_color, background);
}
void ScoreAdd(){ // add to the current score lower left
score++;
beep(sound_pin, 3000, 50); // SOUND
if(score > highscore) {
drawInt(score, 97, textline1, p_color_default, background);
}
else {
drawInt(score, 97, textline1, w_color, background);
}
w_widthMin = max(1, 4 - score/50);
w_widthMax = max(2, 8 - score/50);
if(score == 1) {
p_color = p_color_default;
}
}
// transform from int to string and display it
void drawInt(unsigned int num, byte nx, byte ny, unsigned int color, unsigned int color2) {
arcada.display->fillRect(nx, ny, 29, 7, color2);
drawChar(nx+24, ny, 48+(num%10), color);
if(num > 9) {
drawChar(nx+18, ny, 48+(num%100)/10, color);
if(num>99) {
drawChar(nx+12, ny, 48+(num%1000)/100, color);
if(num>999) {
drawChar(nx+6, ny, 48+(num%10000)/1000, color);
if(num>9999) {
drawChar(nx, ny, 48+(num%100000)/10000, color);
}
}
}
}
}
/*
world[a][b] ->
a -> platform id
b -> 0 = x
1 = y
2 = last x
3 = last y
4 = width
4 = last width
*/
void FillWorld(){
for(byte i=0; i<w_size; i++) {
world[i][0] = (s_width * (w_size-i-1) / w_size)/2;
world[i][1] = 1 + i * (s_height / w_size);
world[i][2] = world[i][0];
world[i][3] = world[i][1];
world[i][4] = s_width * (i+1)/w_size;
world[i][5] = world[i][4];
}
}
void ScrollWorld(){
byte fastScrolling = 0;
if(p_Y < s_height/4) { // if the player reaches the top of the screen
fastScrolling ++;
if(p_Y < s_height/8) { // if the player reaches the top of the screen
fastScrolling ++;
}
}
p_Y += fastScrolling;
for(byte i=0; i<w_size; i++) { // for each platform
world[i][3] = world[i][1]; // update last y
world[i][2] = world[i][0]; // update last x
world[i][5] = world[i][4]; // update last width
world[i][1] += w_speed ; // move the platform downward
world[i][1] += fastScrolling;
if(world[i][1] > s_height){ // if the platform reaches the bottom of the screen
//world[i][2] = world[i][0]; // update last x
world[i][1] = world[i][1] - s_height + 1; // put the platform on the top of the screen
world[i][4] = min(random(w_widthMin, w_widthMax)*p_width_default, s_width-world[i][0]); //pick a random width
world[i][0] = random(0, s_width-world[i][4]); // pick a random x
CheckBonus(i); // check if the new platform will have a bonus
}
}
}
void CollideWorld() {
for(byte i=0; i<w_size; i++) {
if(p_vY > 0){ // if player falling
if((p_Y+p_width <= world[i][1]) && (world[i][1] < (p_Y+p_vY+p_height)) ) { // and at the height of the platform
if(((p_X+p_width) >= world[i][0]) && (world[i][0]+world[i][4] > (p_X)) ) { // and does not miss it
p_vY = -p_bounceSpeed ;
p_Y = world[i][1] - p_height;
ScoreAdd();
if(b_pendingPlatform && (b_platformID == i)) {
GetBonus();
}
}
}
}
}
}
void DrawWorld() {
for(byte i=0; i<w_size; i++) {
arcada.display->fillRect(world[i][2], world[i][3], world[i][5], 1, background);
arcada.display->fillRect(world[i][0], world[i][1], world[i][4], 1, w_color);
}
if(b_pendingPlatform){
arcada.display->fillRect(world[b_platformID][0], world[b_platformID][1], world[b_platformID][4], 1, b_colors[b_pendingID]);
}
}
void drawString(byte x, byte y, char *text, uint16_t color, bool wrap) { // replicate tft.drawString
arcada.display->setCursor(x,y);
arcada.display->setTextColor(color);
arcada.display->setTextWrap(wrap);
arcada.display->print(text);
}
void drawChar(byte x, byte y, char text, uint16_t color) { // replicate tft.drawChar
arcada.display->setCursor(x,y);
arcada.display->setTextColor(color);
arcada.display->print(text);
Serial.println(text);
}
// A sound-producing function
void beep(unsigned char speakerPin, int frequencyInHertz, long timeInMilliseconds) {
// http://web.media.mit.edu/~leah/LilyPad/07_sound_code.html
int x;
if( sound_on ) { // Only play if the sound_on flag is 1
long delayAmount = (long)(1000000 / frequencyInHertz);
long loopTime = (long)((timeInMilliseconds * 1000) / (delayAmount * 2));
for (x = 0; x < loopTime; x++) {
digitalWrite(speakerPin, HIGH);
delayMicroseconds(delayAmount);
digitalWrite(speakerPin, LOW);
delayMicroseconds(delayAmount);
}
}
}