Sitzung: Jeden Freitag ab 14:30 s.t. online. Falls ihr den Link haben wollt, schreibt uns.

C-Kurs/TicTacToe

So in etwa soll dann unser Spiel aussehen

Tic Tac Toe (auch Drei Gewinnt genannt) ist ein Spiel, das von zwei Spielern auf einem 3x3-Felder großen Spielfeld gespielt wird. Dazu markieren beide Spieler jeweils abwechselnd ein freies Feld mit dem eigenen Symbol (Kreis oder Kreuz). Wer zuerst drei seiner Symbole horizontal, vertikal oder diagonal in einer Reihe hat, gewinnt das Spiel.

In dieser Aufgabe sollt ihr unter Verwendung der Simple DirectMedia Layer-Bibliothek (SDL) eine grafische Version dieses Spiels implementieren. Als Vorgabe dient das aus der Live-Vorführung im 5. Tutorium entstandene Beispielprogramm sdldemo.c.

Vorbereitungen

Voraussetzung für die Bearbeitung dieser Aufgabe ist die Installation der SDL-Bibliothek. Hinweise dazu findest du hier Ckurs2010/Vortrag09. Nachdem du die SDL-Bibliothek erfolgreich installiert hast, kannst du die sdldemo unter dieser Adresse herunterladen, entpacken und unter Linux mit folgendem Aufruf kompilieren:

gcc sdldemo.c -o sdldemo -I /usr/include/SDL -l SDLmain -l SDL

Je nach verwendetem Betriebssystem und Ort der Bibliothek können die Pfade oder notwendigen Parameter etwas abweichen - Details findest du unter Ckurs2010/Vortrag09#Einrichtung_der_SDL Einrichtung der SDL. Nun kannst du die Datei sdldemo.c umbenennen in tictactoe.c und als Vorlage für die Lösung dieser Aufgabe verwenden.

Datenstruktur

Das Spielfeld soll intern durch ein zweidimensionales Array der Größe 3x3 repräsentiert werden. Leere Felder sollen den Wert -1, vom ersten Spieler belegte Felder den Wert 0 und vom zweiten Spieler belegte Felder den Wert 1 enthalten. Am besten, du definierst dir mittels #define Konstanten wie EMPTY, PLAYER_A etc. für diese Feld-Werte sowie Konstanten für die Anzahl der Zeilen und Spalten des Spielfelds. Letztere kannst du dann verwenden um anschließend das globale Array board für das Spielfeld zu deklarieren.

Schreibe nun eine Funktion reset_board(), die das Array initialisiert, also alle Felder auf EMPTY setzt. Da es sich um ein zweidimensionales Array handelt, benötigst du dazu auch zwei for-Schleifen.


Grafikausgabe

TicTacToe-coords.png

Zunächst muss als Symbol für den zweiten Spieler die Kreuz-Grafik (Datei cross.bmp) geladen werden. Nun wird eine Funktion benötigt, die den im Array gespeicherten Spielstatus, also die Belegung des Spielfelds, grafisch ausgibt. Schreibe dazu die Funktion draw_board(...) mit allen nötigen Parametern. Die Funktion soll SDL_BlitSurface() verwenden, um zuerst den Hintergrund in das Ausgabefenster zeichnen, und dann für jedes Feld überprüfen, ob es schon von einem Spieler belegt ist, und ggf. das jeweilige Symbol an die entsprechende Stelle zeichnen. Vergiss nicht, am Ende SDL_Flip() aufzurufen.

Durch die vorgegebene Hintergrundgrafik ist jedes Feld 100 Pixel hoch und breit. Beachte, dass der Ursprung des Koordinatensystems in der linken, oberen Ecke ist. Darüber hinaus ist das Spielfeld von einem 25 Pixel breitem Rand umgeben, so dass das Feld in der linken oberen Ecke an den Koordinaten (25,25) beginnt, das Feld in der rechten unteren Ecke an den Koordinaten (225,225). (siehe Grafik)

Teste deine Funktion abschließend mit Testaufrufen wie:

reset_board();
board[0][0] = PLAYER_A;
draw_board(...);
SDL_Delay(2000);
board[2][2] = PLAYER_B;
draw_board(...);
SDL_Delay(2000);


Interaktion mit den Spielern

