// project using ATtiny85 with SSD1306 OLED Display to create a simplest possible game (dice game)

// created by upir, 2023
// youtube channel: https://www.youtube.com/upir_upir

// YOUTUBE VIDEO: https://youtu.be/RmhiZg_6-Mo
// SOURCE FILES: https://github.com/upiir/attiny85_dice_game

// Links from the video:
// ATtiny85 chip: https://s.click.aliexpress.com/e/_DeT7tBh
// Arduino UNO: https://s.click.aliexpress.com/e/_AXDw1h
// Arduino prototyping shield: https://s.click.aliexpress.com/e/_ApbCwx
// U8X8 Documentation: https://github.com/olikraus/u8g2/wiki/u8x8reference
// Previous project with ATTINY85+OLED: https://wokwi.com/projects/378925799915970561
// Image2cpp (convert array to image): https://javl.github.io/image2cpp/
// Photopea (online graphics editor like Photoshop): https://www.photopea.com

// Videos using ATtiny85 chip: https://www.youtube.com/playlist?list=PLjQRaMdk7pBbt-is-fkRmUoRcV4dzraYH

// Related videos with Arduino UNO and 128x64 OLED screen:
// Arduino + OLED displays: https://www.youtube.com/playlist?list=PLjQRaMdk7pBZ1UV3IL5ol8Qc7R9k-kwXA



#include <Arduino.h>
#include <U8x8lib.h> // u8x8 library for drawing on the OLED display

U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/ 4, /* data=*/ 3, /* reset=*/ U8X8_PIN_NONE); // software IIC communication with the OLED display

// images were generated using the image2cpp website
// when generating images, make sure to set the Draw mode to Vertical
// and delete "PROGMEM" word after generating the arrays

