Teil 19 „Time & motion“

Der C-Kurs
Antworten
Benutzeravatar
bodo
User
Beiträge: 319
Registriert: 14.02.2007, 17:21
Kontaktdaten:

Teil 19 „Time & motion“

Beitrag von bodo » 02.01.2011, 12:39

C für BASIC-Programmierer

Arbeiten mit dem z88dk Cross-Compiler

von Jens Sommerfeld und Bodo Wenzel

Teil 19

Bei Vickers heißt das Kapitel 19 „Time & motion“. Wir wollen uns deshalb hier mit Funktionen befassen, die für Benutzereingaben und Zeiterfassung dienen. Damit ist ein Teil der für Spiele notwendigen Funktionalität realisierbar.

Weil dieses ja ein Kurs in C und kein Kurs in Spieleprogrammierung ist, folgt „nur“ eine Vorstellung ausgewählter Funktionen jeweils mit einem Beispielprogramm.

Was für euch angehender C-Programmierer interessant daran ist: Funktionen dienen dem Abstrahieren. Ihr seid nicht wie bei BASIC auf eine Funktionalität festgelegt, sondern ihr könnt durch Aufruf einer anderen, häufig selbstgeschriebenen Funktion eine gewünschte Funktionalität erreichen. So ermöglicht z.B. der unten beschriebene „richtige“ Tastaturtreiber die automatische Wiederholung von Tasten.

Abfrage auf gedrückte Taste

Häufig ist nur wichtig, ob der Anwender irgendeine Taste gedrückt hat; welche Taste das war, ist dagegen egal. Hier gibt es seit seligen DOS-Zeiten die Funktion kbhit(), die dann „wahr“ zurückgibt, wenn eine Taste gedrückt wurde. Dazu muss noch die Headerdatei „conio.h“ angegeben werden:

Code: Alles auswählen

#include <conio.h>
#include <stdio.h>

int main(void) {
    puts("WARTE AUF TASTE...");
    while (!kbhit());
    puts("FERTIG");

    return 0;
}
INKEY$ „auf C“

Natürlich wollt ihr alle wissen, wie ihr die heiß geliebte Abfrage auch in C durchführen könnt. Die entsprechende Funktion heißt beim z88dk getk(). Sie liefert das Zeichen '\0' (Dieses kennzeichnet normalerweise das Ende einer Zeichenkette, siehe Teil 7.), wenn keine Taste gedrückt wurde, und das Zeichen auf der Taste (Übrigens auch das Leerzeichen... Kein BREAK mehr, juchhu!), wenn eine gedrückt wurde. Wie INKEY$ auch, blockiert der Aufruf der Funktion nicht den Programmlauf.

Code: Alles auswählen

#include <stdio.h>

int main(void) {
    int taste;

    puts("ENDE = Q");
    do {
        taste = getk();
        if (taste != '\0') {
            printf("TASTE %02x ", taste);
            while (getk() != '\0');
            puts("LOSGELASSEN");
        }
    } while (taste != 'Q');

    return 0;
}
Das z88dk enthält einen „richtigen“ Tastaturtreiber

Das war auch für uns eine Überraschung: das z88dk hat in den mitgelieferten Bibliotheken eine richtig schöne Unterstützung von Benutzereingaben. Die dazugehörige Headerdatei heißt „input.h“, aus der wir hier zunächst einige Funktionen zur Tastaturabfrage vorstellen.

Was sonst durch einen Hardwarezusatz realisiert werden muss, kann ja eigentlich auch Software schaffen, siehe ASZMIC. Gemeint ist die automatische Wiederholung von Tastendrücken, wenn der Benutzer die Taste gedrückt hält. Dazu gibt es zwei Funktionen, in_GetKeyReset() bereitet den Treiber vor, und in_GetKey() liefert uns das nächste Zeichen von der Tastatur. Weiterhin müssen wir vier globale Variablen anlegen und mit den folgenden Werten füllen:
  1. in_KeyDebounce enthält die Anzahl Aufrufe von in_GetKey(), bevor eine Taste als gedrückt akzeptiert wird. Dieser Wert dient der Entprellung.
  2. in_KeyStartRepeat enthält die Anzahl Aufrufe von in_GetKey(), bevor die automatische Wiederholung bei dauerhaft gedrückter Taste einsetzt. Diesen Effekt könnt ihr auch an eurer PC-Tastatur sehen.
  3. in_KeyRepeatperiod enthält die Anzahl Aufrufe von in_GetKey(), die zwischen zwei automatisch wiederholten Tasten liegen. Damit wird die Wiederholungsrate festgelegt.
  4. in_KdbState wird für die interne Verwaltung des Tastaturtreibers benötigt.
