Du liebst knifflige Rätsel und bastelst gerne mit Mikrocontrollern? Dann ist der Arduino Code-Knacker genau das richtige Projekt für dich! In diesem Tutorial zeige ich dir, wie du das klassische Logikspiel mit einem Arduino Uno, einem Encoder und einem hochauflösenden ILI9486 TFT-Display zum Leben erweckst. Dieses Projekt ist perfekt für alle, die tiefer in die Arduino-Programmierung einsteigen wollen. Wir schauen uns an, wie man eine flüssige Menüführung programmiert, Farben im RGB565-Format definiert und eine intelligente Spiel-Logik umsetzt, die bis zu 45 Versuche speichert. Egal ob als Geschenk oder für den eigenen Schreibtisch – dieser selbstgebaute Code-Knacker ist ein echter Hingucker für jeden Maker.
Spiel 92 aus der 200 Arduino Spiele Sammlung.
So wird gespielt: Logik und Taktik am Arduino
Das Prinzip des Arduino Code-Knackers basiert auf dem klassischen Deduktionsspiel: Ein Spieler (oder der Zufall) legt einen geheimen Farbcode fest, und der zweite Spieler muss diesen durch logisches Kombinieren entschlüsseln. Dabei geht es nicht nur um das Erraten der Farben, sondern auch um deren exakte Position innerhalb der vier Steckplätze. Nach jedem Versuch erhält der Spieler ein Feedback, das ihm hilft, die Lösung Schritt für Schritt einzugrenzen.
Der tatsächliche Ablauf an deiner "Kreativen Kiste" ist intuitiv und spannend gestaltet:
- Phase 1 – Den Code setzen: Zu Beginn wählt Spieler 1 über den Encoder vier Farben aus dem leuchtenden Set (Magenta, Blau, Rot, Grün) aus. Sobald der vierte Stecker bestätigt wurde, verschwindet der Code vom Display, um das Geheimnis zu wahren.
- Phase 2 – Das Raten: Spieler 2 versucht nun sein Glück. Durch Drehen am Encoder wählt er eine Farbe, ein kurzer Druck auf den Button bestätigt die Wahl für die aktuelle Position.
- Phase 3 – Die Auswertung: Nach einer vollständigen Vierer-Reihe erfolgt die automatische Analyse.
- Wert A: Zeigt an, wie viele Farben grundsätzlich richtig gewählt wurden (egal an welcher Stelle).
- Wert B: Zeigt an, wie viele Farben bereits an der exakt richtigen Position stehen.
- Phase 4 – Sieg oder Niederlage: Der Spieler hat insgesamt 45 Versuche Zeit. Gelingt es ihm, alle vier Positionen korrekt zu besetzen (Wert B = 4), erscheint der grüne Siegerbildschirm. Läuft der Countdown auf Null, ohne dass der Code geknackt wurde, heißt es "Game Over". Ein Resett des Spieles ist immer möglich, halte dazu den Button für länger als 3 Sekunden gedrückt.
Farben am TFT Display eingeben:
Die Hardware: So wird der Code-Knacker verdrahtet
Der Aufbau des Projekts ist dank der Verwendung von Standard-Bauteilen sehr übersichtlich. Das Herzstück ist ein Arduino Uno, der über die SPI-Schnittstelle das 4,0 Zoll TFT-Display steuert. Für die Eingabe nutzen wir einen einfachen Inkrementalgeber (Rotary Encoder).
Pin-Belegung des TFT-Displays (ILI9486 SPI)
Das Display wird über den SPI-Bus angeschlossen. Achte darauf, dass die Versorgungsspannung meist 3,3V oder 5V beträgt (je nach Shield-Version deines Displays).
| Display Pin | Arduino Uno Pin | Funktion |
| VCC | 5V / 3,3V | Stromversorgung |
| GND | GND | Masse |
| CS | Pin 10 | Chip Select |
| DC / RS | Pin 8 | Data / Command |
| RST | Pin 9 | Reset |
| SDI (MOSI) | Pin 11 | Datenleitung |
| SCK | Pin 13 | Taktsignal |
Pin-Belegung des Encoders (Eingabe)
Der Encoder übernimmt das Scrollen durch die Farben und die Bestätigung der Wahl. Die Pins 2 und 3 werden am Arduino verwendet, da diese Interrupt-fähig sind, was für eine flüssige Erkennung der Drehung sorgt.
| Encoder Pin | Arduino Uno Pin | Funktion |
| CLK (A) | Pin 2 | Drehsignal A |
| DT (B) | Pin 3 | Drehsignal B |
| SW (Button) | Pin 4 | Taster (integriert) |
| + / VCC | 5V | Stromversorgung |
| GND | GND | Masse |
Tipp für den Aufbau: Falls die Drehrichtung im Spiel falsch herum ist (beim Rechtsdrehen wandern die Farben nach links), musst du lediglich die Kabel an Pin 2 und Pin 3 miteinander vertauschen oder die Logik im Code anpassen. Der Taster an Pin 4 ist per Software mit einem internen konfiguriert, sodass kein externer Widerstand nötig ist.
Eingabe der Farben oben die Versuche, rechts die Auswertung.
Zentrale Code stellen:
1. Die Farbauswahl anpassen
Dies ist die zentrale Stelle, um die Optik des Spiels zu verändern. Hier definierst du, welche Farben dem Spieler zur Verfügung stehen.
Der Code:
// Palette: Magenta, Blau, Rot, Grün
uint16_t colors[] = {0xF81F, 0x001F, 0xF800, 0x07E0};
Erklärung: Die Farben werden im RGB565-Format (16-Bit) gespeichert. Das bedeutet, jede Farbe wird durch einen Hexadezimalwert repräsentiert. Wenn du eine Farbe ändern möchtest, musst du nur den entsprechenden Wert im Array austauschen. Wichtig ist, dass die Anzahl der Farben im Array (hier 4) mit der Modulo-Rechnung im () übereinstimmt.
2. Die Auswertungs-Logik (A und B Werte)
Das Herzstück des Spiels ist der Algorithmus, der den Versuch des Spielers mit dem geheimen Code vergleicht.
Der Code:
if (currentGuess[i] == secretCode[i]) {
b++; // Farbe UND Position richtig
sUsed[i] = gUsed[i] = true;
}
}
// ... danach folgt die Prüfung auf richtige Farben an falscher Stelle (a++)
Erklärung: Der Code prüft in zwei Durchläufen. Zuerst wird geschaut, ob Farben an der exakt gleichen Stelle stehen (Wert B). Diese Positionen werden markiert, damit sie im zweiten Durchlauf nicht doppelt gezählt werden. Dann wird geprüft, ob die restlichen gewählten Farben zumindest grundsätzlich im Code vorkommen (Wert A). Diese Logik sorgt dafür, dass das Spiel präzise Feedback gibt.
3. Speichereffiziente Historie (RAM-Management)
Besonders beim Arduino Uno ist der Arbeitsspeicher (SRAM) extrem knapp. Diese Stelle zeigt, wie man 45 Spielrunden speichert, ohne dass der Controller abstürzt.
Der Code:
byte history[45][4];
byte historyA[45];
byte historyB[45];
Erklärung: Standardmäßig verwenden viele Programmierer für Zahlen. Ein belegt auf dem Arduino Uno 2 Byte. Da unsere Farb-Indizes und Ergebnisse aber nie größer als 255 sind, nutzen wir den Datentyp (1 Byte). Bei 45 Spielrunden und mehreren Arrays sparen wir so massiv Arbeitsspeicher, was den Betrieb erst stabil macht und Platz für andere Funktionen lässt.
Vollständiger Arduino Code für den Code-Knacker:
// www.kreativekiste.de
// Version 1.9 10.02.2026
#include <SPI.h>
#include <ILI9486_SPI.h>
#include <Encoder.h>
// 1. PIN-DEFINITIONEN
#define TFT_CS 10
#define TFT_DC 8
#define TFT_RST 9
#define ENC_A 2
#define ENC_B 3
#define ENC_BTN 4
ILI9486_SPI tft(TFT_CS, TFT_DC, TFT_RST);
Encoder myEnc(ENC_A, ENC_B);
// 2. GLOBALE VARIABLEN
uint16_t colors[] = {0xF81F, 0x001F, 0xF800, 0x07E0}; // Farben
int secretCode[4];
int currentGuess[4];
// Historie für bis zu 45 Versuche
byte history[45][4];
byte historyA[45];
byte historyB[45];
int attempt = 0;
int currentPos = 0;
bool codeSet = false;
// Maße für Hochformat
const int SMALL_W = 45;
const int SMALL_H = 45;
const int LARGE_W = 60;
const int LARGE_H = 60;
const int ROW_HEIGHT = 55;
const int HEADER_H = 60;
// 3. HILFSFUNKTIONEN
void drawUI() {
tft.fillRect(0, 0, 320, HEADER_H - 5, 0x2104);
tft.setTextSize(2);
tft.setTextColor(0xFFFF, 0x2104);
if (!codeSet) {
tft.setCursor(10, 8);
tft.print("www.KREATIVEKISTE.de");
tft.setCursor(10, 34);
tft.print("CODE SETZEN...");
} else {
tft.setCursor(10, 20);
tft.print("NOCH ");
tft.print(45 - attempt); // Zählt von 45 runter
tft.print(" VERSUCHE");
}
}
void drawShape(int x, int y, uint16_t color, bool selected, int w, int h) {
if (selected) tft.drawRect(x-2, y-2, w+4, h+4, 0xFFFF);
else tft.drawRect(x-2, y-2, w+4, h+4, 0x0000);
tft.fillRect(x, y, w, h, color);
}
void resetGame() {
attempt = 0;
currentPos = 0;
codeSet = false;
// Historie-Arrays leeren (bis 45!)
for(int i = 0; i < 45; i++) {
historyA[i] = 0; historyB[i] = 0;
for(int j = 0; j < 4; j++) history[i][j] = 0;
}
tft.fillScreen(0x0000);
drawUI();
}
void redrawBoard() {
int startRow = (attempt >= 7) ? (attempt - 6) : 0;
for (int displayRow = 0; displayRow < 7; displayRow++) {
int actualRow = startRow + displayRow;
int yLoc = HEADER_H + (displayRow * ROW_HEIGHT);
for (int p = 0; p < 4; p++) {
uint16_t col = 0x0000;
if (actualRow < attempt) col = colors[history[actualRow][p]];
drawShape(10 + (p * 52), yLoc, col, false, SMALL_W, SMALL_H);
}
tft.fillRect(220, yLoc, 100, 50, 0x0000);
if (actualRow < attempt) {
tft.setTextSize(2);
tft.setTextColor(0x07FF, 0x0000); tft.setCursor(225, yLoc + 2);
tft.print("A:"); tft.print(historyA[actualRow]);
tft.setTextColor(0xFDA0, 0x0000); tft.setCursor(225, yLoc + 25);
tft.print("B:"); tft.print(historyB[actualRow]);
}
}
}
void checkGuess() {
int a = 0; int b = 0;
bool sUsed[4] = {false}; bool gUsed[4] = {false};
for (int i = 0; i < 4; i++) {
if (currentGuess[i] == secretCode[i]) { b++; sUsed[i] = gUsed[i] = true; }
}
for (int i = 0; i < 4; i++) {
if (!gUsed[i]) {
for (int j = 0; j < 4; j++) {
if (!sUsed[j] && currentGuess[i] == secretCode[j]) { a++; sUsed[j] = true; break; }
}
}
}
// Ergebnisse in Historie speichern
for(int i=0; i<4; i++) history[attempt][i] = currentGuess[i];
historyA[attempt] = a + b;
historyB[attempt] = b;
if (b == 4) {
// GEWONNEN
tft.fillScreen(0x07E0); // Alles Grün
tft.setTextColor(0x0000); tft.setTextSize(4);
tft.setCursor(45, 200); tft.print("GEWONNEN!");
delay(4000);
resetGame();
}
else if (attempt >= 44) {
// GAME OVER (45. Versuch war falsch)
tft.fillScreen(0xF800); // Alles Rot
tft.setTextColor(0xFFFF); tft.setTextSize(4);
tft.setCursor(45, 200); tft.print("GAME OVER");
delay(4000);
resetGame();
}
}
// 4. SETUP
void setup() {
pinMode(ENC_BTN, INPUT_PULLUP);
tft.setSpiKludge(false);
tft.init();
tft.setRotation(2);
tft.fillScreen(0x0000);
drawUI();
}
// 5. LOOP
void loop() {
long newPos = myEnc.read() / 4;
int colorIdx = abs(newPos % 4);
if (!codeSet) {
int xLoc = 35 + (currentPos * 65);
drawShape(xLoc, 180, colors[colorIdx], true, LARGE_W, LARGE_H);
} else {
int dRow = (attempt < 7) ? attempt : 6;
int yLoc = HEADER_H + (dRow * ROW_HEIGHT);
int xLoc = 10 + (currentPos * 52);
drawShape(xLoc, yLoc, colors[colorIdx], true, SMALL_W, SMALL_H);
}
if (digitalRead(ENC_BTN) == LOW) {
unsigned long pressStart = millis();
bool longPressActive = false;
while (digitalRead(ENC_BTN) == LOW) {
if (millis() - pressStart > 3000) {
longPressActive = true;
resetGame();
while(digitalRead(ENC_BTN) == LOW);
break;
}
}
if (!longPressActive && (millis() - pressStart > 50)) {
if (!codeSet) {
secretCode[currentPos] = colorIdx;
drawShape(35 + (currentPos * 65), 180, colors[colorIdx], false, LARGE_W, LARGE_H);
currentPos++;
if (currentPos > 3) {
delay(800);
codeSet = true;
currentPos = 0;
tft.fillScreen(0x0000);
drawUI();
}
} else {
currentGuess[currentPos] = colorIdx;
int dRow = (attempt < 7) ? attempt : 6;
drawShape(10 + (currentPos * 52), HEADER_H + (dRow * ROW_HEIGHT), colors[colorIdx], false, SMALL_W, SMALL_H);
currentPos++;
if (currentPos > 3) {
checkGuess();
if (codeSet) {
attempt++;
currentPos = 0;
redrawBoard();
drawUI();
}
}
}
}
}
}