Nun soll der jeweils aktive Spieler mittels Mausklick ein Feld auf dem Spielfeld mit seinem Symbol markieren können. Dazu müssen zunächst die Koordinaten der Maus, die in einem Bereich zwischen (0,0) und (350,350) liegen, in Spielfeldkoordinaten zwischen (0,0) und (2,2) umgerechnet werden. Dazu kannst du die folgende Funktion convert_coords() verwenden.

// Konvertiert Mauskoordinaten in Spielfeldkoordinaten
void convert_coords(int x, int y, int *row, int *column) {
    *row = (y-25) / 100;
    if (*row < 0) *row = 0;
    if (*row >= ROWS) *row = ROWS-1;
    *column = (x-25) / 100;
    if (*column < 0) *column = 0;
    if (*column >= COLS) *column = COLS-1;
}

Im Event-Loop muss nun bei jedem Mausklick geprüft werden, ob das entsprechende Feld noch frei ist. Ist das der Fall, muss es vom aktuellen Spieler als belegt markiert werden. Danach muss das komplette Spielfeld via draw_board() neu gezeichnet und der aktive Spieler gewechselt werden.

Tipp: Um eine Variable x zwischen den Werten 0 und 1 wechseln zu lassen reicht die Anweisung:

x = 1 - x;


Spielende

Das Spiel endet entweder wenn der Spieler, der den letzten Zug gemacht hat, drei seiner Symbole in einer Reihe angeordnet hat, oder wenn kein freies Feld mehr auf dem Spielfeld übrig ist.

Zum Überprüfen des ersten Falls definierst du am besten eine Hilfsfunktion int check_win(int player) die zurückgibt, ob vom übergebenen Spieler im board-Array drei Felder in einer Reihe belegt sind. Dazu musst du das Array jeweils auf horizontale und vertikale 3er-Reihen, sowie diagonale Reihen von rechts oben nach links unten sowie von links oben nach rechts unten testen. Diese Funktion kann nun nach dem Belegen eines Feldes im Event-Loop aufgerufen werden.

Zum Überprüfen auf ein Unentschieden reicht eine einfache if-Anweisung. Überlege dir darüber hinaus was jeweils nach Spielende zu tun ist, damit die Spieler danach ein neues Spiel spielen können, ohne das Programm neu starten zu müssen.


Feinschliff / Erweiterungen

Spielende

Nach dem Spielende sollte den Spielern mitgeteilt werden, wer gewonnen hat oder ob das Spiel unentschieden endete. Am einfachsten kann das durch Anpassung des Fenstertitel via SDL_WM_SetCaption() erfolgen. Etwas hübscher wird es, wenn eine separate BMP-Grafik angezeigt wird, welche die gewünschte Nachricht enthält. Eine solche kann z.B. via Gimp oder unter Windows mit Paint erzeugt werden.

Alternativ könnten auch die drei Symbole des Gewinners zum Blinken gebracht werden, indem draw_board() in einer Schleife mit einer kurzen Pause aufgerufen wird, und die jeweiligen Felder bei jedem 2. Aufruf nicht mitgezeichnet werden.

Wer die Installation einer weiteren Bibliothek nicht scheut, kann mit der SDL-Erweiterung sdl_ttf TrueType-Fonts verwenden, um beliebigen Text in das Ausgabefenster zu schreiben. Eine Anleitung zur Verwendung dieser Erweiterung findest du in dem Tutorial unter http://lazyfoo.net/SDL_tutorials/lesson07/.

Audio

Die SDL Bibliothek ermöglicht die Wiedergabe von WAV-Dateien. Damit könntest du beim Setzen eines Symbols z.B. ein Klick-Geräusch abspielen. Ein Beispiel zur Verwendung der Audio-Funktionen findest du hier.

KI

Wer es leid ist, immer einen 2. Spieler suchen zu müssen, kann eine Künstliche Intelligenz (KI) implementieren. Auf Wikipedia gibt es eine Übersicht über ein paar einfache Regeln zum Gewinnen. Ein Algorithmus zur rekursiven Bestimmung der optimalen Strategie ist der Minimax-Algorithmus.


Viel Spaß!

Kommentare

Wenn du Anmerkungen zur Aufgabe hast oder Lob und Kritik loswerden möchtest, ist hier die richtige Stelle dafür. Klicke einfach ganz rechts auf "bearbeiten" und schreibe deinen Kommentar direkt ins Wiki. Keine Scheu, es geht nichts kaputt ;)