Eine kleine Zeichnung verdeutlicht das Zeitverhalten. In den Kästchen sind die Aufrufe von in_GetKey() aufgetragen, das 'X' kennzeichnet die Rückgabe des Zeichens, sonst wird '\0' zurückgegeben:
19.png
19.png (11.02 KiB) 2457 mal betrachtet

Code: Alles auswählen

#include <input.h>
#include <stdio.h>

uchar in_KeyDebounce = 1;
uchar in_KeyStartRepeat = 255;
uchar in_KeyRepeatperiod = 5;
uint in_KbdState;

int main(void) {
    int taste;

    in_GetKeyReset();

    puts("MIT AUTO-REPEAT (ENDE = X):");
    do {
        taste = in_GetKey();
        if (taste != '\0') {
            printf("TASTE %02x ", taste);
            while (in_GetKey() != '\0');
            puts("LOSGELASSEN");
        }
    } while (taste != 'X');

    puts("OHNE AUTO-REPEAT (ENDE = Q):");
    do {
        taste = in_Inkey();
        if (taste != '\0') {
            printf("TASTE %02x ", taste);
            while (in_Inkey() != '\0');
            puts("LOSGELASSEN");
        }
    } while (taste != 'Q');

    return 0;
}
Falls ihr zwar diese Eingabemethode verwenden möchtet, aber keine automatische Wiederholung braucht, benutzt ihr die Funktion in_Inkey(). Warum die wohl so heißt? ;-)

Wie ihr am Testprogramm sehen könnt, blockieren die Funktionen nicht den Programmlauf, sondern sie liefern wie getk() das Zeichen '\0', wenn keine Taste gedrückt ist.

Wenn das Programm warten soll

Im z88dk gibt es zwei Funktionen, mit denen der Programmlauf „ausgebremst“ werden kann. Bei in_Pause() kann der Anwender die Pause unterbrechen, indem er eine Taste drückt. in_Wait() ist dagegen stur und ignoriert jeden Tastendruck. Beide Funktionen erwarten die abzuwartende Zeit in Millisekunden...

Code: Alles auswählen

#include <input.h>
#include <stdio.h>

int main(void) {
    int i;

    for (i = 0; i < 3; i++) {
        puts("PAUSE...");
        in_Pause(5000);
        while (in_Inkey() != '\0');
    }

    puts("WAIT...");
    in_Wait(5000);

    return 0;
}
Hausaufgabe: Warum steht die Zeile "while (in_Inkey() != '\0');" nach dem Aufruf von in_Pause()?

Joystick-Emulation

Von PC-Spielen seid ihr es ja gewohnt, dass ihr die Eingabemethode auswählen und konfigurieren könnt. Ähnliches kann das z88dk auch, wenn ihr die Funktionen für Joysticks verwendet. Dazu müsst ihr eine Variable eines speziellen Typs anlegen (im Beispiel „user_defined_key“) und diese mit bestimmen Werten füllen. Danach wird die Funktion in_JoyKeyboard() einen Wert zurückliefern, der in seinen einzelnen Bits anzeigt, was der Benutzer gerade will:

Bit 7: Feuertaste
Bit 6: (nichts)
Bit 5: (nichts)
Bit 4: (nichts)
Bit 3: Rechts
Bit 2: Links
Bit 1: Runter
Bit 0: Rauf

Die Werte, die in die spezielle Variable gefüllt werden muss, liefert uns die Funktion in_LookupKey(). Für Profis: das ist der sogenannte Scancode der Taste...

Code: Alles auswählen

#include <input.h>
#include <stdio.h>

int main(void) {
    struct in_UDK user_defined_key;
    int joystick;

    user_defined_key.fire = in_LookupKey(' ');
    user_defined_key.right = in_LookupKey('8');
    user_defined_key.left = in_LookupKey('5');
    user_defined_key.down = in_LookupKey('6');
    user_defined_key.up = in_LookupKey('7');

    do {
        joystick = in_JoyKeyboard(&user_defined_key);
        printf("%x\n", joystick);
    } while (in_Inkey() != 'Q');

    return 0;
}
Verzögerungen nach Standard

Weiter vorn habt ihr die Verzögerungsfunktionen speziell des z88dk kennengelernt. Es gibt aber auch eine Funktion, die (fast) jedes C-System kennt: sleep() wartet die angegebene Anzahl von Sekunden.

Für ganz tolle Tricks hat das z88dk sogar noch eine Funktion, der ihr die gewünschte Anzahl Prozessortakte als Parameter übergeben könnt. Das Minimum beträgt 160!

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int i;

    printf("SCHLAFE 5 SEKUNDEN... ");
    sleep(5);
    printf("OK.\n");
    printf("WARTE CA. 5000000 TAKTE...");
    for (i = 0; i < 100; i++) {
        delay(50000); /* mindestens 160 */
    }
    printf("OK.\n");

    return 0;
}
Zeiten voll kompatibel nehmen

