Tinkercad

🎮 Het bouwen van het spelletje in het echt is natuurlijk het allerleukste!
Maar soms heb je niet alle onderdelen bij de hand, of wil je iets snel testen.
Dan kan je gebruik maken van simulatiesoftware.
Die laat jou jouw schakeling en programma maken in een virtuele wereld en zelfs testen!
Zo kan je eerst uitproberen of jouw idee werkt, voor je alles koopt.

🖥️ Wij gebruiken hiervoor Tinkercad. Wat is dat?
Tinkercad is een gratis programma op het internet waar je:
✅ Je eigen schakeling kan bouwen met Arduino en andere onderdelen
✅ Je code kan schrijven en testen
✅ Alles kan zien werken alsof het echt is!
En je kan er ook 3D tekenen!
Je kan zelf onderdelen ontwerpen of leuke figuren maken die je later zelfs kan 3D-printen.

Het is een beetje zoals een speel-labo op je computer.
Je kan jouw eigen testlabo aanmaken via deze link:
👉 https://www.tinkercad.com

👨‍👩‍👧 Misschien heb je hulp nodig van mama of papa om een account aan te maken.
Vraag het gerust, samen lukt het zeker! 💪😊

🎮 Speel je graag verder of wil je het programma van het car race spelletje aanpassen, maar heb je thuis (nog) geen Arduino en ander elektronica materiaal?

Bouw je graag het spelletje virtueel na?
Dan kan je in Tinkercad het aansluitschema overnemen en de code kopiëren naar de programmeermodule van Tinkercad.

Zo kan je het spel testen in de virtuele wereld!

💡 Tip voor Tinkercad:
Klik op het LCD-scherm in Tinkercad.
Bij Type kies je “Gebaseerd op PCF8574”.
Als je dat niet doet, kan het scherm soms niet werken!

Kopieer de onderstaande code naar Tinkercad!

//Hoera! Deze coole code werd een beetje aangepast door de studenten van Odisee – opleiding Elektromechanica op Campus Aalst, Kwalestraat 154.
//Ze deden dat speciaal voor de Dag van de Wetenschap!
//Nu is het jouw beurt om te bouwen, te ontdekken en vooral: veel plezier te maken!

#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#define PIN_BUTTON 2
#define PIN_AUTOPLAY 1
#define SPRITE_RUN1 1 //Car sprite
#define SPRITE_RUN2 2
#define SPRITE_JUMP 3
#define SPRITE_JUMP_UPPER ‘.’
#define SPRITE_JUMP_LOWER 4
#define SPRITE_TERRAIN_EMPTY ‘ ‘
#define SPRITE_TERRAIN_SOLID 5
#define SPRITE_TERRAIN_SOLID_RIGHT 6
#define SPRITE_TERRAIN_SOLID_LEFT 7
#define CAR_HORIZONTAL_POSITION 1 // Horizontal position of CAR on screen
#define TERRAIN_WIDTH 16
#define TERRAIN_EMPTY 0
#define TERRAIN_LOWER_BLOCK 1
#define TERRAIN_UPPER_BLOCK 2
#define CAR_POSITION_OFF 0 // CAR is invisible
#define CAR_POSITION_RUN_LOWER_1 1 // CAR is running on lower row (pose 1)
#define CAR_POSITION_RUN_LOWER_2 2 // (pose 2)
#define CAR_POSITION_JUMP_1 3 // Starting a jump
#define CAR_POSITION_JUMP_2 4 // Half-way up
#define CAR_POSITION_JUMP_3 5 // Jump is on upper row
#define CAR_POSITION_JUMP_4 6 // Jump is on upper row
#define CAR_POSITION_JUMP_5 7 // Jump is on upper row
#define CAR_POSITION_JUMP_6 8 // Jump is on upper row
#define CAR_POSITION_JUMP_7 9 // Half-way down
#define CAR_POSITION_JUMP_8 10 // About to land
#define CAR_POSITION_RUN_UPPER_1 11 // CAR is running on upper row (pose 1)
#define CAR_POSITION_RUN_UPPER_2 12 // (pose 2)
LiquidCrystal_I2C lcd(0x27, 16, 2);
static char terrainUpper[TERRAIN_WIDTH + 1];
static char terrainLower[TERRAIN_WIDTH + 1];
static bool buttonPushed = false;
void initializeGraphics() {
static byte graphics[] = {
// Run position 1
0b00000,
0b00000,
0b00100,
0b11110,
0b11110,
0b11111,
0b01001,
0b00000,
// Run position 2
0b00000,
0b00000,
0b00100,
0b11110,
0b11110,
0b11111,
0b01001,
0b00000,
// Jump
0b00000,
0b00000,
0b00100,
0b11110,
0b11110,
0b11111,
0b01001,
0b00000,
// Jump lower
0b00000,
0b00000,
0b00100,
0b11110,
0b11110,
0b11111,
0b01001,
0b00000,
// Ground
0b01110,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
// Ground right
0b01110,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
// Ground left
0b01110,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
0b10101,
0b11111,
};
int i;
// Skip using character 0, this allows lcd.print() to be used to
// quickly draw multiple characters
for (i = 0; i < 7; ++i) {
lcd.createChar(i + 1, &graphics[i * 8]);
}
for (i = 0; i < TERRAIN_WIDTH; ++i) {
terrainUpper[i] = SPRITE_TERRAIN_EMPTY;
terrainLower[i] = SPRITE_TERRAIN_EMPTY;
}
}

