Aufgaben zum Praktikum Programmieren PRP1 Prof. Dr. Thomas Klinker Department Informations- und Elektrotechnik, HAW Hamburg 16. September 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: Schreiben Sie ein Programm, das eine Ziehung der Lotto-Zahlen simuliert. Wie allgemein bekannt, befinden sich beim Ziehen der Lotto-Zahlen in einer Schale 49 Kugeln. Die Kugeln sind durchnummeriert von 1 bis 49. Nacheinander wird nun zufällig eine Kugel herausgenommen und die Zahl auf der Kugel notiert. Wichtig: Die jeweils herausgenommene Kugel wird nicht zurück gelegt! Insgesamt werden 6 Kugeln gezogen. Das Ziehen einer Kugel soll mit Hilfe des Zufallsgenerators in C, also durch die Funktion int rand(void) realisiert werden. rand( ) gibt einen int-Wert zurück. Dabei gilt für den Rückgabewert, dass er in dem Bereich 0 ≤ rand() ≤ 32767 liegt. Mit der Anweisung zahl = rand(); wird der Variable zahl also eine Zufallszahl zwischen 0 und 32767 zugewiesen. Sie müssen für die Verwendung der Funktion int rand(void) die Header-Datei stdlib.h einbinden. 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 den folgenden Befehlen #include <stdlib.h> #include <time.h> ... time_t t; srand((unsigned) time(&t)); // Initialisiert den Zufallsgenerator // am Anfang des Prgramms (!) ... zahl = (rand() % 49) + 1; ... // Erzeugt Zufallszahl zwischen 1 und 49 könnte man somit beispielsweise Zufallszahlen zwischen 1 und 49 erzeugen. Achten Sie insbesondere darauf, dass jede Zahl nur einmal gezogen werden kann! Das Programm soll eine wiederholte Ziehung der Lotto-Zahlen erlauben, solange es der Benutzer wünscht. Dabei sollte sich der Ablauf des Programms auf der Konsole genauso abspielen, wie in nachfolgendem Beispiel dargestellt. Beispielhafter Ablauf des Programms auf der Konsole: Die Lottozahlen lauten: 1 6 25 42 37 4 Soll dass Programm noch einmal ausgefuehrt werden (j/n): j Die Lottozahlen lauten: 14 2 17 37 27 34 Soll dass Programm noch einmal ausgefuehrt werden (j/n): j Die Lottozahlen lauten: 32 49 36 22 21 2 Soll dass Programm noch einmal ausgefuehrt werden (j/n): n Press any key to continue Programmieren in C - Teil I, Praktikumsaufgaben, Prof. Dr. Thomas Klinker 5 Aufgabe 3b: 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 können zunächst in einer ersten Version so realisiert, daß er zufällig eins, zwei oder drei Hölzchen nimmt. (Hierbei verwende man den Zufallsgenerator wie in Aufgabe 3a). 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. 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. Er spielt dann also mit maximaler Spielstärke. Achten Sie darauf, daß auch der Spieler immer nur ein, zwei oder drei Hölzchen nehmen darf, nicht mehr und nicht weniger. Zu Beginn des Programms soll man die Anfangszahl der Hölzchen, mit der gespielt werden soll, eingeben können. Als nächstes soll man auswählen können, wer anfängt zu ziehen, der Computer oder der Spieler. Die Darstellung auf der Konsole könnte dann z.B. wie folgt aussehen: Die aktuelle Anzahl der Hoelzchen ist: 13 * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | Wieviele Hoelzchen nehmen Sie? (1, 2, 3): Programmieren in C - Teil I, Praktikumsaufgaben, Prof. Dr. Thomas Klinker 6 Aufgabe 4a: 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) Aufgabe 4b: Ein magisches Quadrat der Ordnung n ist eine quadratische Anordnung der Zahlen 1, 2, . . . , n2 , so daß alle n Zeilen– und Spalten– sowie die Hauptdiagonalsummen gleich sind. Schreiben Sie ein C–Programm, das nach Eingabe einer ungeraden natürlichen Zahl n ein magisches Quadrat der Ordnung n berechnet und auf dem Bildschirm ausdruckt. Verwenden Sie dazu folgende Methode: Die Zahlen 1, 2, . . . , n2 werden fortlaufend in das Quadrat eingetragen. Beginnen Sie damit, daß Sie die 1 in die Mitte der ersten Zeile eintragen. Gehen Sie dann jeweils eine Spalte nach links und eine Zeile nach oben. Verwenden Sie dabei periodische Randbedingungen, das heißt, wenn Sie über die oberste Zeile hinauskommen, fahren Sie mit der untersten fort, und wenn Sie über den linken Rand hinauskommen fahren Sie rechts fort. Trifft man auf ein schon besetztes Feld, so gehe man statt dessen ein Feld nach unten und fahre dort fort. Dieses Verfahren ist in dem nachfolgenden Bild dargestellt am Beispiel eines magischen Quadrates der Ordnung 5. Programmieren in C - Teil I, Praktikumsaufgaben, 15 16 22 3 9 8 14 20 21 2 1 7 13 19 25 24 5 6 12 18 Prof. Dr. Thomas Klinker 7 17 23 4 10 11 Das Programm soll magische Quadrate bis zur Ordnung n = 19 berechnen und auf der Konsole können. Prüfen Sie anhand des obigen Beispiels für n = 5, ob Ihr Programm korrekt arbeitet. Programmieren in C - Teil I, Praktikumsaufgaben, 8 Prof. Dr. Thomas Klinker Aufgabe 5: 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ärungen hierzu und Vorschläge zur Realisierung der periodischen Randbedingungen werden noch in der Vorlesung gegeben). 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 9 */ 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, so daß man das game of life auf einem größeren Spielfeld ablaufen lassen kann (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 10 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.