NIKOLA ANICIC 2019 0099 Arcade game: Rendezvous SW1: Clockwise rotation SW2: Counter-clockwise rotation POT1: Main engine POT2: Retrorockets

Dependencies:   mbed Adafruit_GFX

NIKOLA ANICIC 2019 0099

Arcade game: Rendezvous Controls: - SW1 (PC_9) : Clockwise rotation - SW2 (PC_8) : Counter-clockwise rotation - POT1 (PA_0) : Main engine - POT2 (PA_1) : Retrorockets

main.cpp

Committer:
an_thanik
Date:
2021-11-21
Revision:
2:d015424ca47e
Parent:
0:63446647fa0e

File content as of revision 2:d015424ca47e:

/* 
 * Nikola Aničić 2019/0099 
 * ETF Beograd
 * napisano 20/11/2021
 *
 * Zadatak 3.4 - Napraviti arkadnu igricu po zelji 
 *               Kao komande je moguće koristiti POT1, POT2, SW1, SW2. 
 *               Pozavršetku zadatka isti objaviti (Publish) i obavestiti predmetnog nastavnika.
 *
 *             - Ideja: Brod i letelica za sletanje treba da poklope vektore brzine
 *               i prispoje se. Letelica za sletanje na raspolaganju ima: 
 *                    -> kontrolu usmerenja (Rotacija:    POT1 ccw,       POT2 clockwise)
 *                    -> kontrolu pogona    (Brzina X, Y: SW1 retrograde, SW2 prograde)
 */

#include "mbed.h"
// Potrebne biblioteke za Adafruit
#include "Adafruit_GFX.h"
#include "Adafruit_GFX_Config.h"                                                /* Sve potrebne funkcionalnosti su otkomentarisane */
#include "Adafruit_SSD1306.h"    
// Custom headers za bitmape
#include "bitmaps.h"

// Program options - uncomment to enable
#define SHOW_SPLASH
// #define DEBUG_SPRITES
 
 // I2C bus                  
 #define SCL                 PB_13
 #define SDA                 PB_14
 // I2C address
 #define I2C_ADDRESS         0x3C
 #define I2C_ADDRESS_MBED    I2C_ADDRESS << 1
 // I2C frequency
 #define FREQ                400000 // 400 kHz
 // OLED dimensions
 #define OLED_HEIGHT      64
 #define OLED_WIDTH       128
 
 // Time Intervals
 /* milliseconds */
 #define RFR_MS              5
 #define SHR_MS              2
 /* seconds      */
 #define SPLASH_TIME         2
 
 // Scalers
 #define WIDTH_SCALE         128
 #define HEIGHT_SCALE        64
 #define SENSITIVITY         0.35f
 #define MAXVEL              10
 
 #define RESET_PIN           PB_5                                               /* Neaktivno, ali potrebno za inicijalizaciju displeja */
 
 I2C                         i2c_obj(SDA, SCL); 
 Adafruit_SSD1306_I2c        myOLED(                                            /* Pogledaj Adafruit_SSD1306.cpp dokumentaciju */
                                    i2c_obj, 
                                    RESET_PIN, 
                                    I2C_ADDRESS_MBED, 
                                    OLED_HEIGHT, 
                                    OLED_WIDTH
                                    );

// Kontrolni signali
DigitalIn                    SW1(PC_9);
DigitalIn                    SW2(PC_8);
AnalogIn                     POT1(PA_0);
AnalogIn                     POT2(PA_1);

Ticker tick;
Timer timeScore;

static int x0, y0 = 0;
static int x, y, vx, vy = 0;
static int orientation  = 0;
static bool SW1_released = 0;
static bool SW2_released = 0;
static int victory = 0;

static int y_target = 30,
           x_target = 30,
           vx_target = 6,
           vy_target = 2;

// General functions
void displayImage(int, int, const unsigned char *, int, int);
void checkBounds(void);
void updatePos(void);
void updateState(void);                                 /* Originalno je trebalo da bude jedna genericna funkcija, */ // RIP onPress(DigitalIn)
                                                        /* ali potreba da se razlikuje ponasanje to cini teskim    */
void victoryCheck(void);
void mainBurn(void);
void retroBurn(void);