Seit „Urzeiten“ gibt es eine Standardfunktion, die jedes C-System kennt. Sie heißt clock() und liefert die Anzahl Takte seit dem Systemstart. Jetzt fragt ihr euch, wieso das voll kompatibel sein soll, denn jedes System wird ja einen anderen Takt haben. Das ist auch richtig überlegt, aber dafür definiert die Headerdatei „time.h“ eine Konstante CLOCKS_PER_SEC, die eben diese Anzahl Takte pro Sekunde wiedergibt. Das kleine Testprogramm zeigt die Anwendung:

Code: Alles auswählen

#include <stdio.h>
#include <time.h>

int main(void) {
    int sekunde;

    printf("%d\n", CLOCKS_PER_SEC);
    for (sekunde = 0; sekunde < 20; sekunde++) {
        unsigned short start;
        unsigned short jetzt;

        start = clock();
        do {
            jetzt = clock();
        } while (jetzt - start < CLOCKS_PER_SEC);
        printf("%d ABGELAUFEN.\n", sekunde + 1);
    }

    return 0;
}
Beim z88dk müsst ihr noch eines beachten, sonst sucht ihr euch dumm und dämlich nach einem Fehler, den ihr gar nicht gemacht habt. Die Funktion clock() liefert nämlich den Inhalt der Systemvariablen FRAMES, der nur 16 Bit breit ist, als 32-Bit-Wert. Durch passende Programmierung muss dieser Wert also auf 16 Bit reduziert werden, das Beispielprogramm macht das mit einer lokalen Variablen entsprechender Breite.

Ein zusammenfassendes Beispiel

Eigentlich sollte hier das schöne Uhrenprogramm aus dem BASIC-Handbuch einmal in C dargestellt werden. Leider funktionieren die trigonometrischen Funktionen ja nicht wegen der mangelnden Unterstützung von Fließpunktzahlen, abgesehen von der erhöhten Komplexität bei Grafik. Daher seht ihr hier ein Programm zur Ermittlung der Reaktionszeit des Anwenders:

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    int countdown;
    clock_t zeit;
    unsigned int seconds;
    unsigned int milli;

    puts("REAKTIONSZEIT-MESSER");
    printf("WENN BEREIT, TASTE DRUECKEN:");
    (void)fgetc_cons();

    printf("\nACHTUNG");
    for (countdown = rand() % 10; countdown > 0; countdown--) {
        sleep(1);
        printf(".");
    }
    printf(" LOS");

    zeit = clock();
    (void)fgetc_cons();
    zeit = clock() - zeit;

    seconds = (unsigned int)zeit / CLOCKS_PER_SEC;
    milli = (1000UL * (zeit % CLOCKS_PER_SEC)) / CLOCKS_PER_SEC;
    printf("\nZEIT = %u.%03u SEKUNDEN\n", seconds, milli);

    return 0;
}
Dabei wird noch eine weitere neue Funktion verwendet: fgetc_cons(). Diese wartet auf einen Tastendruck und liefert das zugehörige Zeichen. Das Name ist abgeleitet von einer Standardfunktion fgetc() (file get character) und erweitert um die Abkürzung „cons“, wahrscheinlich für „Console“, also Bildschirm/Tastatur. Der Fantasie der z88dk-Macher sind keine Grenzen gesetzt...

Mit dem Cast (siehe Teil 6) auf den Datentyp void machen wir klar, dass uns der zurückgegebene Wert nicht interessiert. Dies ist ein Beispiel für selbsterklärenden Quelltext, denn der erzeugte Maschinencode unterscheidet sich nicht von dem, der bei nicht so ausführlichem Quelltext erzeugt würde. Also, schreibt lieber ausführlich, ihr werdet euch später darüber freuen!

Hinweise für weitere schicke Funktionen

Im Wiki des z88dk finden sich noch viele weitere Funktionen, die zur Programmierung von Spielen nützlich sind. Besonders interessant erscheinen die Sprites; das sind kleine Grafikobjekte, die (einmal definiert) ein- und ausgeschaltet werden und auf dem Bildschirm bewegt werden können.

Also, wer in die Spieleprogrammierung eintauchen möchte, sollte die Seiten des Wiki aufrufen und auch die Beispielprogramme dort studieren. Hier tut sich ein Feld für herrliche, wochenlange Experimente auf! Viel Spaß dabei!
B0D0: Real programmers do it in hex.

Antworten