// Toevoegen van de juiste bibliotheken
#include <LiquidCrystal_I2C.h>
// Instellen van gebruikte Arduino Pins
#define PIN_BUTTON 7
#define PIN_POT A2
// Instelling die eigen zijn aan de LCD
#define I2C_ADDR 0x27 // I2C heeft een adres nodig waar het de display kan vinden
#define LCD_COLUMNS 15
#define LCD_LINES 2
// States van het Game of de display
#define GAME_ON 0
#define CHANGE_PADDLE 1
#define NEW_BALL 2
#define CHECK_POINTS 3
// In het leven roepen van de LCD in het geheugen van de Arduino
// Hierna zijn alle methodes van de bibliotheek beschikbaar
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
// Variabelen
byte stateLcd = NEW_BALL; // 2
byte paddlePos;
byte ballPos;
byte teller = 0; // is de bron voor een loop sequentie 0-7
int oldPaddlePos; // vorige locatie van het 'glaasje'
const int SPEED = 150; // valsnelheid (interframe-delay)
int score = 0;
int highScore = 0;
uint8_t newChar0[8] = {17, 17, 31, 4, 4, 4, 4, 4}; // opbouw van char (glaasje)
// opbouw van char (druppel) voor de acht mogelijke posities.
uint8_t newChar1[8][8] = {
{4, 0, 0, 0, 0, 0, 0, 0},
{0, 4, 0, 0, 0, 0, 0, 0},
{0, 0, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 0, 0},
{0, 0, 0, 0, 0, 0, 4, 0},
{0, 0, 0, 0, 0, 0, 0, 4}
};
// Prototypes van de functies
void checkPoints(); // Controleert of de paddlePos=ballPos en geeft punten
void drawPaddle(); // Tekent het 'glaasje' op een nieuwe plaats
void checkInputPot(); // Controleert op de potentiometer verzet werd
void drawBall(); // Tekent de druppel op een nieuwe locatie
void goToNewBallPos(); // Verwijdert oude druppel en gaat naar nieuwe positie
void setup() {
Serial.begin(9600);
pinMode(PIN_POT, INPUT);
// Zorgt voor een herrangschikking van de willekeurige getallen in de random-functie
randomSeed(analogRead(0));
// Initialiseert de display
lcd.init();
lcd.backlight();
// maak het 'glaasje' in het geheugen van de display
lcd.createChar(0, newChar0);
drawPaddle();
}
void loop() {
switch (stateLcd) {
case GAME_ON: // 0
checkInputPot();
drawBall();
break;
case CHANGE_PADDLE: // 1
drawPaddle();
stateLcd = GAME_ON;
break;
case NEW_BALL: // 2
goToNewBallPos();
stateLcd = GAME_ON;
break;
case CHECK_POINTS: // 3
checkPoints();
stateLcd = NEW_BALL;
break;
default:
Serial.println("error");
}
}
void goToNewBallPos() {
lcd.setCursor(ballPos, 0);
// Door een spatie te zetten op de plaats van de oude druppel overschrijven
// We deze en is die bijgevolg niet meer zichtbaar
lcd.print(" ");
// Dit genereert een willekeurig getal van 0 tem 15
// Dit wordt gebruikt om de nieuwe positie van de druppel te bepalen.
ballPos = random(15);
}
void drawPaddle() {
// De inkomende waardes van de potentiometer moeten herleid worden tot
// posities op de LCD. Deze heeft slecht 16 posities dus moet de waarde
// omgerekend worden. Dit doet de map() functie.
paddlePos = map(oldPaddlePos, 0, 1023, 0, 15);
// Ga naar de nulde positie op de bovenste lijn
lcd.setCursor(0, 1);
// Teken tot aan het glaasje X-aantal keer niets, of dus een spatie
for (int i = 0 ; i < paddlePos ; i++) {
lcd.print(" ");
}
// Teken nu het glaasje
lcd.print(char(0));
// Teken vanaf het glaasje tot aan de rand van de display,
// dat is dus het aantal kolommen MIN de positie van het glaasje.
for (int i = paddlePos ; i <= LCD_COLUMNS ; i++) {
lcd.print(" ");
}
}
void checkInputPot() {
// Controleer of de vorige (oude) waarde van de potentiometer anders is dan
// wat je nu kan meten. Is dat zo dan moet er actie ondernomen worden, anders niet
if (oldPaddlePos != analogRead(A2)) {
// Serial.println(oldPaddlePos);
// update de oude waarde van de potentiometer naar de huidige gemeten waarde
oldPaddlePos = analogRead(A2);
// verander de state zodat het glaasje op een nieuwe positie
// getekend kan worden
stateLcd = CHANGE_PADDLE;
}
}
void drawBall() {
// De druppel mag niet altijd getekend worden, er moet een kleine pauze
// zijn tussen twee verschillende weergaven. Deze pauze bepaalt de
// snelheid waarmee de druppel valt.
// De module zorgt hier voor een loop (0-SPEED). De loop waarin het progamma
// zit heeft misschien enkele millis nodig om deze volledig te doorlopen.
// daarom is er een kleine marge genomen van 10 millis om dit eventueel op
// te vangen.
if (millis() % SPEED > SPEED - 10) {
// Omdat teller een steeds groter wordend getal is zort (teller % 8) voor een
// loop (0-7) sequentie. Deze getallen gebruiken we om de juiste druppel op
// het scherm te kunnen zetten. Dit getal wordt ook gebruikt om het moment
// te bepalen waarop het spel moet beslissen of de druppel werd opgevangen
// of niet.
if (teller % 8 == 7) {
stateLcd = CHECK_POINTS;
}
// Omdat alle mogelijke charakters die een vallende druppel voorstellen
// gecombineerd zijn in een lijst met de naam newChar1, moet het juiste (het 0-7de)
// uit de lijst gehaald worden dit doe je door de vierkante haken te gebruiken.
// dat haalt het zoveelste element op
// (teller % 8) kan gelijk zijn aan 3
// dus newChar1[teller % 8] is gelijk aan newChar1[3] en haalt dus het vierde
// element op (lijstindexen zijn 0-based)
lcd.createChar(1, newChar1[teller % 8]);
lcd.setCursor(ballPos, 0);
lcd.print(char(1)); // teken het druppel-charakter
teller++;
drawPaddle();
}
}
void checkPoints() {
// Indien op dit moment in het programma het glas op dezelfde X-positie staat
// als de druppel, dan krijg je een punt en verhoogt je score.
if (paddlePos == ballPos) {
score++;
// Als deze score hoger is dan de hoogste score, dan pas je de hoogste
// score aan en ontstaat er dus een nieuwe Highscore
if (score > highScore) {
highScore = score;
}
Serial.print("Je hebt nu ");
Serial.print(score);
if (score == 1) {
Serial.print(" punt");
} else {
Serial.print(" punten");
}
} else { // Hier start de 'else' je glas stond dus op de foute plaats en bijgevolg
// gaat je score naar 0 en ben je zogezegd 'Kapot'
Serial.println("->|KAPOT|<- ");
score = 0;
}
// Dit is allemaal debug en extra info die we printen
Serial.print(" -- *** Highscore ");
Serial.print(highScore);
Serial.print(" ");
Serial.print(analogRead(0));
Serial.println(" *** --");
}