// Slide the terrain to the left in half-character increments
//
void advanceTerrain(char* terrain, byte newTerrain) {
for (int i = 0; i < TERRAIN_WIDTH; ++i) {
char current = terrain[i];
char next = (i == TERRAIN_WIDTH – 1) ? newTerrain : terrain[i + 1];
switch (current) {
case SPRITE_TERRAIN_EMPTY:
terrain[i] = (next == SPRITE_TERRAIN_SOLID) ? SPRITE_TERRAIN_SOLID_RIGHT : SPRITE_TERRAIN_EMPTY;
break;
case SPRITE_TERRAIN_SOLID:
terrain[i] = (next == SPRITE_TERRAIN_EMPTY) ? SPRITE_TERRAIN_SOLID_LEFT : SPRITE_TERRAIN_SOLID;
break;
case SPRITE_TERRAIN_SOLID_RIGHT:
terrain[i] = SPRITE_TERRAIN_SOLID;
break;
case SPRITE_TERRAIN_SOLID_LEFT:
terrain[i] = SPRITE_TERRAIN_EMPTY;
break;
}
}
}

bool drawCAR(byte position, char* terrainUpper, char* terrainLower, unsigned int score) {
bool collide = false;
char upperSave = terrainUpper[CAR_HORIZONTAL_POSITION];
char lowerSave = terrainLower[CAR_HORIZONTAL_POSITION];
byte upper, lower;
switch (position) {
case CAR_POSITION_OFF:
upper = lower = SPRITE_TERRAIN_EMPTY;
break;
case CAR_POSITION_RUN_LOWER_1:
upper = SPRITE_TERRAIN_EMPTY;
lower = SPRITE_RUN1;
break;
case CAR_POSITION_RUN_LOWER_2:
upper = SPRITE_TERRAIN_EMPTY;
lower = SPRITE_RUN2;
break;
case CAR_POSITION_JUMP_1:
case CAR_POSITION_JUMP_8:
upper = SPRITE_TERRAIN_EMPTY;
lower = SPRITE_JUMP;
break;
case CAR_POSITION_JUMP_2:
case CAR_POSITION_JUMP_7:
upper = SPRITE_JUMP_UPPER;
lower = SPRITE_JUMP_LOWER;
break;
case CAR_POSITION_JUMP_3:
case CAR_POSITION_JUMP_4:
case CAR_POSITION_JUMP_5:
case CAR_POSITION_JUMP_6:
upper = SPRITE_JUMP;
lower = SPRITE_TERRAIN_EMPTY;
break;
case CAR_POSITION_RUN_UPPER_1:
upper = SPRITE_RUN1;
lower = SPRITE_TERRAIN_EMPTY;
break;
case CAR_POSITION_RUN_UPPER_2:
upper = SPRITE_RUN2;
lower = SPRITE_TERRAIN_EMPTY;
break;
}
if (upper != ‘ ‘) {
terrainUpper[CAR_HORIZONTAL_POSITION] = upper;
collide = (upperSave == SPRITE_TERRAIN_EMPTY) ? false : true;
}
if (lower != ‘ ‘) {
terrainLower[CAR_HORIZONTAL_POSITION] = lower;
collide |= (lowerSave == SPRITE_TERRAIN_EMPTY) ? false : true;
}

byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1;

// Draw the scene
terrainUpper[TERRAIN_WIDTH] = ‘\\0’;
terrainLower[TERRAIN_WIDTH] = ‘\\0’;
char temp = terrainUpper[16 – digits];
terrainUpper[16 – digits] = ‘\\0’;
lcd.setCursor(0, 0);
lcd.print(terrainUpper);
terrainUpper[16 – digits] = temp;
lcd.setCursor(0, 1);
lcd.print(terrainLower);

lcd.setCursor(16 – digits, 0);
lcd.print(score);

terrainUpper[CAR_HORIZONTAL_POSITION] = upperSave;
terrainLower[CAR_HORIZONTAL_POSITION] = lowerSave;
return collide;
}

