// ATtiny85 webboogles BREAKOUT für die eigene Spielkonsole mit 1,3"-Display
//
// angepasst an die Tiny4kOLED-Bibliothek
//
// für SSD1316 und SH1106 Displays
//
// https://webboggles.com/attiny85-breakout-keychain-game/
//
// https://github.com/datacute/Tiny4kOLED
//
// Add SH1106 132x64 display support
// https://github.com/datacute/Tiny4kOLED/issues/32
//
// Zur Verwendung des SH1106-Displays #define SH1106 in Tiny4kOLED_common.h aktivieren!
//
#include <EEPROM.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include "Tiny4kOLED_common.h"
#include "Tiny4kOLED_TinyWireM.h"
#include "Tiny4kOLED.h"
volatile byte player = 0; //0 to 128-platformWidth - this is the position of the bounce platform
byte platformWidth = 16;
byte ballx = 62; // coordinate of the ball
byte bally = 50; // coordinate of the ball
int vdir = -1; // vertical direction and step distance
int hdir = -1; // horizontal direction and step distance
long lastFrame = 0; // time since the screen was updated last
boolean row1[16]; // on-off array of blocks
boolean row2[16];
boolean row3[16];
int score = 0; // score - counts the number of blocks hit and resets the array above when devisible by 48(total blocks)
int px;
int py;
void drawPlatform() {
noInterrupts();
oled.setCursor(player, 7);
oled.startData();
for (byte pw = 1; pw < platformWidth; pw++) {
oled.sendData(0b00000011);
}
oled.endData();
interrupts();
}
void ssd1306_fillscreen(uint8_t fill) {
for (byte i =0; i<8; i++){
oled.setCursor(0, i);
oled.startData();
for (byte j =0; j<128; j++){
oled.sendData(fill);
}
oled.endData();
}
}
void sendBlock(bool fill) {
oled.setCursor(px*8, py);
oled.startData();
if (fill == 1) {
oled.sendData(0b00000000);
oled.sendData(0b01111110);
oled.sendData(0b01111110);
oled.sendData(0b01111110);
oled.sendData(0b01111110);
oled.sendData(0b01111110);
oled.sendData(0b01111110);
oled.sendData(0b00000000);
}
else {
oled.sendData(0b00000000);
oled.sendData(0b00000000);
oled.sendData(0b00000000);
oled.sendData(0b00000000);
oled.sendData(0b00000000);
oled.sendData(0b00000000);
oled.sendData(0b00000000);
oled.sendData(0b00000000);
}
oled.endData();
}
void beep(int bCount,int bDelay){
for (int i = 0; i<=bCount; i++){digitalWrite(1,HIGH);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}digitalWrite(1,LOW);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}}
}
void resetGame(){
oled.setCursor(16, 2);
oled.print(F("B R E A K O U T"));
oled.setCursor(20, 4);
oled.print(F("webboggles.com"));
delay(200);
oled.setCursor(12, 6);
oled.print(F("Tweet @webboggles"));
oled.setCursor(22, 7);
oled.print(F("#AttinyArcade"));
beep(200,600);
beep(300,200);
beep(400,300);
delay(2000);
ssd1306_fillscreen(0x00);
for (byte i =0; i<16;i++){ // reset blocks
row1[i]=1; row2[i]=1; row3[i]=1;
}
platformWidth = 16;
ballx = 64;
bally = 50;
hdir = -1;
vdir = -1;
score = 0;
player = random(0,128-platformWidth);
ballx = player+platformWidth/2;
}
void collision(){ // the collsision check is actually done befor this is called, this code works out where the ball will bounce
if ((bally+vdir)%8==7&&(ballx+hdir)%8==7){ // bottom right corner
if (vdir==1){hdir=1;}else if(vdir==-1&&hdir==1){vdir=1;}else {hdir=1;vdir=1;}
}else if ((bally+vdir)%8==7&&(ballx+hdir)%8==0){ // bottom left corner
if (vdir==1){hdir=-1;}else if(vdir==-1&&hdir==-1){vdir=1;}else {hdir=-1;vdir=1;}
}else if ((bally+vdir)%8==0&&(ballx+hdir)%8==0){ // top left corner
if (vdir==-1){hdir=-1;}else if(vdir==1&&hdir==-1){vdir=-1;}else {hdir=-1;vdir=-1;}
}else if ((bally+vdir)%8==0&&(ballx+hdir)%8==7){ // top right corner
if (vdir==-1){hdir=1;}else if(vdir==1&&hdir==1){vdir=-1;}else {hdir=1;vdir=-1;}
}else if ((bally+vdir)%8==7){ // bottom side
vdir = 1;
}else if ((bally+vdir)%8==0){ // top side
vdir = -1;
}else if ((ballx+hdir)%8==7){ // right side
hdir = 1;
}else if ((ballx+hdir)%8==0){ // left side
hdir = -1;
}else {
hdir = hdir*-1; vdir = vdir*-1;
}
beep(30,300);
}
// Routines to set and clear bits (used in the sleep code)
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
void system_sleep() {
ssd1306_fillscreen(0x00);
//ssd1306_send_command(0xAE);
cbi(ADCSRA,ADEN); // switch Analog to Digitalconverter OFF
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
sleep_enable();
sleep_mode(); // System actually sleeps here
sleep_disable(); // System continues execution here when watchdog timed out
sbi(ADCSRA,ADEN); // switch Analog to Digitalconverter ON
//ssd1306_send_command(0xAF);
}
ISR(PCINT0_vect) { // PB3 pin button interrupt
if (player > 0) {
player--;
}
return;
}
void playerInc() { // PB4 pin button interrupt
if (player < 128 - platformWidth) {
player++;
}
}
void setup() {
//resetGame();
DDRB = 0b00000010; // saet PB1 as output (for the speaker)
PCMSK = 0b00000001; // pin change mask: listen to portb bit 1
GIMSK |= 0b00100000; // enable PCINT interrupt
sei(); // enable all interrupts
pinMode(4,INPUT);
attachInterrupt(0,playerInc,CHANGE);
lastFrame = millis();
}
void loop() {
noInterrupts();
// Two fonts are supplied with this library, FONT8X16 and FONT6X8
oled.setFont(FONT6X8);
#ifndef SH1106
oled.begin(128, 64, sizeof(tiny4koled_init_128x64br), tiny4koled_init_128x64br);
#else
oled.begin(132, 64, sizeof(tiny4koled_init_132x64), tiny4koled_init_132x64);
#endif
oled.on();
oled.clear();
ssd1306_fillscreen(0x00);
oled.setCursor(16, 2);
oled.print(F("B R E A K O U T"));
oled.setCursor(20, 4);
oled.print(F("webboggles.com"));
delay(200);
oled.setCursor(12, 6);
oled.print(F("Tweet @webboggles"));
oled.setCursor(22, 7);
oled.print(F("#AttinyArcade"));
beep(200,600);
beep(300,200);
beep(400,300);
delay(2000);
ssd1306_fillscreen(0x00);
while (1==1) {
// continue moving after the interrupt
if (digitalRead(4)==1){
if (player <128-platformWidth){
player++;
}
if (player <128-platformWidth){
player++;
}
if (player <128-platformWidth){
player++;
}
}
if (digitalRead(3)==1){
if (player >0){
player--;
}
if (player >0){
player--;
}
if (player >0){
player--;
}
}
// bounce off the sides of the screen
if ((bally+vdir<54&&vdir==1)||(bally-vdir>1&&vdir==-1)){
bally+=vdir;}
else {
vdir = vdir*-1;
}
if ((ballx+hdir<127&&hdir==1)||(ballx-hdir>1&&hdir==-1)){
ballx+=hdir;}
else {
hdir = hdir*-1;
}
// frame actions
if (lastFrame+10<millis()){
if(bally>10&&bally+vdir>=54&&(ballx<player||ballx>player+platformWidth)){ // game over if the ball misses the platform
int topScore = EEPROM.read(0);
topScore = topScore << 8;
topScore = topScore | EEPROM.read(1);
if (score>topScore){
topScore = score;
EEPROM.write(1,topScore & 0xFF);
EEPROM.write(0,(topScore>>8) & 0xFF);
}
ssd1306_fillscreen(0x00);
oled.setCursor(32, 3);
oled.print(F("Game Over"));
oled.setCursor(32, 5);
oled.print(F("score:"));
char temp[4] = {0,0,0,0};
itoa(score,temp,10);
oled.setCursor(70, 5);
oled.print(temp);
oled.setCursor(32, 6);
oled.print(F("top score:"));
itoa(topScore,temp,10);
oled.setCursor(96, 6);
oled.print(temp);
for (int i = 0; i<1000; i++){
beep(1,random(0,i*2));
}
delay(1000);
system_sleep();
resetGame();
}
else if (ballx<player+platformWidth/2&&bally>10&&bally+vdir>=54){ // if the ball hits left of the platform bounce left
hdir=-1; beep(20,600);
}
else if (ballx>player+platformWidth/2&&bally>10&&bally+vdir>=54){ // if the ball hits right of the platform bounce right
hdir=1; beep(20,600);
}
else if (bally+vdir>=54){
hdir=1; beep(20,600);
}
collisionCheck: // go back to here if a collision was detected to prevent flying through a rigid
if (floor((bally+vdir)/8)==2){
if (row3[ballx/8]==1){
row3[ballx/8]=0; score++;
collision();
goto collisionCheck; // check collision for the new direction to prevent flying through a rigid
}
}
else if (floor((bally+vdir)/8)==1){
if (row2[ballx/8]==1){
row2[ballx/8]=0;
score++;
collision();
goto collisionCheck;
}
}
else if (floor((bally+vdir)/8)==0){
if (row1[ballx/8]==1){
row1[ballx/8]=0; score++;
collision();
goto collisionCheck;
}
}
// reset blocks if all have been hit
if (score%48==0){
for (byte i =0; i<16;i++){
row1[i]=1; row2[i]=1; row3[i]=1;
}
}
}
// update whats on the screen
noInterrupts();
// blocks
for (int bl = 0; bl <16; bl++){
px = bl;
py = 0;
if(row1[bl]==1){
sendBlock(1);
}
else {
sendBlock(0);
}
}
for (int bl = 0; bl <16; bl++){
px = bl;
py = 1;
if(row2[bl]==1){
sendBlock(1);
}
else {
sendBlock(0);
}
}
for (int bl = 0; bl <16; bl++){
px = bl;
py = 2;
if(row3[bl]==1){
sendBlock(1);
}
else {
sendBlock(0);
}
}
oled.setCursor(0, 3);
oled.startData();
for (byte i =0; i<128; i++){
oled.sendData(0b00000000);
}
oled.endData();
oled.setCursor(0, 4);
oled.startData();
for (byte i =0; i<128; i++){
oled.sendData(0b00000000);
}
oled.endData();
oled.setCursor(0, 5);
oled.startData();
for (byte i =0; i<128; i++){
oled.sendData(0b00000000);
}
oled.endData();
oled.setCursor(0, 6);
oled.startData();
for (byte i =0; i<128; i++){
oled.sendData(0b00000000);
}
oled.setCursor(0, 7);
oled.startData();
for (byte i =0; i<128; i++){
oled.sendData(0b00000000);
}
oled.endData();
// draw ball
oled.setCursor(ballx,bally/8);
uint8_t temp = 0b00000001;
oled.startData();
temp = temp << bally%8+1;
oled.sendData(temp);
oled.endData();
drawPlatform();
interrupts();
}
}