// Ticker functions
void ping (void);
void debugSprites(void);

 int main () 
 {
    // Inicijalizacija programa -- Uspostavljanje komunikacije sa displejom, clear ekrana, i postavljanje brzine komunikacije.
    i2c_obj.frequency(FREQ);
    myOLED.begin();
    
    #ifdef SHOW_SPLASH
        displayImage(0, 0, spriteSheet[20], OLED_WIDTH, OLED_HEIGHT);
        
        wait(SPLASH_TIME); // Total: 2.005 sec
        
        myOLED.clearDisplay();
    #endif
    #ifdef DEBUG_SPRITES
        printf("Debugging!\n\r");
        while(1)
        {
            tick.attach(&debugSprites, 3.0);
            
            for(int i = 0; i < 24; i++)
            {
            displayImage(64, 32, spriteSheet[i], OLED_WIDTH, OLED_HEIGHT);
            wait(SPLASH_TIME);
            }
        }
    #endif
    
    displayImage(0, 0, spriteSheet[21], OLED_WIDTH, OLED_HEIGHT);
    
    printf("\rMaking final approach...                        \n\r");
    timeScore.start();
    while(1) 
    {   
        victoryCheck();
        if(!victory)
        {
            updateState();
            checkBounds();
            updatePos();
            
            printf("\rAscent Module (%3d, %3d) | Distance (%3d, %3d)", vx, vy, x - x0, y - y0);
            myOLED.clearDisplay();
        }
        else
        {
            x = 61;
            y = 31;
            x_target = 70;
            y_target = 32;
            vx = 0;
            vy = 0;
            vx_target = 0;
            vy_target = 0;
            
            
            wait(SPLASH_TIME);
            
            for (int i = 0; i < 4; i++)
            {
                wait(1);
                myOLED.clearDisplay();
                
                x++; 
                
                displayImage(x_target, y_target, spriteSheet[22], SHIP_OFF_WIDTH, SHIP_HEIGHT);
                displayImage(x, y, spriteSheet[18], SHIP_OFF_WIDTH, SHIP_HEIGHT);
            }
            
            while(x_target > -150)
            {
                myOLED.clearDisplay();
                
                vx_target -= 1;
                vx -= 1;
                
                x += vx;
                x_target += vx_target;
                
                displayImage(x_target, y_target, spriteSheet[23], SHIP_ON_WIDTH, SHIP_HEIGHT);
                displayImage(x, y, spriteSheet[18], SHIP_OFF_WIDTH, SHIP_HEIGHT);
                
                wait_ms(RFR_MS);
            }
        }
    }
 }
 
 void displayImage(int x, int y, const unsigned char *bmp, int w, int h)
 {
    myOLED.drawBitmap(x, y, bmp, w, h, WHITE);         /* To sto je bitmap staticka mi nista ne daje osim komplikacija. */
    myOLED.display();
 }
 void ping ()
 {
    printf("\r\nPing!\r\n");   
    wait_ms(RFR_MS);
 }
 
 void debugSprites()
 {
     printf("\n\rDebug ping!\r\n");
 }
 
 void updateState () 
 {
    if (!SW1)
    // Stanje: Dugme je pritisnuto
    {
       SW1_released = 1;
       
       displayImage(x_target, y_target, spriteSheet[22], SHIP_OFF_WIDTH, SHIP_HEIGHT);
       
       /* Action while button pressed */
       switch(orientation)
          {
             case 0:
                displayImage(x, y, spriteSheet[16], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 1:
                displayImage(x, y, spriteSheet[17], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 2:
                displayImage(x, y, spriteSheet[18], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 3:
                displayImage(x, y, spriteSheet[19], OLED_WIDTH, OLED_HEIGHT);
             break;
             default:
             printf("\r\n Error: Invalid orientation.\r\n");
          }
    }
    else
    // Stanje: Dugme nije pritisnuto
    {
       if(SW1_released)
       // Stanje: Dugme je neposredno bilo pritisnuto -> Aktiviraj
       {
          SW1_released = 0;
          
          myOLED.clearDisplay();
          displayImage(x_target, y_target, spriteSheet[22], SHIP_OFF_WIDTH, SHIP_HEIGHT);
          
          /* Action */
          switch(orientation)
          {
             case 0:
                displayImage(x, y, spriteSheet[12], OLED_WIDTH, OLED_HEIGHT);
                orientation = 1;
             break;
             case 1:
                displayImage(x, y, spriteSheet[13], OLED_WIDTH, OLED_HEIGHT);
                orientation = 2;
             break;
             case 2:
                displayImage(x, y, spriteSheet[14], OLED_WIDTH, OLED_HEIGHT);
                orientation = 3;
             break;
             case 3:
                displayImage(x, y, spriteSheet[15], OLED_WIDTH, OLED_HEIGHT);
                orientation = 0;
             break;
             default:
             printf("\r\n Error: Invalid orientation.\r\n");                    
          }
       }
       else
       // Stanje: Nista se nije promenilo
       {
          SW1_released = 0;
          
          displayImage(x_target, y_target, spriteSheet[22], SHIP_OFF_WIDTH, SHIP_HEIGHT);
          
          /* Action */
          switch(orientation)
          {
             case 0:
                displayImage(x, y, spriteSheet[16], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 1:
                displayImage(x, y, spriteSheet[17], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 2:
                displayImage(x, y, spriteSheet[18], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 3:
                displayImage(x, y, spriteSheet[19], OLED_WIDTH, OLED_HEIGHT);
             break;
             default:
             printf("\r\n Error: Invalid orientation.\r\n");                    
          }
       }
    }
    if (!SW2)
    // Stanje: Dugme je pritisnuto
    {
       SW2_released = 1;
          
       displayImage(x_target, y_target, spriteSheet[22], SHIP_OFF_WIDTH, SHIP_HEIGHT);
       
       /* Action while button pressed */
       switch(orientation)
          {
             case 0:
                displayImage(x, y, spriteSheet[16], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 1:
                displayImage(x, y, spriteSheet[17], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 2:
                displayImage(x, y, spriteSheet[18], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 3:
                displayImage(x, y, spriteSheet[19], OLED_WIDTH, OLED_HEIGHT);
             break;
             default:
             printf("\r\n Error: Invalid orientation.\r\n");
          }
    }
    else
    // Stanje: Dugme nije pritisnuto
    {
       if(SW2_released)
       // Stanje: Dugme je neposredno bilo pritisnuto -> Aktiviraj
       {
          SW2_released = 0;
          
          myOLED.clearDisplay();
          displayImage(x_target, y_target, spriteSheet[22], SHIP_OFF_WIDTH, SHIP_HEIGHT);
          
          /* Action */
          switch(orientation)
          {
             case 0:
                displayImage(x, y, spriteSheet[8], OLED_WIDTH, OLED_HEIGHT);
                orientation = 3;
             break;
             case 1:
                displayImage(x, y, spriteSheet[9], OLED_WIDTH, OLED_HEIGHT);
                orientation = 0;
             break;
             case 2:
                displayImage(x, y, spriteSheet[10], OLED_WIDTH, OLED_HEIGHT);
                orientation = 1;
             break;
             case 3:
                displayImage(x, y, spriteSheet[11], OLED_WIDTH, OLED_HEIGHT);
                orientation = 2;
             break;
             default:
             printf("\r\n Error: Invalid orientation.\r\n");                    
          }
       }
       else
       // Stanje: Nista se nije promenilo
       {
          SW2_released = 0;
          
          displayImage(x_target, y_target, spriteSheet[22], SHIP_OFF_WIDTH, SHIP_HEIGHT);
          
          /* Action */
          switch(orientation)
          {
             case 0:
                displayImage(x, y, spriteSheet[16], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 1:
                displayImage(x, y, spriteSheet[17], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 2:
                displayImage(x, y, spriteSheet[18], OLED_WIDTH, OLED_HEIGHT);
             break;
             case 3:
                displayImage(x, y, spriteSheet[19], OLED_WIDTH, OLED_HEIGHT);
             break;
             default:
             printf("\r\n Error: Invalid orientation.\r\n");                    
          }
       }
    }
    if(POT1 > 0.9f)
    {
        mainBurn();
    }
    if(POT2 > 0.9f)
    {
        retroBurn();
    }
 }

void retroBurn()
{
          
    myOLED.clearDisplay();
    displayImage(x_target, y_target, spriteSheet[22], SHIP_OFF_WIDTH, SHIP_HEIGHT);
    
    switch(orientation)
    {
        case 0:
            displayImage(x, y, spriteSheet[6], OLED_WIDTH, OLED_HEIGHT);
            vx > -MAXVEL ? vx-- : vx;
        break;
        case 1:
            displayImage(x, y, spriteSheet[5], OLED_WIDTH, OLED_HEIGHT);
            vy > -MAXVEL ? vy-- : vy;
        break;
        case 2:
            displayImage(x, y, spriteSheet[4], OLED_WIDTH, OLED_HEIGHT);
            vx < MAXVEL ? vx++ : vx;
        break;
        case 3:
            displayImage(x, y, spriteSheet[7], OLED_WIDTH, OLED_HEIGHT);
            vy < MAXVEL ? vy++ : vy;
        break;
    }
}

void mainBurn()
{
          
    myOLED.clearDisplay();
    displayImage(x_target, y_target, spriteSheet[22], SHIP_OFF_WIDTH, SHIP_HEIGHT);
    
    switch(orientation)
    {
        case 0:
            displayImage(x, y, spriteSheet[0], OLED_WIDTH, OLED_HEIGHT);
            vx < MAXVEL ? vx++ : vx;
        break;
        case 1:
            displayImage(x, y, spriteSheet[1], OLED_WIDTH, OLED_HEIGHT);
            vy < MAXVEL ? vy++ : vy;
        break;
        case 2:
            displayImage(x, y, spriteSheet[2], OLED_WIDTH, OLED_HEIGHT);
            vx > -MAXVEL ? vx-- : vx;
        break;
        case 3:
            displayImage(x, y, spriteSheet[3], OLED_WIDTH, OLED_HEIGHT);
            vy > -MAXVEL ? vy-- : vy;
        break;
    }
}
 
 void checkBounds()
 {
     // Osigurava da je x within bounds
    if (x + vx > OLED_WIDTH) 
    { 
        x -= OLED_WIDTH;
    }
    if (x + vx < 0) 
    { 
        x += OLED_WIDTH; 
    }
    // Osigurava da je y within bounds
    if ( y + vy > OLED_HEIGHT) 
    {
        y -= OLED_HEIGHT; 
    }
    if (y + vy < 0) 
    { 
        y += OLED_HEIGHT; 
    }   
    // Isto i za metu
    if (x_target + vx_target > OLED_WIDTH) 
    { 
        x_target -= OLED_WIDTH;
    }
    if (x_target + vx_target < 0) 
    { 
        x_target += OLED_WIDTH; 
    }
    // Osigurava da je y within bounds
    if (y_target + vy_target > OLED_HEIGHT) 
    {
        y_target -= OLED_HEIGHT; 
    }
    if (y_target + vy_target < 0) 
    { 
        y_target += OLED_HEIGHT; 
    }   
 }
 
 void updatePos()
 {
    x += vx;
    y += vy;
    x_target += vx_target;
    y_target += vy_target;
    
    x0 = x_target - 13;
    y0 = y_target;
    
    myOLED.drawCircle(x0, y0, 4, WHITE);
    myOLED.display();
    
 }
 
 void victoryCheck()
 {
     if ((x - x0) <= 2 && (y - y0) <= 2 && (vx - vx_target) == 0 && (vy - vy_target) == 0 && orientation == 0 && !victory)
     {
         victory = 1;
         timeScore.stop();
         printf("\n\rWith their vessels docked, the crew get ready for the journey home. The time taken was %f.2 seconds!\n", timeScore.read());
     }
 }
 
    /* Dodatni resursi */
    // https://javl.github.io/image2cpp/ --> za bitmape
    // Code Output Format -> Adafruit GFX Bitmap format; Vertical 1-bit-per-pixel
    // Ovaj konvertor je veoma los; 
    // -- Ako ima vise slika razlicitih dimenzija, koristi prvu na koju naidje i ostale konvertuje kao da su iste bez obzira na prave dimenzije.
    // -- Kada brises, brisi od prve inace ubaguje.
    // -- Ne moze da pravi slike koje su manje siroke nego sto su dugacke, sto je autisticno.
    // https://ezgif.com/ --> za dekonstrukciju gif-a
    // https://www.mischianti.org/images-to-byte-array-online-converter-cpp-arduino/#Byte_array_generator --> Za properly separated 2D byte array