Project: |
activate https |
Beschreibung
Ich taufe dieses Spiel mit dem Namen: CPP_Tut
Dieses Spiel wurde rein aus Lehrzwecken programmiert. Es soll nicht
wirklich viel koennen, es soll nur ganz kurz eine kleine Einfuehrung
in C++ geben, wobei ausserdem auch auf die Nutzung objektorientierter
Moeglichkeiten verzichtet wurde, um alles so einfach wie moeglich zu
halten.
Siehe auch
Ein sehr viel grundlegenderes Programmieranfaengertutorial ist
coding for absolute dummies. Wenn du
hierhin gefunden hast, wird dich dieses evtl. interessieren.
Code
Um Schrittweise den Code durchzugehen, habe ich 3 Versionen hier
veroeffentlicht, die im Laufe des Programmierens entstanden, so dass man
gut sehen kann, wo und wie man ueberhaupt mal anfaengt etwas zu
programmieren (dies ist ein nicht unbedeutendes Hinderniss fuer Anfaenger,
die haeufig einfach gar nicht wissen, so sie ueberhaupt anfangen sollen)
und wie sich das Programm dann so langsam immer weiter aufbaut, bis
es letztendlich zu etwas Spielbarem/Benutzbarem wird.
Einfuehrung
Es sind hier nicht sehr viele Vorraussetzungen noetig, um das Spiel zu
verstehen. Ich gehe nur davon aus, dass ihr vielleicht bereits einmal in
ein C/C++ Buch geguckt habt und somit C-Syntax lesen und verstehen koennt.
Das Beispiel sollte auf jedem beliebigen System mit einer Shell laufen.
Das koennte Windows ausschliessen - ich habe es ehrlich gesagt nie auf
Windows getestet. Es reicht aber, wenn ihr SSH-Zugang zu einem beliebigen
Linux-PC habt, denn das Programm laeuft rein auf der Konsole.
Die meisten der Funktionen bauen auf dem beruechtigten "Hallo Albert"
Anfangsbeispiel auf. Hier kurz noch mal die Wiederholung davon:
"Hallo Albert" Beispiel
#include <iostream> using namespace std; int main() { cout << "Hallo Albert" << endl; return 42; }Mit #include wird alles in der entsprechend angegebenen Datei zu deiner Datei hinzugefuegt. In dem Fall bekommst du also alle in iostream definierten Funktionalitaeten. Das sind, wie der Name sagt (Abkuerzung fuer Input/Output Streaming), die Moeglichkeiten, Daten fuer den Benutzer auszugeben und auch Daten von ihm einzulesen. In dem Fall brauchen wir nur die Ausgabe-Moeglichkeit, denn sehr viel tut das Programm ja nicht.
Kompilieren
Das Kompilieren geht einfach: Wenn du eine Datei mit dem Namen
kompiliere_mich.cpp hast, musst du (im Falle von C++) folgendes
aufrufen:
g++ kompiliere_mich.cpp -o kompiliertes_ichStarten kannst du die kompilierte Version dann so:
./kompiliertes_ichUnd bestaune das Ergebnis...
Der Anfang - 1. Version
Download: spiel1.cpp
Ladet euch den Code am besten runter und schaut ihn euch einmal an.
In dieser 1. Version wollen wir eine Kugel sich von links nach rechts ueber
den Bildschirm bewegen lassen.
Wir beginnen also mit dem Schreiben unserer
Main-Funktion, denn dies ist schliesslich unsere Startfunktion. Die
komplette Ausgabe soll ueber die Konsole geschehen, weil dies erstmal
das einfachste ist, was wir auch koennen. Da es nicht so ganz leicht
erscheint, die genaue Groesse der Konsole irgendwie zu ermitteln,
legen wir fuer den Anfang mal eine feste Hoehe und Breite fuer die
Ausgabe auf der Konsole fest. Passende Variablennamen dazu sind sicherlich
screen_width und screen_height. Da wir erstmal sowieso nur
eine Bewegung von links nach rechts realisieren wollen, ignorieren wir
vorerst alles auf der Y-Koordinate.
Nun wollen wir uns unsere Kugel definieren. Wir koennten dafuer eine
einfache Variable definieren, die fuer die X-Koordinate der Kugel steht,
aber wir wollen direkt einmal einen allgemeinen Kugel-Typ definieren,
wenn wir spaeter mal mehrere solcher Kugeln anzeigen lassen wollen.
Wir definieren uns also eine Struktur, die im
Endeffekt eine Kugel repraesentieren soll. Eine Struktur ist nichts anderes
als eine Menge von Variablen, also quasi eine Menge von Eigenschaften. In
diesem Fall hat unsere Struktur Kugel (so nennen wir die passenderweise)
die Eigenschaften c
(im Prinzip das visuelle Bild der Kugel; in dem Fall ein einzelnes Zeichen
(char)), x und y (die Koordinaten der Kugel). Da diese
Struktur fuer den gesamten restlichen Code bekannt sein soll, packen wir sie
ganz an den Anfang (allerdings unterhalb der Includes; dies ist so ueblich).
Kehren wir nun zurueck in die Main-Funktion. Hier erstellen wir nun eine
Variable von unserem gerade definierten Kugel-Typ; die Variable nennen wir
einfach mal myKugel. Sie soll als ein grosses X dargestellt werden
und erstmal am Anfang auf der linken Seite sein.
Die Kugel soll sich nun bewegen. Wir wollen einfache jede Position
von ganz links bis ganz rechts einmal durchlaufen. So etwas realisiert
man am besten mit einer Schleife, die genau jede Position einmal
durchlaeuft.
Eine for-Schleife bekommt 3 Parameter inklusive einem Codeblock.
Der 1. Parameter definiert den Startwert. Wir wollen hierfuer mal eine
neue Variable definieren, wir koennten aber auch direkt myKugel.x
als Laufvariable verwenden. Unsere Laufvariable nennen wir passenderweise
mal pos_x und wir wollen ganz links beginnen, also an Position 0.
Der 2. Parameter bestimmt die Abbruchbedingung der Schleife. Die
for-Schleife wird dabei genau so lange immer wieder neu ausgefuehrt,
so lange die Bedingung des 2. Parameters den Wert true zurueckgibt.
Dieser 2. Parameter wird also zu Beginn jedes Durchlaufes des Codeblocks
ausgewertet und die komplette Auswertung der Schleife bricht genau dann ab,
wenn die Bedingung false ergibt. In unserem Fall soll die Schleife
immer dann noch weiter durchlaufen werden, so lange die Bewegung noch nicht
zu Ende ist. Die Bewegung ist dann zu Ende, wenn die Kugel ueber den rechten
Bildschirmrand hinaus will.
Der 3. Parameter laesst die Laufvariable laufen. In unserem Fall soll sie
immer genau einen Wert vorwaerts gehen. Das pos_x++ ist dabei eine
Abkuerzung fuer pos_x = pos_x + 1.
Im Inneren des Schleifen-Codes, also des Codeblocks der Schleife, wollen
wir nun die Kugel an die entsprechende Position setzen, also die Bewegung
durchfuehren. Dies ist ein einzelner Befehl (myKugel.x = pos_x;).
Nachdem das getan ist, kommt natuerlich noch ein entscheidender bisher
fehlender Teil in unserem Programm, naemlich die Darstellung von dem,
was wir da eigentlich tun, naemlich die Kugel zu bewegen.
Unser aktueller Status ist der, dass wir nun die Kugel an einer bestimmten
Position haben und dies nun auf dem Bildschirm darstellen wollen.
Dazu lassen wir erstmal alles bisherige auf dem Bildschirm komplett loeschen
und zeichnen dann das neue Bild. Da wir ja auf der Konsole unsere Sachen
ausgeben lassen, muessen wir dort also alles aktuell zu sehende loeschen.
Wie man dies genau tut, erfaehrt man auch leicht im Internet. Die Technik
ist im Prinzip die, dass bestimmte Steuerzeichen fuer die Konsole genau das
bewirken, naemlich das loeschen des aktuell zu sehenden Bereichs.
Da wir den Code verstaendlich halten wollen, packen wir dieses Senden der
speziellen Steuerzeichen in eine eigene Funktion, die wir passenderweise
clear_screen() nennen (die nichts zurueckgeben muss, deshalb
void). Nun zurueck in der Schleife rufen wir diese Funktion
clear_screen() einmal auf, um genau das zu bewirken, was wir
wollten.
Nachdem wir das getan haben, haben wir nun also einen leeren Bildschirm.
Auf diesen muessen wir jetzt noch die Kugel irgendwie bekommen. Die Kugel
befindet sich an Position myKugel.x. Bisher kuemmern wir uns
erstmal nur um die X-Koordinate, d.h. wir zeichnen nur eine Zeile. Wir
gehen also diese eine Zeile von links nach rechts durch und geben
ein "X" aus, wenn wir gerade an der Position sind, wo die Kugel sich
befindet, ansonsten geben wir ein Leerzeichen aus (also ' '). Dieses
Leerzeichen auszugeben ist natuerlich wichtig, denn ansonsten wuerde
die Kugel einfach immer am linken Rand festkleben. Probiert es einfach
aus, indem ihr den entsprechenden Bereich auskommentiert (d.h.
setzt einfach // vor den Befehl).
Wenn wir den bisherigen Code so testen (testet es am besten selbst),
werden wir feststellen, dass die Bewegung viel zu schnell ablaeuft.
Warum das so ist? Wir haben an keiner Stelle etwas einprogrammiert,
was die Bewegung langsam machen sollte. Damit die Bewegung langsamer
ablaeuft, lassen wir das Programm in jedem Schleifendurchlauf einfach
fuer 100 Millisekunden warten.
Nach einer kurzen Suche im Internet finden wir die Funktion usleep.
Dafuer muessen wir noch eine 2. Datei includen und zwar die unistd.h.
Diese benoetige wir, um die Funktion usleep benutzen zu koennen.
usleep tut nichts weiter, als das Programm fuer die entsprechende
Anzahl an Mikrosekunden in den Schlaf zu versetzen, also beispielsweise
usleep(250 * 1000); setzt das Programm 1/4 Sekunde in den Schlaf.
Solche Sachen weiss man in der Regel natuerlich nicht auswendig, aber dafuer
gibt es ja das Internet. Es hat auch nicht besonders viel mit Programmieren
zu tun, jede noch so exotische Funktion auswendig zu kennen. Bei so
elementaren Funktionen wie eine Schlaf-Funktion findet man in der Regel
recht schnell das was man braucht. Und ansonsten gibt es ja auch
zahllose Foren, wo einem geholfen wird.
Nachdem wir das eingebaut haben, werden wir feststellen, dass das
Programm nun zwar viel langsamer ablaeuft, aber nicht jeder einzelne
Bewegungsschritt richtig gezeichnet wird. Dies scheint uns im ersten Moment
voellig unverstaendlich zu sein. Nach ein wenig weiterer Forschung im
Internet finden wir heraus, dass cout einen Cache benutzt, d. h.
es wird nicht sofort direkt alles ausgegeben, sondern erstmal gespeichert
und erst dann ausgegeben, wenn genug Daten zusammen sind. Das ist
natuerlich nicht gut fuer das was wir wollen, naemlich das das Bild immer
sofort direkt ausgegeben wird. Dies ist mit der flush() Methode
von cout moeglich, die alle Daten, die im Moment im Cache sind, in
jedem Fall nun ausgibt.
Nach einem weiteren Test werden wir feststellen, dass das Programm
nun endlich genau das tut, was wir wollen, naemlich die Anzeige
der Bewegung einer Kugel von links nach rechts.
Screenshot:
Es schreitet voran - 2. Version
Download: spiel2.cpp
Wir wollen unser bisheriges Programm nun so erweitern, dass wir anstatt
nur einer Kugel gleich mehrere Kugeln darstellen koennen. Ausserdem
wollen wir uns nun auch um die Y-Koordinate kuemmern. Dies sind keine
wirklich grossartigen Erweiterungen, aber sie werden schon mal
mehr Moeglichkeiten fuer weiteres schaffen.
Da wir ein einheitliches System schaffen wollen, wo wir alle Kugeln
unterbringen, erschaffen wir eine neue Struktur, in der wir eine Liste
aller Kugeln und sonstigem zu zeichnendem Inhalt speichern wollen.
Diese Struktur nennen wir World. Diese Welt enthaelt auch eine
Groesse. Im eigentlichen Code, d.h. in der Main-Funktion, erstellen wir
uns nun eine Variable von unserem neu definierten Typ World; die
Variable nennen wir myWorld. Diese Welt, in der unsere Kugeln
sein werden, soll die komplette Groesse des Bildschirms einnehmen.
Die Welt soll erstmal aus 2 Kugeln bestehen, was wir in der Struktur
vorerst direkt festlegen muessen. Wir koennen diese Zahl auch variable
halten, das wird aber dann etwas mehr Aufwand. Wir definieren uns direkt
auch eine Funktion World_KugelnCount, die die Anzahl der Kugeln
in einer Welt ermittelt. Die 2 Kugeln in World sind als Array
definiert, ein Array mit 2 Elementen vom Typ Kugel. Die Funktion
World_KugelnCount tut nichts anderes, als die Groesse des
Arrays zu betrachten und durch die Groesse einer einzelnen Kugel
zu teilen - so bekommen wir natuerlich die Anzahl der Elemente in dem
Array.
Zurueck in der Main-Funktion weisen wir nun unseren 2 Kugeln bestimmte
Startwerte zu. Die 1. Kugel ('A') soll links oben in der Ecke sein
und die 2. Kugel ('B') rechts unten. Wir wollen nun, dass sich beide
Kugeln gleichzeitig bewegen - und zwar soll sich die Kugel 'A' von
links nach rechts bewegen und die Kugel 'B' von rechts nach links -
beide mit gleicher Geschwindigkeit.
Wir lassen dazu unsere bisherige for-Schleife so wie sie ist,
nur dass wir den Codeblock nun ein wenig anpassen muessen. Die
Kugel 'A' bekommt dabei genauso wie im 1. Beispiel die Position der alten
Kugel; die Kugel 'B' bekommt nun genau die gleiche Entfernung, allerdings
vom rechten Rand aus gemessen. Um genau zu sein ignorieren wir im
Moment voellig die Groesse von myWorld, sondern beziehen uns direkt
auf die Groesse vom gesamten Bildschirmbereich, was aber egal ist, da
beide Groessen uebereinstimmen.
Das Zeichnen der Kugeln wird jetzt noch erweitert durch das Zeichnen
jeder einzelnen Zeile (bisher war es ja nur eine einzige). Um den Code
uebersichtlich zu halten, verpacken wir alles, was mit dem Zeichnen zu
tun hat, in eine Funktion, die wir passenderweise World_draw nennen
wollen. Diese Funktion soll als Parameter die entsprechend zu zeichnende
Welt bekommen, also eine Variable vom Typ World; in unserem Fall
wird sie also myWorld ueberwiesen bekommen. Ganz am Ende in der
Schleife soll dann natuerlich, wie auch im ersten Beispiel, eine
realistische Geschwindigkeit mit usleep erzwungen werden.
Wir muessen nun also die Funktion World_draw noch programmieren.
Der Aufbau ist aehnlich wie schon im ersten Beispiel. Wir beginnen damit,
erstmal den Bildschirm zu saeubern, um einen leeren Bildschirm zu haben.
Im ersten Beispiel haben wir nun einfach immer ein Leerzeichen gezeichnet
bis zu dem Punkt, wo die Kugel zu zeichnen war. In diesem Fall haben wir
evtl. auch manche Zeilen, wo ueberhaupt keine Kugeln sind (da wir nur
2 Kugeln ueberhaupt bei unserer Welt zu zeichnen haben, werden das auch
einige Zeilen sein). Wir wollen also erstmal untersuchen, ob wir in dieser
Zeile ueberhaupt etwas zu Zeichnen haben und erst wenn wir wissen,
dass sich wirklich etwas in der Zeile befindet, zeichnen wir auch diese
Zeile. Das Zeichnen der Zeile selbst geschieht dabei dann genauso wie
beim 1. Mal, nur dass wir diesmal noch ein paar mehr Kugeln zu
ueberpruefen haben. Nach der Zeile muessen wir natuerlich noch zur
naechsten Zeile springen, also muessen wir ein endl ausgeben.
Zum Test koennt ihr mal den Part mit der Ueberpruefung, ob die Zeile
ueberhaupt gezeichnet werden soll, weglassen (d.h. auskommentieren)
und draw_this_line immer auf true setzen. Wenn ihr einen
etwas aelteren PC habt, werden ihr vielleicht einen kleinen Unterschied
bemerken.
Screenshot:
Endversion - 3. Version
Download: spiel3.cpp
Wir wollen nun von der automatischen Bewegung der Kugeln weggehen
und die Kugeln vom Benutzer bewegen lassen. Dazu muessen wir also
eine Auswertung der Benutzereingaben vornehmen. Ich habe bereits
am Anfang von dein Eingabe Moeglichkeiten der iostream
geredet. Diese sind in unserem Fall aber eher nicht zu gebrauchen, denn
diese Eingaben dort (das cin-Objekt) sind dafuer gedacht, irgendwelche
Werte vom Benutzer zu erfragen und diese dann weiter zu verarbeiten.
Fuer unser kleines Programm ist es aber besser, wenn wir nur genau
ein einzelnes Zeichen vom Benutzer erfragen. Dazu sehen wir uns mal wieder
ein bisschen im Internet um und stossen auf die vorprogrammierte Funktion
getch. Diese kopieren wir uns einfach in unser Programm rein.
Diese Funktion benoetigt noch das Includen der Datei termios.h.
Mit dieser Funktion haben wir nun die Moeglichkeit, ein einzelnes Zeichen
vom Benutzer abzufragen. Wie die Funktion selber genau funktioniert,
interessiert uns dabei weniger, uns reicht zu wissen, dass wir so das
naechste vom Benutzer eingegebene Zeichen ermitteln koennen.
Wir wollen unsere Welt erweitern auf 5 Kugeln. Dazu passen wir einfach
unsere World-Struktur entsprechend an. 2 von diesen 5 Kugeln
wollen wir von der Tastatur steuern lassen, die 1. dabei von den Tasten
'w', 'a', 's', 'd' und die 2. von den Tasten 'i', 'j', 'k', 'l'.
Da es aber bloed ist, immer nur die gleichen 2 Kugeln steuern zu koennen,
fuehren wir 2 Variablen input1 und input2 ein, die festlegen,
welche 2 Kugeln wir steuern. Wenn die Taste 'e' gedrueckt wird, soll
input1 neu festgelegt werden koennen, bei der Taste 'o'
entsprechend fuer input2.
Soweit erstmal die Idee der Steuerung. Mit ESC soll das Programm beendet
werden.
Beim Initialisieren unserer Welt muessen wir uns noch um die Startpositionen
kuemmern. Da es langweilig ist, wenn die Kugeln immer an den gleichen
Positionen starten, wollen wir die Position zufaellig festlegen.
Nach einer weiteren Nachforschung im Internet stossen wir dazu auf die
Funktion rand(). Um diese Benutzen zu koennen, muessen wir die Datei
stdlib.h includen. rand() liefert dabei dann einen zufaelligen
positiven ganzzahligen Wert zurueck. Diese Zufallsfunktion muss bei Beginn
des Programms aber erstmal initialisiert werden mit der Funktion
srand(). Wir benoetigen hierfuer einen zufaelligen Anfangswert,
der jedes Mal anders sein wird. Hierfuer bietet sich die Zeit an, also
benotigen wir noch einen Include fuer die Datei time.h, die uns
dann die Funktion time(..) bereitstellt. Zum Initialisieren der
Zufallsfunktion erstellen wir uns nun eine Funktion randomize.
Das alles ist so ueblich und findet man so in jedem 2. Beispiel zur
Anwendung der rand()-Funktion.
Zurueck in der Main-Funktion: Wir sind immer noch dabei, die 5 Kugeln
zu initialisieren. Wir wollen diese mit 'A' bis 'E' bezeichnen.
Die Position soll zufaellig gewaehlt im kompletten Raum der Welt sein.
Mit rand() % myWorld.width erhalten wir einen zufaelligen Wert
von 0 bis myWorld.width - 1, denn % ist der Modulo-Operator,
der den rechten Wert so lange von dem linken Wert abzieht, bis dieser
kleiner als der rechte ist. Entsprechendes tun wir fuer die Y-Koordinate.
Wir beginnen nun damit, die Eingaben des Benutzers abzufragen. Dafuer
definieren wir uns eine Variable input, in der wir das zuletzt
vom Benutzer eingegebene Zeichen speichern wollen. Dieses wollen wir
immer wieder neu abfragen, quasi endlos, bis der Benutzer mit ESC mitteilt,
er moechte das Programm beenden. Diesen Status wollen wir in der Variable
end speichern. Wir gehen nun also in eine Schleife, die so lange
wiederholt werden soll, solange end == false ist. Dafuer bietet
sich die while-Schleife an. In ihr programmieren wir die
entsprechenden Aktionen auf das eingegebene Zeichen, so wie wir sie haben
wollen. Zur einfacheren Handhabung der Bewegung einer Kugel definieren
wir uns noch eine Kugel_move-Funktion, die nichts anderes tut,
als eine Kugel zu bewegen.
Unsere World_draw-Funktion wollen wir auch etwas erweitern - und
zwar sieht es noch netter aus, wenn wir einen Rand um die Funktion
drumrum zeichnen. Wir wollen ausserdem die aktuelle Position jeder
Kugel angeben. Den Aufruf dieser Funktion World_draw_debug, die genau
das tut, koennen wir auch spaeter auskommentieren, wenn wir diese
Informationen nicht mehr ausgeben wollen.
Im Prinzip ist damit erstmal alles fertig, was wir haben wollten.
Irgendwelche Erweiterungen sollten jetzt auch nicht weiter schwierig
sein.
Screenshot:
Fragen, Sonstiges
Falls ihr irgendwelche Fragen oder sonstige Rueckmeldungen habt,
schreibt mich einfach an. Ich werde bei Aenderungswuenschen dann auch
entsprechend diesen Artikel erweitern.
Weiterfuehrendes Tutorial
Ich kann jedem nur waermstens die Programmiersprache Free Pascal empfehlen.
Es handelt sich um einen freien Open Source Pascal Compiler. Darauf aufbauend
gibt es Lazarus, eine RAD-Entwicklungsumgebung aehnlich wie Delphi oder
Visual Basic auf Basis einer sehr maechtigen Programmiersprache mit allen
Vorzuegen der objektorientierten Programmierung. Auch ohne diese vielen
schoenen Moeglichkeiten der Programmiersprache zu nutzen, kann man aber
bereits schon so einiges schaffen. Als aehnliches Tutorial wie dies hier
habe ich dazu ein etwas komplexeres Spiel programmiert, welches aber im
Prinzip genauso wenig Voraussetzungen benoetigt wie auch dieses Tutorial.
Hier ist der Link: Robot 2
If you want to support my work, please donate via Gittip/Flattr here:
The program published here is under the copyright of Albert Zeyer. In that case there is the source code to download, it is under the LGPL-licence. Distributions of it are only allowed with a reference to this page.
Links
- Other projects
- Mainsite
Albert Zeyer (Mail)
You are the 2277094th human, who was not scared by this site.
"No, no, do the goldfish!" tongued the ear licking housewife as the bung-hole stuffing friar slurped her pendulous eyes and pounded his warty stick shift into her soft-spoken jam jar.
15:21:51 up 1368 days, 21:03, 10 users, load average: 0.01, 0.04, 0.05
The code can be seen here. Please contact me if you find any problems. :)