Tinkering Arduino

eine kleine Welt der Elektronik.

2048 Auf Dem Arduino Nano

Wie schon angekündigt, wollte ich auch zeigen, wie man 2048 ohne Esplora Board auf mit einem Arduino Nano oder Arduino Uno spielen kann. Dazu habe ich die Spiele-Bibliothek noch etwas erweitert und verbessert. So ist die komplette Logik des Spiels in der Bibliothek gekapselt und nur die notwendigen Funktionen stehen außerhalb der Bibliothek zur Verfügung. Variablen lassen sich nicht mehr direkt verändern, sondern können nur über sogenannte Getter-Funktionen ausgelesen werden.

Der Aufbau

Man benötigt folgende Teile:

  • Ein Arduino (z.B. Nano oder Uno). Ich habe hier mal den Sippino, ein Arduino Nano kompatiblen Bausatz verwendet.
  • 1 Steckbrett
  • 5 Taster
  • 5 Widerstände a 10kOhm
  • Dräthe und/oder Drathbrücken

Das Programm

Das Programm für den Arduino ist mit dem Programm aus dem [vorherigen Beitrag](/blog/2014/05/19/das-spiel-2048-auf-dem-arduino-esplora/ sehr ähnlich. Am Anfang erfolgen einige Includes und die Definition an welchem Pin welcher Taster angeschlossen ist.

1
2
3
4
5
#define BUTTON_START 7
#define BUTTON_U 6
#define BUTTON_D 5
#define BUTTON_L 3
#define BUTTON_R 2

Neu hingegen ist, dass Verwenden der neuen Klasse Game_2048 aus der Datei game_2048.h. Diese wird

1
Game_2048 game_2048;

instanziiert. Alle öffentlichen Funktionen der Klasse Game_2048 lassen sich dann über die Variable game_2048 aufrufen. Das werde wie später noch genauer sehen.

Die größeren Änderungen findet man in der Funktion drawMatrix(). Alle anderen Änderungen in den Funktionen für die Steuerung sind zum größten Teil Anpassungen an die Eingabe über Buttons anstatt über einen Joystick. Schauen wir die Funktion drawMatrix() genauer an.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
void drawMatrix() {
  int xOffset;
  int posX;
  int posY;
  unsigned int actCell = 0;

  for (unsigned int row = 0; row < 4; row++) {
    for (unsigned int col = 0; col < 4; col++) {
      posX = col * 31 + 2;
      posY = row * 31 + 30;
      actCell = game_2048.getCell( row, col);
      switch ( actCell ) {
        case 2:
          xOffset = 12;
          TFTscreen.fill(0xEE, 0xE4, 0xDA);
          break;
        case 4:
          xOffset = 12;
          TFTscreen.fill(0xED, 0xE0, 0xc8);
          break;
        case 8:
          xOffset = 12;
          TFTscreen.fill(0xF9, 0xF6, 0xF2);
          break;
        case 16:
          xOffset = 8;
          TFTscreen.fill(0xF5, 0x95, 0x63);
          break;
        case 32:
          xOffset = 9;
          TFTscreen.fill(0xF6, 0x7C, 0x5F);
          break;
        case 64:
          xOffset = 9;
          TFTscreen.fill(0xF6, 0x5E, 0x3B);
          break;
        case 128:
          xOffset = 6;
          TFTscreen.fill(0xED, 0xCF, 0x72);
          break;
        case 256:
          xOffset = 6;
          TFTscreen.fill(0xED, 0xCC, 0x61);
          break;
        case 512:
          xOffset = 6;
          TFTscreen.fill(0xED, 0xC8, 0x50);
          break;
        case 1024:
          xOffset = 3;
          TFTscreen.fill(0xED, 0xC5, 0x3F);
          break;
        case 2048:
          xOffset = 3;
          TFTscreen.fill(0xED, 0xC2, 0x2E);
          break;
        default:
          TFTscreen.fill(0xBB, 0xAD, 0xA0);
      }
      TFTscreen.rect( posX, posY, 29, 29);
      if ( !game_2048.isGameLost() && !game_2048.isGameWon() && ( actCell > 0 ) ) {
        tileValueString = String( actCell );
        tileValueString.toCharArray(tileValueCharArray, 5);
        TFTscreen.text( tileValueCharArray , posX + xOffset, posY + 10 );
      }
    }
  }

  if ( game_2048.isGameLost() ) {
    drawGameOver();
  }

  if ( game_2048.isGameWon() ) {
    drawGameWon();
  }

  game_2048.outputRefreshed();
}

Da der Zugriff auf die interne Repräsentation des Spielfeldes in der Bibliothek gekapselt ist, und im Programm so nicht zur Verfügung steht, müssen wir uns einer öffentlichen Getter-Funktion behelfen um einen Wert in für eine Zelle im Spielfeld auszulesen. In Zeile 11 wird dies getan. Wie man sieht, ist die interne Repräsentation als eindimensionales Array gekapselt und wir greifen über die Angabe von Zeile uns Spalte auf die Zelle zu.

In der Bibliothek ist die Umsetzung wie folgt gelöst.

1
2
3
unsigned int Game_2048::getCell( unsigned int row, unsigned int col) {
  return cells[row *4 + col];
}

Der Vorteil das so zu machen ist, dass man die interne Repräsentation des Spielfeldes auch anders realisieren kann, ohne an der Ausgabe etwas ändern zu müssen.
Auch die Logik für das Gewinnen oder das Verlieren eines Spiels ist in der Bibliothek Game_2048 realisiert. Im Steuer- und Ausgabeprogramm Arduino_2048 kann man diese Werte nicht mehr ändern, sondern wie in den Zeilen 61, 69 und 73 auslesen. Die Bibliothek stellt für diese Werte keine Setter-Funktionen bereit. Lediglich über die startGame()-Funktion lassen sich die Werte von Außen zurücksetzen.

Ein Problem, das mit bei der ersten Version aufgefallen ist, war das ständige Neuzeichnen des Spielfeldes. Das wurde auch gemacht, ohne dass es Änderungen gab. Daher habe ich der Bibliothek noch zwei Funktionen spendiert. Die eine Funktion needsOutputRefresh() gibt als Rückgabewert ein TRUE zurück, wenn sich das Spielfeld durch eine Aktion geändert hat. In diesem Fall müsste das Spielfeld wirklich neu gezeichnet werden. Die zweite öffentliche Funktion outputRefreshed() wird aufgerufen, wenn das Spielfeld neu gezeichnet wurde und setzt den entsprechenden booleschen Wert in der Bibliothek zurück.

Neben diesen Anpassung wurde auch eine Methode für das Gewinnen hinzugefügt.

Das Fazit

Die Verwendung einer Bibliothek erlaubt es, dass Zugriff auf die interne Repräsentation des Spieles nicht mehr möglich ist. Man kann die Funktionalität des Spieles anpassen, ohne dass man am Steuer- und Anzeigeprogramm etwas ändern müsste, vorausgesetzt, dass die öffentlichen Methoden sich nicht ändern. Anders herum, kann man aber auch das Steuer- und Anzeigeprogramm so ändern, ohne Rücksicht auf die interne Repräsentation des Spieles nehmen zu müssen.

Die Links

Das Programm Arduino_2048 findet man wie versprochen auf dem Github Account.

Comments