// ' tile_dice_empty', 8x8px
unsigned char epd_bitmap__tile_dice_empty [] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// ' tile_dice_dot', 8x8px
unsigned char epd_bitmap__tile_dice_dot [] = {
	0x00, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x00
};
// ' tile_outline_left', 8x8px
 unsigned char epd_bitmap__tile_outline_left [] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff
};
// ' tile_outline_right', 8x8px
unsigned char epd_bitmap__tile_outline_right [] = {
	0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// ' tile_outline_bottom', 8x8px
 unsigned char epd_bitmap__tile_outline_bottom [] = {
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
};
// ' tile_outline_top', 8x8px
unsigned char epd_bitmap__tile_outline_top [] = {
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80
};


// dice dots lookup table, 0=empty, 1=dot
byte dice_tile_lookup[7][9] = {
  { // 0 - fully empty dice, displayed when the game has not started yet
    0, 0, 0,
    0, 0, 0, 
    0, 0, 0
  },
  { // 1
    0, 0, 0,
    0, 1, 0, 
    0, 0, 0
  },
  { // 2
    1, 0, 0,
    0, 0, 0, 
    0, 0, 1
  },
  { // 3
    1, 0, 0,
    0, 1, 0, 
    0, 0, 1
  },
  { // 4
    1, 0, 1,
    0, 0, 0, 
    1, 0, 1
  },
  { // 5
    1, 0, 1,
    0, 1, 0, 
    1, 0, 1
  },
  { // 6
    1, 0, 1,
    1, 0, 1, 
    1, 0, 1
  }
};


void setup(void)
{
  u8x8.begin(); // display initialization
  u8x8.setPowerSave(0); // this is required

  // draw outline of the dice
  // since drawing the outlines made from individual images takes some time (because we use software IIC)
  // the drawing is in the order that it looks like it´s being drawn as a continuous line
  
  u8x8.drawTile(/*x*/3, /*y*/2, /*tiles*/1, epd_bitmap__tile_outline_top); 
  u8x8.drawTile(/*x*/4, /*y*/2, /*tiles*/1, epd_bitmap__tile_outline_top); 
  u8x8.drawTile(/*x*/5, /*y*/2, /*tiles*/1, epd_bitmap__tile_outline_top);     

  u8x8.drawTile(/*x*/6, /*y*/3, /*tiles*/1, epd_bitmap__tile_outline_right);       
  u8x8.drawTile(/*x*/6, /*y*/4, /*tiles*/1, epd_bitmap__tile_outline_right);       
  u8x8.drawTile(/*x*/6, /*y*/5, /*tiles*/1, epd_bitmap__tile_outline_right);       

  u8x8.drawTile(/*x*/5, /*y*/6, /*tiles*/1, epd_bitmap__tile_outline_bottom);       
  u8x8.drawTile(/*x*/4, /*y*/6, /*tiles*/1, epd_bitmap__tile_outline_bottom);       
  u8x8.drawTile(/*x*/3, /*y*/6, /*tiles*/1, epd_bitmap__tile_outline_bottom);           

  u8x8.drawTile(/*x*/2, /*y*/5, /*tiles*/1, epd_bitmap__tile_outline_left);         
  u8x8.drawTile(/*x*/2, /*y*/4, /*tiles*/1, epd_bitmap__tile_outline_left);         
  u8x8.drawTile(/*x*/2, /*y*/3, /*tiles*/1, epd_bitmap__tile_outline_left);      
  
  u8x8.drawTile(/*x*/3+7, /*y*/2, /*tiles*/1, epd_bitmap__tile_outline_top); 
  u8x8.drawTile(/*x*/4+7, /*y*/2, /*tiles*/1, epd_bitmap__tile_outline_top); 
  u8x8.drawTile(/*x*/5+7, /*y*/2, /*tiles*/1, epd_bitmap__tile_outline_top);     

  u8x8.drawTile(/*x*/6+7, /*y*/3, /*tiles*/1, epd_bitmap__tile_outline_right);       
  u8x8.drawTile(/*x*/6+7, /*y*/4, /*tiles*/1, epd_bitmap__tile_outline_right);       
  u8x8.drawTile(/*x*/6+7, /*y*/5, /*tiles*/1, epd_bitmap__tile_outline_right);       

  u8x8.drawTile(/*x*/5+7, /*y*/6, /*tiles*/1, epd_bitmap__tile_outline_bottom);       
  u8x8.drawTile(/*x*/4+7, /*y*/6, /*tiles*/1, epd_bitmap__tile_outline_bottom);       
  u8x8.drawTile(/*x*/3+7, /*y*/6, /*tiles*/1, epd_bitmap__tile_outline_bottom);           

  u8x8.drawTile(/*x*/2+7, /*y*/5, /*tiles*/1, epd_bitmap__tile_outline_left);         
  u8x8.drawTile(/*x*/2+7, /*y*/4, /*tiles*/1, epd_bitmap__tile_outline_left);         
  u8x8.drawTile(/*x*/2+7, /*y*/3, /*tiles*/1, epd_bitmap__tile_outline_left);     

  u8x8.setFont(u8x8_font_chroma48medium8_r); // set u8x8 font, list of the available fonts is here: https://github.com/olikraus/u8g2/wiki/fntlist8x8
  u8x8.drawString(0,1,"   Dice Game!   "); // draw a string, include spaces to delete anything that was drawn previously  

  pinMode(0, INPUT);


}

// function to draw 3x3 dice on defined position, dice is made either from dot or empty piece
// note that outline is not redrawn, as it´s outside the cube, and only drawn once on startup
void draw_dice(byte xoffset, byte yoffset, byte dice_number, byte dice_number_previous) {

  // update all 9 sections of the dice
  for (byte i = 0; i < 9; i++) {
    // update all 9 sections, but only redraw areas that are different from the previously displayed dice
    // this speeds up the redrawing quite a lot    
    if ((dice_tile_lookup[dice_number][i] == 1) && (dice_tile_lookup[dice_number_previous][i] != 1)) {
      u8x8.drawTile(/*x*/xoffset + (i % 3), /*y*/yoffset + (i / 3), /*tiles*/1, epd_bitmap__tile_dice_dot); // draw dot tile      
    } 
    else if ((dice_tile_lookup[dice_number][i] == 0) && (dice_tile_lookup[dice_number_previous][i] != 0)) {
      u8x8.drawTile(/*x*/xoffset + (i % 3), /*y*/yoffset + (i / 3), /*tiles*/1, epd_bitmap__tile_dice_empty); // draw empty tile    
    }
  }
}  

byte dice_left = 0; // value for the left dice
byte dice_right = 0; // value for the right dice

byte dice_left_prev = 0; // previous value, used to not redraw all the dots
byte dice_right_prev = 0; // previous value, used to not redraw all the dots

unsigned long elapsed_time_millis; // time in milliseconds since the sketch started
unsigned long elapsed_time_millis_prev; // previously measured time

byte game_status = 0; // game status: 0: waiting for the game to start, 1: game is running, 2: game has ended



void loop(void) // main loop
{

  if (game_status == 1) {
    // game is running, find out if the elapsed time between the last frame was bigger than 500ms
    // and if that´s the case, show different values for dices    
    elapsed_time_millis = millis(); // get the time from starting the sketch in milliseconds
    if ((elapsed_time_millis - elapsed_time_millis_prev) > 500) { // time from last dices update is > 500ms ==> update dices
      // randomize new dices...
      dice_left = random(1, 6);
      dice_right = random(1, 6);  
      //...and draw them
      draw_dice(3, 3, /*number*/ dice_left, /*number previous*/ dice_left_prev);
      draw_dice(10, 3, /*number*/ dice_right, /*number previous*/ dice_right_prev);  
      // update the previous values
      dice_left_prev = dice_left;
      dice_right_prev = dice_right;
      // update the previously measured time
      elapsed_time_millis_prev = elapsed_time_millis;
    }
  }
            

  if (digitalRead(0) == HIGH) { // button is pressed
    if (game_status == 0) { // waiting for the game to start ==> start the game
      u8x8.drawString(0,1,"Press btn = stop"); 
      game_status = 1; // start the game
    } else if (game_status == 1) { // game is running ==> find out if there is a winner or not
      if (dice_left == dice_right) { // we have a winner
        u8x8.drawString(0,1,"    Winner!!    "); 
      } else {
        u8x8.drawString(0,1,"Sorry not winner"); 
      }
      game_status = 2; // game is done
    } else if (game_status == 2) {  // game has ended ==> jump back to the beginning
      dice_left = 0; // set dice to empty dice
      dice_right = 0; // set dice to empty dice
      draw_dice(3, 3, /*number*/ dice_left, /*number previous*/ dice_left_prev); // draw empty dice
      draw_dice(10, 3, /*number*/ dice_right, /*number previous*/ dice_right_prev); // draw empty dice
      dice_left_prev = dice_left;
      dice_right_prev = dice_right;
      u8x8.drawString(0,1,"   Dice Game!   "); 
      game_status = 0; // waiting for game to start
    }

    delay(50); // add a small delay to not accidentaly jump between multiple different steps
  }

}
ATTINY8520PU
Loading
ssd1306