// Handle the button push as an interrupt
void buttonPush() {
buttonPushed = true;
}

void setup() {
pinMode(PIN_BUTTON, INPUT);
digitalWrite(PIN_BUTTON, HIGH);
pinMode(PIN_AUTOPLAY, OUTPUT);
digitalWrite(PIN_AUTOPLAY, HIGH);

// Digital pin 2 maps to interrupt 0
attachInterrupt(0/*PIN_BUTTON*/, buttonPush, FALLING);

initializeGraphics();

lcd.init();

// Welkomsttekst tonen
lcd.setCursor(0, 0);
lcd.print(“Odisee”);
lcd.setCursor(0, 1);
lcd.print(“Elektromechanica”);
delay(2000); // 2 seconden tonen

lcd.clear(); // Scherm leegmaken voor spel

}

void loop() {
static byte CARPos = CAR_POSITION_RUN_LOWER_1;
static byte newTerrainType = TERRAIN_EMPTY;
static byte newTerrainDuration = 1;
static bool playing = false;
static bool blink = false;
static unsigned int distance = 0;

if (!playing) {
drawCAR((blink) ? CAR_POSITION_OFF : CARPos, terrainUpper, terrainLower, distance >> 3);
if (blink) {
lcd.setCursor(0, 0);
lcd.print(“Duw op de knop”);
}
delay(100);
blink = !blink;
if (buttonPushed) {
initializeGraphics();
CARPos = CAR_POSITION_RUN_LOWER_1;
playing = true;
buttonPushed = false;
distance = 0;
}
return;
}

// Shift the terrain to the left
advanceTerrain(terrainLower, newTerrainType == TERRAIN_LOWER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);
advanceTerrain(terrainUpper, newTerrainType == TERRAIN_UPPER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);

// Make new terrain to enter on the right
if (–newTerrainDuration == 0) {
if (newTerrainType == TERRAIN_EMPTY) {
newTerrainType = (random(3) == 0) ? TERRAIN_UPPER_BLOCK : TERRAIN_LOWER_BLOCK;
newTerrainDuration = 10 + random(6);
} else {
newTerrainType = TERRAIN_EMPTY;
newTerrainDuration = 10 + random(6);
}
}

if (buttonPushed) {
if (CARPos <= CAR_POSITION_RUN_LOWER_2) CARPos = CAR_POSITION_JUMP_1;
buttonPushed = false;
}

if (drawCAR(CARPos, terrainUpper, terrainLower, distance >> 3)) {
playing = false; // The CAR collided with something. Too bad.
for (int i = 0; i <= 2; i++) {
}
} else {
if (CARPos == CAR_POSITION_RUN_LOWER_2 || CARPos == CAR_POSITION_JUMP_8) {
CARPos = CAR_POSITION_RUN_LOWER_1;
} else if ((CARPos >= CAR_POSITION_JUMP_3 && CARPos <= CAR_POSITION_JUMP_5) && terrainLower[CAR_HORIZONTAL_POSITION] != SPRITE_TERRAIN_EMPTY) {
CARPos = CAR_POSITION_RUN_UPPER_1;
} else if (CARPos >= CAR_POSITION_RUN_UPPER_1 && terrainLower[CAR_HORIZONTAL_POSITION] == SPRITE_TERRAIN_EMPTY) {
CARPos = CAR_POSITION_JUMP_5;
} else if (CARPos == CAR_POSITION_RUN_UPPER_2) {
CARPos = CAR_POSITION_RUN_UPPER_1;
} else {
++CARPos;
}
++distance;

digitalWrite(PIN_AUTOPLAY, terrainLower[CAR_HORIZONTAL_POSITION + 2] == SPRITE_TERRAIN_EMPTY ? HIGH : LOW);
}
}