Aufgaben zum Praktikum Programmieren PRP1 Prof. Dr. Thomas Klinker Department Informations- und Elektrotechnik, HAW Hamburg 11. März 2013 1 Programmieren in C - Teil I, Praktikumsaufgaben, Prof. Dr. Thomas Klinker 1 Aufgabe 1a: Geben Sie das Programm auf der nächsten Seite ein, bringen Sie es zum laufen und testen Sie es. Aufgabe 1b: Schreiben Sie ein Programm (ganz ähnlich dem in Aufgabe 1a), welches die Höhe h und den Radius r eines Zylinders einliest und dann die gesamte Oberfläche und das Volumen des Zylinders berechnet. Aufgabe 1c: Schreiben Sie ein Programm, welches für eine positive ganze Zahl vom Typ long die Quersumme berechnet. Die Quersumme von 1339 bespielsweise ist 16. Programmieren in C - Teil I, Praktikumsaufgaben, Prof. Dr. Thomas Klinker /******************************************************************** Autor: Datum: Dateiname: klk 03.03.03 aufg01a1.cpp Kurzbeschreibung: Kugelradius einlesen, Oberfläche und Volumen berechnen, auf ein Zeichen warten. Änderungen: Name Datum Kurzbeschreibung -----------------------------------------------------------------klk 04.03.03 ... Beseitigung des Fehlers .... *********************************************************************/ #include <stdio.h> #include <math.h> /* für pow(x, y) */ int main(void) { const float pi = 3.1415927; float radius, oberflaeche, volumen; printf("Kugelradius in Metern: "); scanf("%f", &radius); oberflaeche = 4 * pi * radius * radius; printf("Oberfläche in Quadratmetern: %f\n", oberflaeche); volumen = (4.0/3) * pi * pow(radius, 3); printf("Volumen in Kubikmetern: %f\n", volumen); return 0; } 2 Programmieren in C - Teil I, Praktikumsaufgaben, Prof. Dr. Thomas Klinker 3 Aufgabe 2a: Schreiben Sie ein Programm, das eine Folge von reellen Zahlen vom Typ float einliest. Das Ende der Zahlenfolge wird erkannt durch das erste Zeichen, welches keine Zahl (also z.B. ein Buchstabe) ist. Das Programm soll dann für die eingelesenen Zahlen folgende Größen berechnen: 1. Die Anzahl der eingelesenen Zahlen, 2. die Summe der eingelesenen Zahlen, 3. das Maximum der eingelesenen Zahlen, 4. das Minimum der eingelesenen Zahlen, 5. den Mittelwert der eingelesenen Zahlen, 6. die Standardabweichung der eingelesenen Zahlen. Die eingelesenen Zahlen seien mit xi (i = 1, . . . , n) bezeichnet. Der Mittelwert ist dann definiert durch: n P xi i=1 x̄ = . (1) n Für die Standardabweichung gilt: v σ= uP u n u (xi − x̄)2 t i=1 n−1 (2) . Sie werden feststellen, daß bei der Berechnung der Standardabweichung mit der Formel (2) das Problem auftritt, daß man sich alle eingelesenen Zahlen xi “merken“ (d.h. abspeichern) muß. Man kann dieses Problem vermeiden, indem man die Standardabweichung mit folgender Formel berechnet σ= v u P u n 2 x − n · x̄2 u t i=1 i n−1 , (3) die darüber hinaus für die Bearbeitung von Zahlenfolgen beliebiger Länge geeignet ist. Aufgabe 2b: Schreiben Sie ein Programm, das die kartesischen Koordinaten x und y eines Punktes in der Ebene einliest und sie in Polarkoordinaten r und ϕ (00 < ϕ ≤ 3600 ) umrechnet. Benutzen Sie für die Berechnung des Winkels ϕ die atan2(y, x)–Funktion von C. Testen Sie Ihr Programm auch für x = 0. Umgekehrt soll das Programm auch die Eingabe von Polarkoordinaten gestatten und diese in kartesische Koordinaten umrechnen gemäß den Formeln x = r cos ϕ , y = r sin ϕ . Der Winkel ϕ soll dabei jeweils im Gradmaß (00 < ϕ ≤ 3600 ) ausgegeben (bzw. eingelesen) werden. Programmieren in C - Teil I, Praktikumsaufgaben, Prof. Dr. Thomas Klinker 4 Aufgabe 3a: In dieser Aufgabe soll ein bekanntes Streichholzspiel realisiert werden. Von einer Anfangsmenge von Streichhölzern nehmen zwei Spieler abwechselnd eins, zwei oder drei Hölzchen weg. Wer das letzte Hölzchen nehmen muß, hat verloren. Das Programm soll so gestaltet sein, daß ein Spieler gegen den Computer spielt. Der Spieler kann dabei bei jedem seiner Zügen nach Gutdünken wahlweise eins, zwei oder drei Hölzchen nehmen. Die Züge des Computers seien zunächst in einer ersten Version so realisiert, daß er zufällig eins, zwei oder drei Hölzchen nimmt. Lediglich wenn nur noch vier Hölzchen oder weniger übrig geblieben sind, spielt der Computer natürlich so, daß er gewinnt. Bei vier Hölzchen beispielsweise nimmt er drei, damit der Spieler auf dem letzten Hölzchen sitzen bleibt. Zufallszahlen kann man in C mit der Funktion int rand(void) generieren. Mit der Anweisung zahl = rand(); wird der Variable zahl eine Zufallszahl zwischen 0 und 32767 zugewiesen. Damit bei jedem Programmdurchlauf immer eine neue Folge von Zufallszahlen berechnet wird, muss der Zufallsgenerator einmal (!) am Anfang des Programms initialisiert werden. Dies geschieht mit der Funktion srand(...) . Mit #include <stdlib.h> #include <time.h> ... time_t t; srand((unsigned) time(&t)); // Initialisiert den Zufallsgenerator // am Anfang des Prgramms (!) ... zahl = (rand() % 3) + 1; ... // Erzeugt Zufallszahl zwischen 1 und 3 könnte man somit beispielsweise Zufallszahlen zwischen 1 und 3 erzeugen. Achten Sie darauf, daß auch der Spieler immer nur eins, zwei oder drei Hölzchen nehmen darf, nicht mehr und nicht weniger. Wenn Ihr Programm läuft, denken Sie nun einmal darüber nach, wie eine optimale Strategie für den Computer aussieht. Der Computer soll also in einer verbesserten Version Ihres Programms von Beginn an stehts den optimalen Zug ausführen. Er soll also immer so ziehen, daß er, wenn er die Chance zu gewinnen hat, diese auch nutzt. Die Darstellung auf der Console könnte dann z.B. wie folgt aussehen: Die aktuelle Anzahl der Hoelzchen ist: 13 * I I I * I I I * I I I * I I I * I I I * I I I * I I I * I I I * I I I * I I I * I I I * I I I * I I I Wieviele Hoelzchen nehmen Sie? (1, 2, 3): Programmieren in C - Teil I, Praktikumsaufgaben, Prof. Dr. Thomas Klinker 5 Aufgabe 3b: Schreiben Sie ein Programm, das das Pascalsche Dreieck berechnet. Im Pascalschen Dreieck sind die Binominalkoeffizienten ! n! n (4) = m (n − m)! m! pyramidenförmig angeordnet. 1 1 1 2 1 1 1 1 1 3 4 5 6 1 3 6 10 15 1 4 1 10 20 5 1 15 6 1 Statt mit Gl. (4) lassen sich die Binominalkoeffizienten für alle n ≥ 0 und m = 0, . . . , n schneller nach folgenden Formeln berechnen: n 0 ! n m ! = n n ! = 1 = n−1 m−1 ! + n−1 m ! m = 1, . . . , n − 1 . , Benutzen Sie diese Formeln und verwenden Sie zur Speicherung der Binominalkoeffizienten ein zweidimensionales Feld b[n][m], so daß gilt b[n][m] = n m ! . (5) Programmieren in C - Teil I, Praktikumsaufgaben, 6 Prof. Dr. Thomas Klinker Aufgabe 4: Im Jahr 1968 wurde von J. H. Conway an der Universität Cambridge das “game of life“ erfunden und 1970 von M. Gardner im Scientific American einem breiten Publikum vorgestellt. Dabei handelt es sich um einen Algorithmus, der das Wachstum von fiktiven Lebewesen (Bakterien) simuliert. Infolge der interessanten Muster, die dabei entstehen, ist das game of life weit über Biologenkreise hinaus bekannt geworden. Es ist ein Beispiel für einen sogenannten zellulären Automaten. Schauplatz des game of life ist ein zweidimensionales Gitter aus Zellen, die entweder tot (’ ’) oder lebendig (’X’) sind. Wie sich eine Zelle weiter entwickelt, hängt von ihren acht Nachbarn ab, und zwar gelten folgende Regeln: 1. Eine lebende Zelle überlebt in der nächsten Generation, wenn sie zwei oder drei Nachbarn hat. Sind es weniger bzw. mehr, so stirbt sie an Vereinsamung bzw. Überbevölkerung. 2. Eine tote Zelle wird immer dann in der nächsten Generation zum Leben erweckt, wenn sie genau drei lebendige Nachbarn hat, ansonsten bleibt sie tot. Die Zeit verstreicht dabei in diskreten Schritten, d.h. jede Zelle verharrt in ihrem zuvor eingenommenen Zustand, bis gewissermaßen bei einem Gongschlag alle gleichzeitig in den neuen Zustand übergehen. Anders ausgedrückt: Es wird für jede Zelle nachgeschaut, wie ihr Zustand und der ihrer Nachbarn zu einer bestimmten Zeit n ist und berechnet, wie ihr Zustand zur nächsten Zeit n + 1 sein wird. Hat man dies für alle Zellen getan, so werden alle gleichzeitig auf den neuen Zustand gesetzt. In der abschliessenden Version sollte Ihr Programm periodische Randbedingungen für das zweidimensionale Feld benutzen (genauere Erklärung hierzu und Vorschläge zur Realisierung in der Vorlesung). Gestalten sie das Programm so, daß man als Ausgangsmuster entweder eins der folgenden fünf auswählen kann, oder über die Angabe der Koordinaten (Zeile, Spalte) ein eigenes Ausgangsmuster lebendiger Zellen eingeben kann. XXX Blinker XX XX Block X X X X X X Bienenstock XX X XX X X X Leuchtfeuer Gleiter X XX Jede neue Generation soll auf dem Bildschirm ausgegeben werden, wobei die vorhergehende Generation immer durch die aktuelle überschreiben werden soll. Um dieses einfach bewerkstelligen zu können, werden Ihnen zwei Dateien zur Verfügung gestellt, console.h und console.cpp , die Sie Ihrem Projekt hinzufügen müssen. Damit verfügen Sie über folgende Funktionen: bool setCursor(int zeile, int spalte) /* Funktion, die Cursor auf die angegebene Position (zeile, spalte) setzt. bool cls() /* Funktion, die Consolen-Puffer mit aktuellem Attribut löscht /* und Cursor auf die Home-Position (0,0) setzt (clear screen). */ */ */ Programmieren in C - Teil I, Praktikumsaufgaben, void cursorOff() /* Funktion, die den Cursor unsichtbar macht. Prof. Dr. Thomas Klinker 7 */ void cursorOn() /* Funktion, die den Cursor wieder sichtbar macht. */ DWORD cursorSize(DWORD size) /* Funktion, die die CursorGröße setzen kann (in Prozent). */ Wählen sie für das game of life ein Feld der Größe: 22 Zeilen und 78 Spalten. Über dem Feld sollte die Nummer der aktuellen Generation angezeigt werden. Auf diese Weise wird die Größe des Bildschirms gut ausgenutzt. Die Größe des Konsolen-Fensters kann allerdings auch geändert werden. (Weitere Hinweise in der Vorlesung). Das fortlaufende Anzeigen immer neuer Generationen kann wahweise durch das Drücken einer Taste erfolgen oder automatisch durch die folgende Schleifenkonstruktion while(!_kbhit()) { statement; ... } // Weitermachen, bis irgendeine Taste gedrueckt wird Hierbei werden die neuen Generationen automatisch angezeigt, bis irgendeine Taste gedrückt wird. Damit die Generationen nicht zu schell nacheinander angezeigt werden, verwenden Sie bei obiger Schleifenkonstruktion die Funktion Sleep(n) (deklariert im Headerfile <windows.h>), die eine Verzögerung des Programmablaufs um n Millesekunden bewirkt. Wer noch Lust hat, mit dem Programm etwas zu spielen, kann folgende Anfangskonfiguration eingeben, die als Gleiterkanone bezeichnet wird. 6 7 8 9 0 1 X 2 X X 3 X XX X 4 XX X XX XXXX X 5 XX X XX XXXX X 6 X X X X XX 7 X XXXX XX 8 XXXX 9 X 0 123456789012345678901234567890123456789012345678901234567890123456789 Die Gleiterkanone wurde 1970 entdeckt von R. W. Gosper, der damals Student am Massachusetts Institut of Technologie (MIT) war, und sich unbedingt den 50-Dollar-Preis verdienen wollte, den Programmieren in C - Teil I, Praktikumsaufgaben, Prof. Dr. Thomas Klinker 8 Conway ausgelobt hatte. Den Preis sollte der erhalten, der als erster beweisen würde, daß eine Ausgangsstellung unbegrenzt wachsen kann. Gospers Gleiterkanone spuckt jeweils nach 30 Zeitschritten einen neuen Gleiter aus, wobei sie selbst in den Ausgangszustand zurückkehrt. Auf der Basis der Gleiterkanone lassen sich logische Gatter, also NOT-, AND- und OR-Gatter und damit auch Rechner konzipieren.