HARALD MELCHER

Startrek in C

Schöpfung des Weltalls

Körper im Weltall

Nachdem du bei bool bereits gesehen hast, dass man existierenden C-Type neue Namen geben kann, kombinieren wir das nun mit einem struct.

Ein struct ist ein Behälter, der ein oder mehrere Variable aufnehmen kann und sie mit Namen belegen, die nur in Verbindung mit dem struct gelten.

Der C Compiler behandelt struct wie die eingebauten Werte-Typen (z.B. int oder long). Das heißt, wenn ein struct als Argument einer Funktion auftaucht, übergibt der C-Compiler eine Kopie des struct – und damit kann die aufgerufene Funktion die übergeben Kopie zwar ändern, aber das “Original” bleibt unverändert.

Soll die aufgerufene Funktion auch das Original verändern können, übergibt man statt des structs (=Kopie) einen Zeiger auf das Original. (Genaunommen ist das dann eine Kopie des Zeigers, aber auch die zeigt auf das Original)

Die galaxy.h erhält nun ein struct, das dem typedef als Vorlage für einen neuen Typ-Namen dient:

typedef struct {
    char type;
    short x;
    short y;
} unit;

Auf Variablen in einem struct greift man mit der Punkt-Schreibweise zu, also zum Beispiel

unit klingon;
klingon.type = 'K';
klingon.x = 13;
klingon.y = 27;

Ist die Variable aber ein Pointer auf ein strukt, geht die Pfeilschreibweise, die weiter unten nochmal auftaucht.

unit* actual_unit = &units[4];
actual_unit->x = 16;
actual_unit->y = 3;

Initialisieren des Weltalls II

Aus main.c ruft der Code bereits das galaxy_init() von galaxy.c auf. Dieses Gerüst bekommt nun Leben:

In den Kopf von galaxy.c kommen drei #defines, die die Zahl der Sonnen auf 30, die Zahl der Klingonen auf 17 und die Zahl der Starbases auf 3 festlegen.

Ebenfalls vor die Funktionen kommt das Array units für die Körper im Weltall. Es muss ein Schiff und alle Sonnen, alle Klingonen und alle Starbases aufnehmen können, die alle vom neudefinierten Typ unit sind. Das sind 30+17+3+1 = 51 Units.

Dazu kommt eine Variable units_top_pos, in der das Programm die Zahl der Units mitzählt, die der Code bereits im Array eingefügt hat. Da noch keine Units im Array sind, startet ihr Wert bei 0.


Später werden wir auch so oft auf das Schiff (das dann erstes Element im Array ist) zugreifen, dass wir eine eigene Variable ship, die auch ein Unit ist, einführen. Dadurch vereinfacht sich der Zugriff auf das Schiff, das im ersten Element (index 0) des units-Arrays liegt.

Wenn wir dieser Variablen ship den Wert des ersten Arrayelements zuweisen würden, dann würde dadurch eine Kopie entstehen und wir könnten zwar Schiffspositionen in der Variablen ship (der Kopie) verändern, im Array wäre das erste Element aber unverändert. Daher erhält diese Variable den Typ (unit* ) und zeigt auf das erste Element. Tipp: Die Zuweisung von ship auf die Position des ersten Array-Elements geht sogar schon zu Compilezeit.


void galaxy_init() ruft insgesamt vier mal die Funktion void place_units(int count, char type) auf: Für 1 Ship, 30 Sonnen, 17 Klingonen und 3 Basen. Dabei gibt der erste Parameter die Zahl der zu erzeugenden Units an und der zweite Parameter das Kürzel für ihren Typ:

 S: Ship
*: Sonne
K: Klingone
B: Base

void place_units(int count, char type) wirft zuerst den Zufallszahlengenerator mit srand(time(0)); an. Dann führt sie für jede zu erstellende Unit aus:

  • die Unit in units, die die oberste im Array ist (die neu zu erstellende), erhält den im Parameter gewünschten Typ
  • die Funktion place_unit(&units[units_top_pos]) platziert die Unit auf einen freien Platz im Weltall. Sie erhält eine Referenz auf die zu platzierende Unit übergeben (und keine Kopie), weil sie den Wert der betreffenden Unit im Array ändern soll.
  • die Position des obersten belegten Elements im Array hat sich dadurch verändert…

void place_unit(unit* u) versucht, eine Unit im All unterzubringen. Sie erzeugt dazu in zwei Variablen für die x- und y-Koordinate mit rand() Zufallszahlen, die sie auf den Bereich von 0…63 einschränkt – die Spielfeldgröße. Dies wiederholt sie so lange, bis die Funktion char unit_type_at(x, y) angibt, dass die Position noch frei ist, indem sie ein Leerzeichen zurückgibt. Dann weißt place_unit() den Wert der Variablen x und y den Positionswerten der übergebenen Unit zu. Dazu gibt es zwei Möglichkeiten

  1. (*u).x = x; dereferenziert den Pointer und verwendet die bekannte Schreibweise für ein struct-Element
  2. u->x = x; ist die Pointer-Schreibweise – sie lässt schon eine Objektorientierung ahnen

char unit_type_at(int x, int y) gibt an, welchen Typ die Unit an der angegebenen Position hat. Dazu geht sie alle bis dahin eingetragenen Units durch und vergleicht die Koordinaten. Sind sie gleich wie die angefragten, dann liegt da schon eine Unit und die Funktion liefert dern Typ zurück. Ist bis zum Ende der Units im Array keines an der Position gewesen, dann ist das Feld noch frei und die Funktion liefert ein Leerzeichen (' ') zurück.


Eine probeweise in galaxy_init() eingebaute Schleife, die alle Objekte mit Typ und Position ausgibt, zeigt, ob das Beleben des Weltalls funktioniert hat.