2. Praxis der OOP mit C#



Ähnliche Dokumente
C# 2000 Expression Beispielcodes für Konsolen- und Formularanwendung

Vererbung. Generalisierung und Spezialisierung Vererbung und Polymorphismus

Programmierung Nachklausurtutorium

Probeklausur: Programmierung WS04/05

Begriffe 1 (Wiederholung)

Vererbung und Polymorphie

ÜBUNGEN ZUR OBJEKTORIENTIERTEN MODELLIERUNG

Java 8. Elmar Fuchs Grundlagen Programmierung. 1. Ausgabe, Oktober 2014 JAV8

Objektorientierte Programmierung. Kapitel 22: Aufzählungstypen (Enumeration Types)

Objektorientierte Programmierung

Objektorientierung. Marc Satkowski 20. November C# Kurs

Neben der Verwendung von Klassen ist Vererbung ein wichtiges Merkmal objektorientierter

Programmieren in Java

7. Übung Informatik II - Objektorientierte Programmierung

Theorie zu Übung 8 Implementierung in Java

Vererbung I. Kfz Eigenschaften und Methoden der Klasse Kfz Lkw. Pkw. Eigenschaften und Methoden der Klasse Kfz

Programmieren in Java

- EINSTIEG IN JAVA - (1/5) Datum:

Programmieren II Abstrakte Klassen / Virtuelle Methoden. Programmieren II Abstrakte Klassen / Virtuelle Methoden

Mapra: C++ Teil 6. Felix Gruber, Sven Groß. IGPM, RWTH Aachen. 13. Juni 2017

Klausur Grundlagen der Programmierung

Am Anfang werden die Attribute deklariert public class Kreis {

In dieser Aufgabe geht es darum, das einfache Meteor-Spiel zu verstehen und anzupassen. Lade als erstes das Spiel sample12 und spiele es.

Es ist für die Lösung der Programmieraufgabe nicht nötig, den mathematischen Hintergrund zu verstehen, es kann aber beim Verständnis helfen.

Objektorientierte Programmierung Studiengang Medieninformatik

Javakurs FSS Lehrstuhl Stuckenschmidt. Tag 3 - Objektorientierung

Fakultät IV Elektrotechnik/Informatik

2. Vererbung und Kapselung

hue13 January 30, 2017

6. Globalübung (zu Übungsblatt 8)

Sichtbarkeiten, Klassenmember und -methoden

Tutorial zu Einführung in die Informatik für LogWi- Ings und WiMas Wintersemester 2015/16. 1 Zauberer und Zwerge, Aufgabenteil 1

Objektorientiertes Programmieren mit C++ für Fortgeschrittene

Objektorientierte Programmierung

Praktikum 4: Grafiken und Ereignisse

Städtisches Gymnasium Olpe Java Ht Informatik - Q1 Die Klasse List im Abitur Methoden und Beispielcode Hier alle wichtigen Methoden. Ein Beispielcode

Musterlösung Stand: 5. Februar 2009

9. Vererbung und Polymorphie. Informatik Vererbung und Polymorphie 1

Javakurs für Anfänger

Objektorientierte Programmierung Studiengang Medieninformatik

Klausur Programmieren 2 SS 2016

IT I: Heute. Nachbetrachtung Wissensüberprüfungen. Einführung Vererbung. Roboter in becker.robots. falls Zeit: Scheduling 8.11.

Probeklausur Informatik 2 Sommersemester 2013

Objektorientierte Programmierung

Handbuch für die Erweiterbarkeit

IT I: Heute. Nachbetrachtung Wissensüberprüfung. Einführung Vererbung. Roboter in becker.robots. Filialenbelieferung 4.11.

Implementieren von Klassen

Objektorientierung. Klassen und Objekte. Dr. Beatrice Amrhein

Kapitel 13. Abstrakte Methoden und Interfaces. Fachgebiet Knowledge Engineering Prof. Dr. Johannes Fürnkranz

Delegatesund Ereignisse

Probeklausur Java Einführung in die Informatik. Wintersemester 2016/2017

Probeklausur Java Einführung in die Informatik. Wintersemester 2017/2018

Probeklausur Java Einführung in die Informatik. Wintersemester 2014/2015

Umsetzung einer Klassenkarte in einer Programmiersprache

2 Teil 2: Nassi-Schneiderman

Staubsauger-Roboter. Als Vorlage dienen dir drei Klassen: RECHTECK Damit kannst du die Wände des Raums darstellen.

Graphic Coding. Klausur. 9. Februar Kurs A

Der Ball kann angezeigt werden: anzeigen( ) {... } Der Ball kann z.b. seine Größe verändern: groesseaendern(int veraenderung) {... } usw.

Javakurs für Anfänger

7. Arrays. Beim Deklarieren und Initialisieren der Liste bräuchte man oft zueinander sehr ähnlichen Code:

Klassen und Objekte. Klassen sind Vorlagen für Objekte. Objekte haben. Attribute. Konstruktoren. Methoden. Merkblatt

Programmieren in Java -Eingangstest-

Einstieg in die Informatik mit Java

Probeklausur Java Einführung in die Informatik. Wintersemester 2014/2015. Musterlösung

01. Grundprinzipien der Vererbung

Algorithmen und Datenstrukturen 07

Klassen können bekanntlich aus zwei Komponententypen bestehen, nämlich Attributen und Methoden.

Ereignisse (Events) Asynchrones Versenden von Informationen Sender (Herausgeber) Empfänger (Abonnent) Dr. Beatrice Amrhein

Greenfoot: Verzweigungen

Programmiertechnik Klassenvariablen & Instantiierung

Arrays. Theorieteil. Inhaltsverzeichnis. Begriffe. Programmieren mit Java Modul 3. 1 Modulübersicht 3

Die Welt in unseren Programmen false -1.4E-12. false. Klassen

Greenfoot: Verzweigungen Nicolas Ruh und Dieter Koch

Das diesem Dokument zugrundeliegende Vorhaben wurde mit Mitteln des Bundesministeriums für Bildung und Forschung unter dem Förderkennzeichen

pue13 January 28, 2017

Objektorientiertes Programmieren (Java)

Polymorphie und UML Klassendiagramme

Überblick. Überblick. Abstrakte Klassen - rein virtuelle Funktionen Beispiele

3. Bedingte Anweisungen

3. Das erste eigene Programm mit Greenfoot: Litte Crab

Einführung in die Programmierung I. 11. Vererbung. Stefan Zimmer

Java Vererbung. Inhalt

Programmierkurs Java

Informatik 10 Objektorientiertes Modellieren und Programmieren mit Java

1 Grundlagen der Objektorientierung

12 Abstrakte Klassen, finale Klassen und Interfaces

Javakurs für Anfänger

Kapitel 9. Programmierkurs. Attribute von Klassen, Methoden und Variablen. 9.1 Attribute von Klassen, Methoden und Variablen

Ein erstes "Hello world!" Programm

9. Zurück zur OOP: Vererbung und Polymorphismus

OOP und Angewandte Mathematik. Eine Einführung in die Anwendung objektorientierter Konzepte in der angewandten Mathematik

Vererbung P rogram m ieren 2 F örster/r iedham m er K apitel 11: V ererbung 1

Propädeutikum Programmierung in der Bioinformatik

Objektorientierte Programmierung (OOP)

Transkript:

2. Praxis der OOP mit C# A) Einleitung In diesem Kapitel 5.2 werden Sie Schritt für Schritt ein eigenes Spiel programmieren und dabei die wichtigsten Gedanken der OOP kennenlernen. Es wird vorausgesetzt, dass Sie Kapitel 2 "Einfache Programme mit C#" durchgearbeitet haben, oder dass Sie mit Schleifen, Verzweigungen und sonstigen grundlegenden Dingen in C# vertraut sind. Eine Vertiefung und Erweiterung Ihres Wissens erreichen Sie, wenn Sie nach diesem Kapitel noch Kapitel 5.3 bearbeiten. Dort wird an einem fertigen Spiel (AntMe!) aufgezeigt, wie selbst in komplexeren Programmen die OOP für Überschaubarkeit und Erweiterbarkeit sorgt. Die Idee zum Spiel in diesem Kapitel: Sie steuern jeweils mit zwei Fingern einer Hand die beiden Ameisen, die Sie von links in das Ziel rechts führen müssen. Käfer von oben und unten machen Ihnen das Leben schwer, denn wenn Ihre Ameisen zu nahe an die Käfer kommen, werden sie gefressen. Am Ende erhalten Sie eine Punktzahl, die von der Geschwindigkeit, mit der Sie das Problem gelöst haben und von der Anzahl der ins Ziel gebrachten Ameisen abhängt. Damit der Eindruck entsteht, dass die Ameisen und Käfer sich tatsächlich bewegen, werden immer abwechselnd zwei verschiedene Bilder der Insekten angezeigt. Obwohl der "rote Faden" in diesem Kapitel das "Ameisen-Käfer-Spiel" ist, werden wir für bestimmte Aufgaben auch andere Problemstellungen behandeln, um eine größere Allgemeinheit zu erreichen. 1

B) Erste Stufe: Klasse, Attribut, Eigenschaft, Methode Vorbereitung Holen Sie sich aus dem Tauschverzeichnis die Hülle des Programms KlassenObjekteInsekten00 : Hier wurde lediglich ein weißes Formular der festen Größe 1000 x 600 erzeugt und die später nötigen Bilder für die Ameisen und Käfer wurden bereits in die Resources.resx eingefügt. (So werden die Bilder dann auch in die ausführbare Datei eingefügt.). Außerdem wurde der Name für den namespace mit AmeisenKäferSpiel festgelegt. Fügen Sie nun noch aus der Toolbox links einen Timer und ein MenuStrip ein. Zum Schluss sorgen Sie für drei Menu-Einträge im MenuStrip: Mit Strg+Shift+S werden alle Änderungen gespeichert. Klasse Ameise Rechtsklick / Linksklick auf AmeisenKäferSpiel im Projektmappen-Explorer. Wie im nebenstehenden Bild dargestellt: Hinzufügen / Neues Element / Class 2

Wie im Theorie-Teil nachzulesen ist, ist eine Klasse (class) eine Schablone nach der dann später die Objekte hergestellt werden. In der ersten Stufe sollen nur zwei Ameisen erstellt werden, die man mit der Tastatur beschleunigend und bremsend auf die rechte Seite führen kann. Daher ist leicht einsehbar, dass eine Klasse für die Ameisen von Vorteil wäre. Klassen werden immer nach dem gleichen Muster erzeugt: class Ameise - Attribute - - Konstruktor - - Eigenschaften / Methoden - Hier ein erster Versuch für die Ameisen-Klasse: class Ameise private int x, y; //Koordinaten private float va; //Geschwindigkeit protected Graphics g; protected Bitmap[] bilder = new Bitmap[2]; ypos) public Ameise(Graphics g, Bitmap bildeins, Bitmap bildzwei, int xpos, int this.g = g; bilder[0] = bildeins; bilder[1] = bildzwei; x = 0; y = ypos; public int Xpos set x = value; get return x; public void Schneller(float dv) va += dv; if (va > 30) va = 30; public void Bremsen(float dv) va -= dv; if (va < 0) va = 0; Zunächst zum gelben und zum grauen Abschnitt: Die Klasse Ameise benötigt x- und y- Koordinaten, die aktuelle Geschwindigkeit va der Ameise und eine Oberfläche, auf die gezeichnet werden kann: Graphics g 3

Die zwei Bilder der Ameise, die abwechselnd gezeigt werden sollen, werden in einer "Bitmap-Liste", bestehend aus zwei Bitmaps, gespeichert. Sicher fallen einem später noch andere praktische Attribute ein. Sie werden dann einfach eingefügt. Es fällt auf, dass den Attributen verschiedene "Zugriffsmodifizierer" zugeordnet wurden, wie private und protected. Verwendet man private, so bedeutet dies, dass nur die Klasse selbst Zugriff auf das Attribut hat. (Bei protected kann auch eine eventuell abgeleitete oder übergeordnete Klasse zugreifen. Mit dem Thema werden wir uns aber erst im nächsten Schritt beschäftigen.) Weshalb soll nur die Klasse selbst auf ihre Attribute zugreifen können? Es wäre ja sehr bequem, wenn man zum Beispiel die Koordinaten x und y public deklarieren würde, denn dann könnte von überall im Projekt etwa per ameise.x = 5 ; die x-koordinate des Ameisenobjekts verändert werden. Aber genau das würde der Idee der OOP widersprechen: Alle Daten des Objekts sollen geheim gehalten (gekapselt) werden. Von außen sieht man nur ein Objekt ameise, mit dem man lediglich per vorgegebenen Eigenschaften und Methoden kommunizieren kann - sofern diese public deklariert sind. Das hat große Vorteile. Denn auf diese Weise bleiben die Daten des Objekts vor Überraschungen verschont, da man sich schon vorher überlegen muss, wer überhaupt das Recht bekommen soll, in die Struktur des Objekts einzugreifen und wie weit das Recht eventuell gehen soll. public int Xpos set x = value; get return x; Die Methode Xpos wird Eigenschaft genannt. Über sie kann die x-koordinate der Ameise ausgelesen und eingetragen werden. Beispiel: ameise.xpos = 5 ; Damit wird der Wert der x-koordinate auf 5 gesetzt. Der Unterschied zum oben dargestellten direkten Eintrag über eine public-variable, besteht vor allem darin, dass man über die Eigenschaft Xpos bestimmen kann, was von außen erlaubt ist. Man könnte beispielsweise die set-zeile weglassen und würde so erreichen, dass der Wert von außen überhaupt nicht gesetzt werden kann. Es wäre aber auch denkbar, dass man nur x-werte kleiner 1000 einsetzen lässt. Die Methoden Schneller(float dv) und Bremsen(float dv) sind kleine Unterprogramme, die einen Eingabe-Parameter dv erwarten und damit offensichtlich die Geschwindigkeit va verändern. Sie sind public und somit von überall im gleichen Projekt aufrufbar. Der Zusatz void (= Leere) besagt nur, dass die Methode keinen Rückgabewert liefert. Die "Intellisense" hilft sehr wirkungsvoll beim Schreiben des Codes: Wenn man beispielsweise (siehe unten) ameise. schreiben, dann werden nach dem Schreiben des Punktes die möglichen Ergänzungen, wie etwa die Eigenschaft Xpos oder die Methode Bremsen angezeigt: 4

Wenn man sich nicht mehr erinnert, welche Bedingungen die Methode Bremsen gefordert hat, dann zeigt ein Klick darauf alles Wissenswerte: (Man kann den beiden Bildern oben auch entnehmen, wie die Icons für Methoden und Eigenschaften aussehen.) Zum türkisfarbenen Abschnitt: Wenn ein Objekt erzeugt werden soll, benötigt man eine Art Methode, die das übernimmt. Man sie Konstruktor. Würde man keinen Konstruktor für Ameise schreiben, so käme ein (parameterloser) Standard-Konstruktor zum Einsatz. Er muss nicht eigens geschrieben werden, aber seine Funktion ist äußerst dürftig: Er initialisiert die Attribute bei der Erzeugung eines Ameisen-Objekts. Durch eine explizite Formulierung eines Konstruktors, wird der Standard-Konstruktor außer Kraft gesetzt. Sehen Sie selbst, was wir unserem Ameisen-Objekt alles mitgeben wollen: public Ameise(Graphics g, Bitmap bildeins, Bitmap bildzwei, int xpos, int ypos) Eine Zeichen-Oberfläche g, zwei Bilder und zwei Koordinaten. Eine Zeile in den nachfolgenden Zuweisungen muss genauer besehen werden: this.g = g; Hierbei bezeichnet this das neu zu erzeugende Objekt. Wenn der Name des Objekts ameise1 wäre, so wird damit festgelegt, dass die als Parameter übergebene Grafik g der neuen Ameise gehört. Mit ameise1.g. könnte man sie aufrufen. Setzt man an geeigneter Stelle der Klasse einen Haltepunkt (Debugging), dann kann man sich im "Überwachungsfenster" alle oben behandelten Begriffe und die zugehörigen Icons nochmals ansehen: 5

Zurück zu den Methoden und Eigenschaften der Klasse Ameise (grauer Abschnitt): Man sollte den Ameisen noch einige andere Fähigkeiten mitgeben. Wir ergänzen den Abschnitt durch folgende Zeilen: public void Schritt(Ameise ameise) // Schritt einer Ameise if (va == 0) return; x += Convert.ToInt32(va); //Verschieben der x-position aktuellesbild = (aktuellesbild + 1) % 2; // Bilder abgewechselnd zeigen public void AmeisenAktion(Ameise ameise) ameise.schritt(ameise); ameise.zeichnen(); // Ameise an neuer Position zeichnen public void Zeichnen() g.drawimage(bilder[aktuellesbild], x, y); public bool Angekommen set angekommen = value; get return angekommen; Aufgabe 1 Welche Abschnitte beschreiben eine Methode und welche Aufgaben haben diese Methoden? In wie fern unterscheidet sich die Eigenschaft Angekommen prinzipiell von der Eigenschaft Xpos? (Die vollständige Klasse Ameise ist im Anhang zu finden) Erstellen Sie mit UMLed ein Klassendiagramm von Ameise. (Lösung und Kommentare im Anhang!) Der mit UMLed erzeugte Java-Code der Klasse Ameise unterscheidet sich, wie Sie nach Bearbeitung von Aufgabe 1 gesehen haben, fast gar nicht vom C#-Code. Einziger Unterschied: Eigenschaft Xpos in C#: public int Xpos set x = value; get return x; Entsprechend die Setzte und Gib-Methode in Java: public void SetzeX(int px) 6

x = px public int GibX() return x Ein Beispiel: x-koordinate der Ameise auf 20 setzen: C# ameise.xpos = 20; Java ameise.setztex(20) Vorteil der Eigenschaften in C#: Man muss sich nur einen Methoden-Namen merken. Klasse Form1 Mit Rechtsklick auf das Formular und der Wahl "Code anzeigen" kann man den von C# selbst erstellen Teil der Klasse Form1 erkennen. Durch Program.cs wird dieser Code am Anfang gleich aufgerufen: Application.Run(new Form1()); Zunächst steht da herzlich wenig: namespace AmeisenKäferSpiel public partial class Form1 : Form public Form1() InitializeComponent(); Vor den Konstruktor von Form1, den wir im Anschluss überschreiben werden, müssen erst die Attribute für Form1 angebracht werden: List<Ameise> ameisenliste; //Die Ameisen private Graphics g; // Grafikobjekt, auf dem gezeichnet wird Der Konstruktor: public Form1() InitializeComponent(); initialisiereameisen(); Die Methode initialisiereameisen() legen wir durch diese Zeilen fest: private void initialisiereameisen() g = this.creategraphics(); ameisenliste = new List<Ameise>(); ameisenliste.add(new Ameise(g, Resources.ameise1a, Resources.ameise1b, 0, 260)); ameisenliste.add(new Ameise(g, Resources.ameise2a, Resources.ameise2b, 0, 410)); Wir hatten einen Timer timer1 integriert und legen nun fest, was bei jedem Tick- Ereignis (Voreinstellung 100 ms auf 50 ms stellen) passieren soll: 7

private void timer1_tick(object sender, EventArgs e) g.clear(color.white); foreach (Ameise ameise in ameisenliste) if (ameise.xpos <= 1000) //selbsterklärend... ameise.ameisenaktion(ameise); else ameise.angekommen = true; Aufgabe 2 Was bewirkt der folgende Code im Konstruktor? ameisenliste.add(new Ameise(g, Resources.ameise1a, Resources.ameise1b, 0, 260)); Kann die Höhe des Formulars von der Klasse Ameise aus geändert werden? Versuchen Sie nachzuvollziehen, welche Aktionen alle 50 ms durchgeführt werden. Was bewirkt die Zeile: ameise.ameisenaktion(ameise);? Technische Ergänzungen Unter dieser Rubrik wird Ihnen jeweils der Code vorgestellt, der das Programm voran bringt aber nicht notwendigerweise Ihr Verständnis für OOP erweitert. Die beiden Ameisen sollen auf Tastendruck reagieren. Wir programmieren hierzu das KeyDown-Ereignis des Formulars Form1: private void Form1_KeyDown(object sender, KeyEventArgs e) //Eigenschaft KeyPreview des Formulars auf true setzten! switch (e.keycode) case Keys.D: // Linke Hand : obere Ameise steuern ameisenliste[0].schneller(1.5f); // schneller: Taste D, langsamer: Taste A case Keys.A: ameisenliste[0].bremsen(1.5f); case Keys.L: // Rechte Hand : untere Ameise steuern ameisenliste[1].schneller(1.5f); // schneller: Taste L, langsamer: Taste J case Keys.J: ameisenliste[1].bremsen(1.5f); case Keys.R: 8

Application.Restart(); Wenn man die Eigenschaft KeyPreview von Form1 nicht auf true setzt, kann man die Tasten so oft drücken, wie man will, es wird nichts passieren, da die Tasten nicht "den Fokus haben". Vergleichbar dem Problem, wenn man online ein Formular ausfüllen will und es passiert nichts. Das Eingabefenster kurz anklicken und die Sache funktioniert, da das Fenster danach den Fokus hat. Die Menu-Einträge neu starten, beenden und Info müssen auch noch mit den passenden Click- Ereignissen versorgt werden: private void beendentoolstripmenuitem1_click(object sender, EventArgs e) Close(); private void neustartentoolstripmenuitem_click(object sender, EventArgs e) Application.Restart(); private void infotoolstripmenuitem_click(object sender, EventArgs e) MessageBox.Show("Steuerung der Ameisen mit den Tasten A/D bzw. J/L" + "\r\n Neustarten mit der Taste R"); Der gesamte Code für Form1 ist nochmals im Anhang aufgeführt. Aufgabe 3 Erstellen und testen Sie das Programm. Ein Spiel braucht eine Bewertung, um vergleichen zu können, wer das gegebene Problem besser löst. Wir werden als Kriterium die Zeit, die der Spieler benötigt, die Ameisen sicher auf die andere Seite zu bringen, messen. Jede Ameise bekommt also ihre Stoppuhr mit. wie viel Zeit bereits vorüber ist, soll für jede Ameise angezeigt 9

werden: (Anmerkung: Die benötigten Zeiten werden aufaddiert, jeweils mit 10 multipliziert und von der Maximal-Punktzahl 1000 abgezogen. Kommt eine Ameise nicht an, so werden von vornherein 500 Punkte abgezogen (entspricht 50 Sekunden für eine Ameise, die ankommt). Natürlich ist das nur ein erster Vorschlag, den Sie nach Belieben umändern können.) Hier ein Vorschlag, wie die Zeitmessung und Anzeige realisiert werden kann (es werden nur die neue hinzugekommenen Zeilen angegeben): Klasse Ameise: private string zeit = "0"; private string lebenszeit; Font schrift = new Font("Arial", 14); //Ausgabeschrift Der Konstruktor wird nur geringfügig verändert. Wir geben den Ameisen die Lebenszeit als String mit: public Ameise(Graphics g, Bitmap bildeins, Bitmap bildzwei, int xpos, int ypos, string lz) this.g = g; bilder[0] = bildeins; bilder[1] = bildzwei; x = xpos; y = ypos; lebenszeit = lz; Bei den Methoden muss Schritt erweitert und ZeitAusgeben hinzugefügt werden: public void Schritt(Ameise ameise) //Schritt einer Ameise if (va == 0) //Ameise steht 10

ZeitAusgeben(ameise,zeit); return; ZeitAusgeben(ameise,zeit); x += Convert.ToInt32(va); //Verschieben der x-position aktuellesbild = (aktuellesbild + 1) % 2; //Bilder abgewechselnd zeigen private void ZeitAusgeben(Ameise ameise, String zeit) zeit = (DateTime.Now - Form1.form1.StartZeit).TotalSeconds.ToString("0.0"); ameise.lebenszeit = zeit; g.drawstring(string.format(zeit + " s"), schrift, Brushes.Black, new PointF(Form1.form1.Breite - 80, y + 30)); Klasse Form1: public static Form1 form1; //Ohne Objekt keine Methoden... private DateTime startzeit; form1 = this; startzeit = DateTime.Now; public DateTime StartZeit get return startzeit; Stecken in diesem Code technische Details, die mit OOP nichts zu tun haben? Dass die Antwort ein klares Nein ist, lernen Sie bei der Bearbeitung der folgenden Aufgabe: Aufgabe 4 Nachfolgende Code-Ausschnitte stammen von der Ergänzung der Klasse Ameise und der Klasse Form1. Interpretieren Sie deren Bedeutung unter Berücksichtigung objektorientierter Aspekte: Convert.ToInt32(va) DateTime.Now Form1.form1.StartZeit ameise.lebenszeit = zeit; new PointF(Form1.form1.Breite - 80, y + 30) Aufgabe 5 Hier verlassen wir für kurze Zeit unser Projekt und versuchen die ersten Erkenntnisse der OOP auf ein eher rechnerisches Problem anzuwenden: Die Bruchrechnung. Über vier 11

Textfelder werden die Zähler und Nenner der Brüche eingegeben. Über die Click- Ereignisse der drei Buttons wird das jeweilige (gekürzte) Ergebnis der Rechnung ermittelt und ausgeben. Im Tauschverzeichnis finden Sie eine unfertige Version, bei der die Klasse Bruch noch ergänzt werden muss. (Ordner: BruchUnvollständig) Schauen Sie sich den Code von Form1 an und ergänzen Sie den Code von Bruch: Testen Sie das Programm! using System; namespace Bruch class Bruch private int zaehler, nenner; private int hv, zaehler1, nenner1; //Hilfsvariable public Bruch(int z, int n) //Konstruktor public int Zaehler //Eigenschaft public int Nenner //Eigenschaft public void addierenmit(bruch bruch) //Beispiel zaehler = zaehler * bruch.nenner + bruch.zaehler * nenner; nenner = nenner * bruch.nenner; kuerzen(); public void multiplizierenmit(bruch bruch) //Methode public void dividierendurch(bruch bruch) //Methode public void kuerzen() zaehler1 = Math.Abs(Zaehler); nenner1 = Math.Abs(Nenner); if (zaehler1 < nenner1) hv = zaehler1; zaehler1 = nenner1; nenner1 = hv; while (nenner1 > 0) hv = zaehler1 % nenner1; zaehler1 = nenner1; nenner1 = hv; hv = zaehler1; zaehler = zaehler / hv; nenner = nenner / hv; (Lösung im Anhang) 12

C) Zweite Stufe: Vererbung, Polymorphie, abstrake Klasse Bisher lassen sich lediglich zwei Ameisen von links nach rechts bewegen. Dabei ist es möglich, die Ameisen zu beschleunigen bzw. sie abzubremsen und zum Stehen zu bringen. Diese Aktionen haben nur Sinn, wenn den Ameisen unterwegs eine Gefahr lauert. In der nächsten Stufe werden wir das Spiel so erweitern, dass zunächst zwei Käfer mit verschiedenen Geschwindigkeiten von oben nach unten laufen. Kommen ihnen die Ameisen zu nahe, so werden sie gefressen. Klar, dass die Ameisen jetzt nur bei geschickter Steuerung ans Ziel kommen. Wenn die letzte Ameise angekommen oder gefressen ist, soll die erreichte Punktezahl ausgegeben werden. Nun liegt es zunächst nahe, dass man einfach eine neue Klasse Käfer erstellt und die Klasse Ameise beibehält. Viel geschickter wäre es allerdings, wenn man für eventuell weiter hinzukommende Akteure eine Klasse Insekt erzeugt. Denn es gibt mit Sicherheit Attribute, Eigenschaften und Methoden, die allen aktuellen und zukünftigen Insekten gemeinsam sind. Eine Klasse Käfer und eine Klasse Ameise erbt dann von der übergeordneten Klasse Insekt. Die Methode Laufen kommt in beiden Klassen vor, bewirkt aber nicht unbedingt das Gleiche. Man spricht in diesem Fall von Polymorphie. Ein Tier namens Insekt gibt es nicht. Daher wird die Klasse Insekt abstact definiert. Das bedeutet, dass man von dieser Klasse sinnvollerweise kein Objekt instanziieren kann. Methoden und Eigenschaften müssen auch in dieser Klasse nicht ausformuliert werden, denn in den Unterklassen bewirken sie gegebenenfalls ganz verschiedene Dinge. Mit UML sieht der Grobentwurf so aus: 13

Die Klasse Insekt ist schon vollständig, in den Unterklassen sind nur die Konstruktoren festgelegt. (Bedenken Sie, dass mit UMLed keine Eigenschaften festgelegt werden können. So muss es hier ein GibX() und ein SetzeX() für einen Lese- bzw Schreibzugriff auf die Koordinat x zu erreichen. ) Aufgabe 6 Laden Sie mit UMLed die Datei Insekt.urd (Bild oben). Schauen Sie sich die Attribute und die Methoden der Klasse Insekt an und überlegen Sie, weshalb sie hier in der Oberklasse formuliert wurden. Welche abstrakten Methoden finden Sie? Weshalb sind sie abstact? Welche Attribute und Methoden sollten nur in den Unterklassen vorkommen? Klasse Insekt abstract class Insekt protected int x, y; protected float va; protected bool lebt; protected string lebenszeit; protected Graphics g; protected Bitmap[] bilder = new Bitmap[2]; protected int w; protected int h; public Insekt(Graphics g, Bitmap bildeins, Bitmap bildzwei, int xpos, int ypos, string lz) this.g = g; bilder[0] = bildeins; bilder[1] = bildzwei; x = xpos; y = ypos; lebenszeit = lz; lebt = true; public int Xpos set x = value; get return x; public int Ypos set y = value; get return y; public float Geschwindigkeit get return va; 14

public bool Lebt get return lebt; public bool Berührt(Insekt k) if (x > k.xpos + k.w) return false; if (y > k.ypos + k.h) return false; if (x + w < k.xpos) return false; if (y + h < k.ypos) return false; return true; public string Lebenszeit set lebenszeit = value; get return lebenszeit; public virtual void Bremsen(float dv) va -= dv; if (va < 0) va = 0; public virtual void Schneller(float dv) va += dv; public virtual void Laufen(float dv) va = dv; public int W set w = value; get return w; public int H set h = value; get return h; public abstract void Zeichnen(); public abstract void Schritt(Insekt insekt); public abstract void AufAnfangspositionSetzen(int a); Zu den Attributen: Neu sind das boolsche Attribut lebt, und die integer-größen w und h. Letztere stehen für die Breite und Höhe des Insektenbildes. Beachten Sie: Nicht immer will man ein in der Oberklasse definiertes Attribut oder eine dort definierte Methode in allen Unterklassen sofort verwenden. Selbst wenn man im Augenblick keine Verwendung für z. B. w und h bei den Ameisen im Auge 15

hat, so lässt man sich durch die Definition in der Oberklasse die Möglichkeit offen, später eventuell doch noch darauf zurückgreifen zu können. Zum Konstruktor: Auch hier wurde dem Insekt der String lz (Lebenszeit) mitgegeben, obwohl wir in der Unterklasse Käfer zunächst keine Verwendung dafür haben. In einer späteren Entwicklung des Spiels wäre aber eine Einbeziehung der Lebenszeit der Käfer denkbar, indem man die zunächst feste Größe erhöht, wenn eine Ameise verspeist wurde. Zu den Eigenschaften und Methoden: Die letzten drei Methoden Zeichnen(), Schritt(Insekt insekt) und AufAnfangspositionSetzen() sind abstract. Sie werden daher hier nicht ausformuliert und bedeuten in den jeweiligen Klassen Verschiedenes (Polymorphie). Die Methode Berührt(Insekt k) muss genauer betrachtet werden. Inhaltlich ist klar, worum es geht: Berührt ein Insekt a ein anderes Insekt k? Wir werden damit testen, ob eine Ameise einen Käfer berührt und somit gefressen wird: ameise.berührt(käfer) (Aber natürlich kann jedes Insekt diese Methode aufrufen, um das Berühren mit jedem beliebigen Insekt zu testen.) Ein Bild soll klar machen, wie es zum obigen Code kommt: Die roten Punkte an den Rechtecken sind die C#-Koordinaten der Bilder der Insekten. Der Ursprung des Koordinatensystems ist links oben. Y-Werte werden von oben nach unten abgetragen. Grün steht hier für einen Käfer (die sich vertikal bewegen), hellblau steht für die vier Grenzsituationen einer Ameise, die gerade 16

eben den Käfer noch berührt. k.h und k.w sind hier also die Bildhöhe und die Bildweite des Käferbildes (ohne Beine - wird sonst zu schwer!). Aufgabe 7 Zeigen Sie Hilfe der obigen Skizze, dass der Code für Berührt(Insekt insekt) die richtige Antwort liefert: public bool Berührt(Insekt k) if (x > k.xpos + k.w) return false; if (y > k.ypos + k.h) return false; if (x + w < k.xpos) return false; if (y + h < k.ypos) return false; return true; Klasse Käfer class Käfer : Insekt //d.h. Käfer ist ein Insekt private int aktuellesbild; public Käfer(Graphics g, Bitmap bildeins, Bitmap bildzwei, int xpos, int ypos, string lebenszeit) : base(g, bildeins, bildzwei, xpos, ypos, lebenszeit) this.w = 74; //Eigentlich je 20 mehr. this.h = 100;//Kollision mit Ameisen soll nur gelten, wenn Ameisen den Käferkörper (Beine zählen nicht) berühren. public void KäferAktion(Käfer käfer) käfer.schritt(käfer); if (käfer.ypos > Form1.form1.Höhe) //Rand erreicht käfer.aufanfangspositionsetzen(0); käfer.zeichnen(); // Käfer an neuer Position zeichnen public override void Laufen(float dv) //Käfer nicht schneller als 14 if (dv < 15) base.laufen(dv); else base.laufen(14); public override void Schritt(Insekt insekt) y += Convert.ToInt32(va); //Verschieben der y-position aktuellesbild = (aktuellesbild + 1) % 2; //Bilder werden so abgewechselnd gezeigt public override void AufAnfangspositionSetzen(int a) 17

y = a; public override void Zeichnen() g.drawimage(bilder[aktuellesbild], x, y); Zu den Attributen: int aktuellesbild ist das einzige neue Attribut. Weil es private definiert wurde, steht es ausschließlich der Käfer-Klasse zur Verfügung. Aber natürlich kann Käfer auch alle Attribute aus Insekt, wie zum Beispiel int x und int y benutzen. Sie wurden ja dort protected definiert, stehen damit den abgeleiteten Klassen ebenfalls zur Verfügung. Zum Konstruktor: Achten Sie hier besonders auf :base(g, bildeins, bildzwei, xpos, ypos, lebenszeit) Hier wird erwartungsgemäß festgelegt, welche Parameter des Konstruktors von der Oberklasse Insekt geerbt werden. (Beachten Sie: Ein private string Attribut lz, das mit dem Parameter lebenszeit initialisiert werden könnte, wurde nur deshalb nicht formuliert, weil wir im Moment noch keine Verwendung dafür haben.) Zu den Eigenschaften und Methoden: Die Methode Laufen(float dv) wurde in der Klasse Insekt virtual angelegt. Das bedeutet, dass sie in allen abgeleiteten Klasse überschrieben werden kann aber nicht notwendigerweise überschrieben werden muss. Hat man sich fürs Überschreiben entschieden, so muss der Methode der Bezeichner override vorangestellt werden. Will man, wie hier, teilweise die Definition aus der Oberklasse übernehmen, so setzt man base vor den Namen der Methode: base.laufen(dv). Ändert man überhaupt nichts, kann man sich die Mühe sparen und die gesamte Methode einfach weglassen. Ähnliche Festlegungen gelten für die abstrakten Methoden der Oberklasse. Allerdings kann hier in der abgeleiteten Klasse nichts übernommen werden, weil ja auch nichts festgelegt wurde! Schritt(Insekt insekt) zum Beispiel muss daher komplett ausformuliert werden, auch wenn man gar nicht die Absicht hätte, in der abgeleiteten Klasse auf die Methode zugreifen. Aufgabe 8 Schreiben Sie den Code für die Klasse Ameise. Zwei Vorgaben sollen Ihnen dabei helfen: Wie wurde Ameise in Stufe 1 definiert und welche Teile sind nun in die Oberklasse ausgelagert worden. (Lösung im Anhang!) Klasse Form1 Die Attribute: List<Ameise> ameisenliste; //Die Ameisen 18

List<Käfer> käferliste; //Die Käfer private Graphics g; // Grafikobjekt, auf dem gezeichnet wird public static Form1 form1; private DateTime startzeit; private bool spielbeendet = false; private int ym; // Höhe des Formulars Font schrift = new Font("Arial", 24); Die neuen Attribute wurden oben durch größere Fett-Schrift hervorgehoben. Wenn Käfer ins Spiel kommen sollen, benötigt man eine käferliste. Über spielbeendet wird kontrolliert, ob beide Ameisen angekommen oder gefressen wurden. Dann wird mit dem Font Arial das Punkte-Ergebnis mit einer passenden Methode ausgegeben. Die Höhe des Formulars ym dient als Kontrollgröße, ob die Käfer unten angekommen sind. Das Ziel ist, sie dann wieder von oben weiterlaufen zu lassen. Der Konstruktor: public Form1() InitializeComponent(); form1 = this; ym = this.size.height; initialisiereinsekten(); startzeit = DateTime.Now; Auch hier sind die neuen Zeilen durch größere Fett-Schrift hervorgehoben. Weil ja sowohl Ameisen als auch Insekten initialisiert werden sollen, heißt die Methode jetzt entsprechend. Die Eigenschaften und Methoden: private void initialisiereinsekten() g = this.creategraphics(); ameisenliste = new List<Ameise>(); käferliste = new List<Käfer>(); ameisenliste.add(new Ameise(g, Resources.ameise1a, Resources.ameise1b, 0, 260,"0.0")); ameisenliste.add(new Ameise(g, Resources.ameise2a, Resources.ameise2b, 0, 410,"0.0")); käferliste.add(new Käfer(g, Resources.käfera, Resources.käferb, 200, 10, "0.0")); käferliste.add(new Käfer(g, Resources.käfera, Resources.käferb, 600, 10, "0.0")); public int Höhe //Eigenschaft nur lesen get return ym; private bool GefressenVonKäfer(Ameise ameise) bool gefressen = false; foreach (Käfer käfer in käferliste) if (ameise.berührt(käfer)) gefressen = true; return gefressen; private void timer1_tick(object sender, EventArgs e) 19

g.clear(color.white); int anzahlaktiverameisen = 0; foreach (Ameise ameise in ameisenliste) if (GefressenVonKäfer(ameise)) //gezeichnet wird nur, wenn Ameise nicht vom Käfer gefressen wird ameise.stirbt(); ameise.lebenszeit = "50"; //Nur für die Punktzahl: Strafe -500 Punkte... if (ameise.xpos <= 1000 && ameise.lebt) //rechter Rand noch nicht erreicht ameise.ameisenaktion(ameise); anzahlaktiverameisen++; if (anzahlaktiverameisen == 0) spielbeendet = true; if (!spielbeendet)//käfer nur zeichnen, wenn mindest. eine Ameise lebt und noch nicht angekommen ist for (int i = 0; i < 2; i++) käferliste[i].laufen(5 + 3*i);//Zweite Ameise schneller käferliste[i].käferaktion(käferliste[i]); if (spielbeendet) //Ameisen sind entweder tod oder angekommen PunkteAngeben(); private void PunkteAngeben() double lebenszeit1 = Convert.ToDouble(ameisenListe[0].Lebenszeit); double lebenszeit2 = Convert.ToDouble(ameisenListe[1].Lebenszeit); double punkte = 1000-10 * lebenszeit1-10 * lebenszeit2; if (punkte <= 0) punkte = 0; g.drawstring(string.format(convert.tostring(punkte) + " Punkte"), schrift, Brushes.Black, new PointF(400, 300)); Es sind nur die neuen und veränderten Methoden und Eigenschaften aufgeführt! Aufgabe 9 Welche Aktionen werden bei jedem Tick-Ereignis durchgeführt. Welche Zeilen dürfen nun im Konstruktor von Ameise gelöscht werden? Erstellen Sie das gesamte Programm und testen sie es. Welche Verbesserungen schlagen Sie vor? (Ergebnis im Tauschverzeichnis: KlassenObjekteInsekten03 ) 20

D) Dritte Stufe: Überladen, überschreiben, Ergänzungen In der dritten Stufe werden die OOP-Begriffe überladen und überschreiben an einem Beispiel erläutert. Außerdem soll unser Programm einige Veränderungen erfahren: Weitere zwei Käfer starten von unten. Die Geschwindigkeit jedes Käfers (wird innerhalb eines bestimmten Intervalls) durch Zufall bestimmt - sie sind also in der Regel nicht gleich (untereinander und in jedem Spiel). Der Spieler hat die Möglichkeit, das Spiel auf leicht, mittel oder schwer zu spielen. Erreicht wird dies durch größere Bandbreite der Käfergeschwindigkeit. Sicher ist es für einen Anfänger nicht leicht, die beiden ähnlich klingenden Methoden Varianten auseinanderzuhalten. Wie man Methoden überschreibt, haben Sie weiter oben schon gelernt. Entscheidend ist hier, dass die überschriebenen Methoden alle die gleiche Bezeichnung haben. Das gilt auch bei überladenen Methoden. Dennoch sind beide grundverschieden: Überschreiben von Methoden: Man kann Methoden nur innerhalb einer Vererbungshierarchie überschreiben. Die Oberklasse besitzt zum Beispiel eine virtual definierte Methode. In der Unterklasse kann diese dann mit override überschrieben werden. Schauen Sie sich dazu die Methode Schneller in Insekten und in der Unterklasse Ameise an. Die Bezeichnung ist unverändert aber der Methodenkörper hat sich geändert. Überladen von Methoden: In einer Klasse existieren zwei oder mehrere Methoden gleichen Namens, die sich aber in den Datentypen und/oder der Reihenfolge der Parameter unterscheiden. Die C#-Methode Convert.ToDouble() beispielsweise besitzt laut Intellisens 17 Überladungen: K a u Kaum zu glauben aber wahr: Man kann dieser Methode 17 verschiedene Parameter übergeben. Es ist ja dennoch sinnvoll, dass alle diese Methoden gleich bezeichnet werden, denn sie konvertieren den Parameter in eine double-variable. Zum ersten Ziel: Es sollen zwei weitere Käfer von unten nach oben starten. Das bedeutet, dass man praktischerweise den Objekten die Information mitgeben sollte, in welche Richtung sie zu laufen haben. Die Ameisen brauchen die Information aber nicht, denn sie laufen immer von links nach rechts. Ein Konstruktor ist eine spezielle Methode mit der Objekte erzeugt werden können. Daher kann man den Konstruktor auch überladen. Und genau das werden wir nun mit dem Konstruktor Insekt tun. 21

Folgender Code wird in der Klasse Insekt hinzugefügt: //überladender Konstruktor public Insekt(Graphics g, Bitmap bildeins, Bitmap bildzwei, int xpos, int ypos, string lz, Richtung richtung) this.g = g; bilder[0] = bildeins; bilder[1] = bildzwei; x = xpos; y = ypos; lebenszeit = lz; lebt = true; this.richtung = richtung; Damit die gleiche Zugriffs-Bedingungen herrschen, definieren wir eine Aufzählung (enum) namens Richtung noch oberhalb der Insekten-Klasse (im namespace): enum Richtung hoch, runter ; Und bei den Attributen wird ergänzt: protected Richtung richtung; Die Ameisen-Objekte werden nach wie vor durch den Konstruktor Ameise erzeugt, welcher wiederum vom dem Konstruktor Insekt abgeleitet ist. Und zwar von der Variante, der der Parameter richtung fehlt. Klingt kompliziert, ist aber im Grunde ganz einfach: Wir haben für beide Konstruktoren (gleichen Namens: Insekt) Verwendung. Den einen benötigen wir für die Ameisen, den anderen für die Käfer. Wäre unsere Insektenwelt größer, könnte man sich leicht vorstellen, dass noch weitere Überladungen hinzukämen. Damit sich der Aufwand lohnt, müsste dann aber jede Überladung für mehrere Insekten zuständig sein. Die Änderungen in der Klasse Käfer: int ym = Form1.form1.Höhe; //brauchen wir jetzt für die Käferaktion Zwei Methoden müssen hier verändert werden. Zum Einen haben die noch oben laufenden Käfer eine andere Anfangsposition als die nach unten laufenden. Zum Andern muss in der Methode Laufen die Richtung berücksichtig werden public void KäferAktion(Käfer käfer) käfer.schritt(käfer); if (käfer.ypos > Form1.form1.Höhe) //Rand erreicht käfer.aufanfangspositionsetzen(0); if (käfer.ypos < 0) //oberer Rand erreicht käfer.aufanfangspositionsetzen(ym); käfer.zeichnen(); // Käfer an neuer Position zeichnen public override void Laufen(float dv) if (richtung == Richtung.hoch) base.laufen(-dv); else base.laufen(dv); In der Klasse Ameise gibt es keine Änderungen. 22

In der Klasse Form1 werden die verschiedenen Insekten initialisiert. Dadurch ergeben sich folgende Änderungen: Initialisierung der vier Käfer in initialisiereinsekten(): käferliste.add(new Käfer(g, Resources.käfera, Resources.käferb, 200, 10, "0.0", Richtung.runter)); käferliste.add(new Käfer(g, Resources.käfer2a, Resources.käfer2b, 400, 480, "0.0", Richtung.hoch)); käferliste.add(new Käfer(g, Resources.käfera, Resources.käferb, 600, 10, "0.0", Richtung.runter)); käferliste.add(new Käfer(g, Resources.käfer2a, Resources.käfer2b, 800, 480, "0.0", Richtung.hoch)); Im Tick-Ereignis muss der Laufparameter geändert werden, da ja nun statt zwei vier Käfer zu steuern sind (Änderungen rot): if (!spielbeendet) for (int i = 0; i < 4; i++) käferliste[i].laufen(4 + 2*i);//ergibt vier verschieden Geschwindigkeiten käferliste[i].käferaktion(käferliste[i]); Aufgabe 10 Erstellen Sie nun, ausgehend vom letzten Stand des Programms, die neue Variante. Machen Sie sich bei den wenigen Änderungen immer klar, was sie bewirken und weshalb sie nötig sind. Weshalb wurde beim Tick-Ereignis von Form1 aus Laufen(5 + 3*i) Laufen(4 +2*i)? (Eine fertige Lösung finden Sie im Ordner KlassenObjekteInsekten04 im Tauschverzeichnis.) Aufgabe 11 Wir verlassen das Spiel und kehren zu unserem Bruchprogramm zurück. Hier sollen Sie unter einfachen Vorgaben das Erlernte vertiefen. Im Tauschverzeichnis finden Sie unter dem Namen 02GemBruchWindowsUnvollständig eine unvollständige Erweiterung des Bruchprogramms für gemischte Zahlen. 23

Es wurde eine Unterklasse GemZahl von Bruch eingefügt. Die Methoden multiplizierenmit() und dividierendurch() sollen überladen werden. Bei der Methode addierenmit() behilft man sich einfach dadurch, dass die gemischten Zahlen zuvor in Brüche umgewandelt werden. Dadurch muss diese Methode dann nicht überladen werden. An welcher Stelle des Programms wird Polymorphie verwendet? (Lösung der Aufgabe im Anhang) Technische Ergänzungen Zurück zum Spiel. Zwei Dinge sollen noch verändert werden: Die Käfer werden mit einer Zufallsgeschwindigkeit initialisiert, sodass der Spieler sich nicht auf wiederkehrende Situationen einstellen kann. Die tatsächliche Geschwindigkeit der Käfer kann man durch einen festen Summanden nach unten begrenzen. Je kleiner der Wert ist, desto einfacher ist das Spiel. Also kann man durch Wahl des Summanden (wir nennen ihn ag, wie Anfangsgeschwindigkeit) die Schwierigkeit des Spiels steuern. Dies soll so realisiert werden, dass beim Druck der (Ziffernblock-) Taste "1" der kleinste Wert (Vorschlag: ag = 3) eingestellt und auch links oben im Spielfeld angezeigt wird. Mit Taste "2" stellt man einen mittleren Schwierigkeitsgrad ein (Vorschlag: ag = 5) und mit Taste "3" wird das Durchkommen der Ameisen dann richtig schwer (Vorschlag: ag = 8). Für das Info-Menu muss noch ein kurzer, klarer Text erstellt werden, damit der Spieler weiß, was er zu tun hat. Alle Ergänzungen treffen ausschließlich Form1. Erweiterung der Attribute: Font schrift2 = new Font("Arial", 10);//Schrift für den Schwierigkeitsgrad private int[] z = new int[4]; //Zufallsgeschwindigkeiten private int ag = 3; //Anfangsgeschwindigkeit Erweiterung der Methode initialisiereinsekten(): ZufallsGeschwindigkeitenErzeugen(); 24

Neue Methode: private void ZufallsGeschwindigkeitenErzeugen() Random zufall = new Random(); for (int i = 0; i < 4; ++i) z[i] = zufall.next(7); Ergänzung bei Ameisen zeichnen im Tick-Event: SchwierigkeitsgradAngeben(); Käfer zeichnen im Tick-Event: for (int i = 0; i < 4; i++) käferliste[i].laufen(ag + z[i]);//ergibt vier Zufalls-Geschwindigkeiten käferliste[i].käferaktion(käferliste[i]); Ergänzung im KeyDown-Event: case Keys.NumPad1://leicht ag = 2; case Keys.NumPad2://mittel ag = 5; case Keys.NumPad3://schwer ag = 8; Neue Methode: private void SchwierigkeitsgradAngeben() string sg = ""; switch (ag) case 2: sg = "Schwierigkeitsgrad: leicht"; case 5: sg = "Schwiergkeitsgrad: mittel"; case 8: sg = "Schwiergkeitsgrad: schwer"; g.drawstring(sg, schrift2, Brushes.Green, new PointF(10, 30)); Die Message-Box im Info-Click-Event: MessageBox.Show("Ziel des Spiels ist es, die Ameisen auf die rechte Seite in ihren Bau zu führen," + "\r\n ohne sich von den stets hungrigen Käfern fressen zu lassen." + "\r\n Steuerung der Ameisen mit den Tasten A/D bzw. J/L" + " \r\n Schwierigkeitsgrad einstellen mit dem Ziffernblock:" + " \r\n 1: leicht, 2: mittel, 3: schwer \r\n Neustarten mit der Taste R"); Aufgabe 12 Erstellen Sie das Programm mit den obigen Angaben. Eigene Experimente sind erwünscht! (Form1 komplett im Anhang) 25

E) Vierte Stufe: Eigene Ideen Der Text dieses Abschnitts ist äußerst kurz! Hier sollen Sie Ihre eigenen Ideen verwirklichen. Hier nur einige wenige Anregungen: Es müssen drei Ameisen gesteuert werden. Die Ameisen müssen (sofern sie überleben) die Strecke zwei- oder dreimal laufen. Ameisen und / oder Käfer können von ihren geraden Strecken abweichen. Dabei sollten aber auch die Bilder entsprechend gedreht werden (anspruchsvoll!). Käfer laufen einen Zufallsweg (noch anspruchsvoller!).... 26

Anhang: Lösungen und Ergänzungen Klasse Ameise (vollständig) Stufe 1: using System; using System.Drawing; namespace AmeisenKäferSpiel class Ameise private int x, y;//koordinaten private float va;//geschwindigkeit private int aktuellesbild; private bool angekommen = false; protected Graphics g; protected Bitmap[] bilder = new Bitmap[2]; public Ameise(Graphics g, Bitmap bildeins, Bitmap bildzwei, int xpos, int ypos) this.g = g; bilder[0] = bildeins; bilder[1] = bildzwei; x = xpos; y = ypos; public int Xpos set x = value; get return x; public void Schneller(float dv) va += dv; if (va > 30) va = 30; public void Bremsen(float dv) va -= dv; if (va < 0) va = 0; public void Schritt(Ameise ameise) //Schritt einer Ameise if (va == 0) return; x += Convert.ToInt32(va); //Verschieben der x-position aktuellesbild = (aktuellesbild + 1) % 2; //Bilder abgewechselnd zeigen public void AmeisenAktion(Ameise ameise) ameise.schritt(ameise); ameise.zeichnen(); // Ameise an neuer Position zeichnen 27

public void Zeichnen() g.drawimage(bilder[aktuellesbild], x, y); public bool Angekommen set angekommen = value; get return angekommen; Aufgabe 1 (Teillösung): Leider unterscheidet UMLed nicht zwischen Methoden und Eigenschaften. Dafür aber nicht standardgemäß zwischen Auftrag und Anfrage. Der Unterscheid: Eine Anfrage liefert Daten zurück (also nicht void...) wie public bool Angekommen. Tipps: Erst ein leeres Diagramm erzeugen und speichern (KlassenBeziehungen.urd) und dann eine Klasse hinzufügen: Ameise. Nach Beendigung: Diagramm speichern. Eigenschaften gibt es in UMLed nicht. Dafür kann man bei der Eingabe eines Attributs eine Lese-Anfrage bzw. einen Schreibauftrag mit erstellen lassen. (Siehe Bild rechts) Export in Java vornehmen (mögliche Lösung im Anschluss - braun: Kommentare) /** * import-liste * ggf. weiter von Hand anpassen */ public class Ameise 28

/** * Attribute der Klasse */ protected Bitmap[] bilder; protected Graphics g; private bool angekommen; private float va; private int aktuellesbild; private int x; /** * Attribute die aus Beziehungen resultieren */ /** * Ameise: Methoden */ /********* Konstruktor Ameise (public) *****************************************/ public Ameise(Graphics g, Bitmap bildeins, Bitmap bildzwei, int xpos, int ypos) //hier ggf. Code ergänzen /********* AmeisenAktion (public) **************************************/ public void AmeisenAktion(Ameise ameise) //hier ggf. Code ergänzen /********* Bremsen (public) ********************************************/ public void Bremsen(float dv) //hier ggf. Code ergänzen /********* Schneller (public) ******************************************/ public void Schneller(float dv) //hier ggf. Code ergänzen /********* Schritt (public) ********************************************/ public void Schritt(Ameise ameise) //hier ggf. Code ergänzen /********* SetzeAngekommen (public) ************************************/ public void SetzeAngekommen(bool pangekommen) angekommen = pangekommen /********* SetzeX (public) *********************************************/ public void SetzeX(int px) x = px 29

/********* Zeichnen (public) *******************************************/ public void Zeichnen() //hier ggf. Code ergänzen /********* GibAngekommen (public) **************************************/ public bool GibAngekommen() return angekommen /********* GibX (public) ***********************************************/ public int GibX() return x Klasse Form1 (vollständig) Stufe 1: using System; using System.Drawing; using System.Windows.Forms; using System.Collections.Generic; using AmeisenKäferSpiel.Properties; namespace AmeisenKäferSpiel public partial class Form1 : Form List<Ameise> ameisenliste; //Die Ameisen private Graphics g; // Grafikobjekt, auf dem gezeichnet wird public Form1() InitializeComponent(); initialisiereameisen(); private void initialisiereameisen() g = this.creategraphics(); ameisenliste = new List<Ameise>(); ameisenliste.add(new Ameise(g, Resources.ameise1a, Resources.ameise1b, 0, 260)); ameisenliste.add(new Ameise(g, Resources.ameise2a, Resources.ameise2b, 0, 410)); private void timer1_tick(object sender, EventArgs e) g.clear(color.white); foreach (Ameise ameise in ameisenliste) if (ameise.xpos <= 1000) //selbsterklärend... ameise.ameisenaktion(ameise); 30

else ameise.angekommen = true; private void Form1_KeyDown(object sender, KeyEventArgs e) //Eigenschaft KeyPreview des Formulars auf true setzten! switch (e.keycode) case Keys.D: // Linke Hand : obere Ameise steuern ameisenliste[0].schneller(1.5f); // schneller: Taste D, langsamer: Taste A case Keys.A: ameisenliste[0].bremsen(1.5f); case Keys.L: // Rechte Hand : untere Ameise steuern ameisenliste[1].schneller(1.5f); // schneller: Taste L, langsamer: Taste J case Keys.J: ameisenliste[1].bremsen(1.5f); case Keys.R: Application.Restart(); private void beendentoolstripmenuitem1_click(object sender, EventArgs e) Close(); private void neustartentoolstripmenuitem_click(object sender, EventArgs e) Application.Restart(); private void infotoolstripmenuitem_click(object sender, EventArgs e) MessageBox.Show("Steuerung der Ameisen mit den Tasten A/D bzw. J/L" + "\r\n Neustarten mit der Taste R"); Lösung Aufgabe 5 public Bruch(int z, int n) zaehler = z; nenner = n; public int Zaehler //Eigenschaft get return zaehler; set zaehler = value; 31

public int Nenner //Eigenschaft get return nenner; set nenner = value; public void multiplizierenmit(bruch bruch) zaehler = zaehler * bruch.zaehler; nenner = nenner * bruch.nenner; kuerzen(); public void dividierendurch(bruch bruch) zaehler = zaehler * bruch.nenner; nenner = nenner * bruch.zaehler; kuerzen(); Lösung Aufgabe 8 class Ameise : Insekt private int aktuellesbild; private bool angekommen = false; private string zeit = "0"; Font schrift = new Font("Arial", 14);//Ausgabeschrift public Ameise(Graphics g, Bitmap bildeins, Bitmap bildzwei, int xpos, int ypos, string lz): base(g, bildeins, bildzwei, xpos, ypos, lz) this.g = g; bilder[0] = bildeins; bilder[1] = bildzwei; x = xpos; y = ypos; this.w = 100; this.h = 75; public override void Schneller(float dv) base.schneller(dv); if (va > 30) va = 30; public override void Schritt(Insekt ameise) //Schritt einer Ameise if (va == 0) //Ameise steht ZeitAusgeben(ameise,zeit); return; ZeitAusgeben(ameise,zeit); x += Convert.ToInt32(va); //Verschieben der x-position aktuellesbild = (aktuellesbild + 1) % 2; //Bilder abgewechselnd zeigen public void AmeisenAktion(Ameise ameise) 32

ameise.schritt(ameise); ameise.zeichnen(); // Ameise an neuer Position zeichnen public override void Zeichnen() g.drawimage(bilder[aktuellesbild], x, y); public bool Angekommen get return angekommen; public void Stirbt() lebt = false; private void ZeitAusgeben(Insekt ameise, String zeit) zeit = (DateTime.Now - Form1.form1.StartZeit).TotalSeconds.ToString("0.0"); ameise.lebenszeit = zeit; g.drawstring(string.format(zeit + " s"), schrift, Brushes.Black, new PointF(920, y + 30)); public override void AufAnfangspositionSetzen(int a) //Im Moment unbenutzt x = a; Lösung Aufgabe 11 Form1: using System; using System.Windows.Forms; namespace Bruch public partial class Form1 : Form public Form1() InitializeComponent(); //Aus Demonstrationszwecken Bruchobjekte verwendet. //So kann man sich eine Überladung der Methode "addierenmit()" //für gem.zahlen ersparen! private void button1_click(object sender, EventArgs e) label_rechenzeichen.text = "+"; GemZahl gem1 = new GemZahl(0, 1, 1); GemZahl gem2 = new GemZahl(0, 1, 1); 33

einlesen(gem1, gem2); gem1.inbruch(); gem2.inbruch(); Bruch bruch1 = new Bruch(1, 1); Bruch bruch2 = new Bruch(1, 1); bruch1.zaehler = gem1.zaehler; bruch1.nenner = gem1.nenner; bruch2.zaehler = gem2.zaehler; bruch2.nenner = gem2.nenner; bruch1.addierenmit(bruch2); //Polymorphie! gem1.zaehler = bruch1.zaehler; gem1.nenner = bruch1.nenner; gem1.ingemzahl(); ausgeben(gem1); //Ohne Bruchobjekte- aber mit eigener Methode für GemZahl private void button2_click(object sender, EventArgs e) label_rechenzeichen.text = "*"; GemZahl gem1 = new GemZahl(0, 1, 1); GemZahl gem2 = new GemZahl(0, 1, 1); einlesen(gem1, gem2); gem1.multiplizierenmit(gem2);//nutzung der Polymorphie ausgeben(gem1); private void button3_click(object sender, EventArgs e) label_rechenzeichen.text = ":"; GemZahl gem1 = new GemZahl(0, 1, 1); GemZahl gem2 = new GemZahl(0, 1, 1); einlesen(gem1, gem2); gem1.dividierendurch(gem2); ausgeben(gem1); private void beendentoolstripmenuitem_click(object sender, EventArgs e) Close(); private void ausgeben(bruch bruch1) edit_zaehler.text = Convert.ToString(bruch1.Zaehler); edit_nenner.text = Convert.ToString(bruch1.Nenner); private void einlesen(gemzahl gem1, GemZahl gem2) gem1.gzahl = Convert.ToInt32(edit_gzahl1.Text);//Erste gem. Zahl einlesen gem1.zaehler = Convert.ToInt32(edit_zaehler1.Text); gem1.nenner = Convert.ToInt32(edit_nenner1.Text); 34

gem2.gzahl = Convert.ToInt32(edit_gzahl2.Text);//Zweite gem. Zahl einlesen gem2.zaehler = Convert.ToInt32(edit_zaehler2.Text); gem2.nenner = Convert.ToInt32(edit_nenner2.Text); private void ausgeben(gemzahl gem1) //Polymorphie! edit_gzahl.text = Convert.ToString(gem1.Gzahl); edit_zaehler.text = Convert.ToString(gem1.Zaehler); edit_nenner.text = Convert.ToString(gem1.Nenner); GemZahl: using System; namespace Bruch class GemZahl : Bruch private int ganzzahl; public GemZahl(int gzahl, int zaehler, int nenner) : base(zaehler, nenner) ganzzahl = gzahl; public int Gzahl // Eigenschaft get return ganzzahl; set ganzzahl = value; //überladene Methode public void multiplizierenmit(gemzahl gem) inbruch(); gem.inbruch(); Zaehler = Zaehler * gem.zaehler; Nenner = Nenner * gem.nenner; kuerzen(); ingemzahl(); //überladene Methode public void dividierendurch(gemzahl gem) inbruch(); gem.inbruch(); Zaehler = Zaehler * gem.nenner; Nenner = Nenner * gem.zaehler; kuerzen(); ingemzahl(); public void inbruch() if (Gzahl > 0) Zaehler = Gzahl * Nenner + Zaehler; Gzahl = 0; this.kuerzen(); 35

if (Gzahl < 0) Zaehler = -(-Gzahl * Nenner + Zaehler); Gzahl = 0; this.kuerzen(); public void ingemzahl() //Es wird davon ausgegangen, dass -2 1/2 -(2 1/2) bedeutet! Gzahl = Zaehler / Nenner; Zaehler = Math.Abs(Zaehler) % Math.Abs(Nenner); Lösung Aufgabe 12 namespace AmeisenKäferSpiel public partial class Form1 : Form List<Ameise> ameisenliste; //Die Ameisen List<Käfer> käferliste; //Die Käfer private Graphics g; // Grafikobjekt, auf dem gezeichnet wird public static Form1 form1; //Damit man in andern Klassen Methoden von Form1 verwenden kann private DateTime startzeit; private bool spielbeendet = false; private int ym; // Höhe des Formulars Font schrift = new Font("Arial", 24); //Schrift für die Punktezahl Font schrift2 = new Font("Arial", 10);//Schrift für den Schwierigkeitsgrad private int[] z = new int[4]; //Zufallsgeschwindigkeiten private int ag = 3; //Anfangsgeschwindigkeit public Form1() InitializeComponent(); form1 = this; ym = this.size.height; initialisiereinsekten(); startzeit = DateTime.Now; private void initialisiereinsekten() g = this.creategraphics(); ameisenliste = new List<Ameise>(); käferliste = new List<Käfer>(); ameisenliste.add(new Ameise(g, Resources.ameise1a, Resources.ameise1b, 0, 260,"0.0")); ameisenliste.add(new Ameise(g, Resources.ameise2a, Resources.ameise2b, 0, 410,"0.0")); käferliste.add(new Käfer(g, Resources.käfera, Resources.käferb, 200, 10, "0.0", Richtung.runter)); käferliste.add(new Käfer(g, Resources.käfer2a, Resources.käfer2b, 400, 480, "0.0", Richtung.hoch)); käferliste.add(new Käfer(g, Resources.käfera, Resources.käferb, 600, 10, "0.0", Richtung.runter)); käferliste.add(new Käfer(g, Resources.käfer2a, Resources.käfer2b, 800, 480, "0.0", Richtung.hoch)); ZufallsGeschwindigkeitenErzeugen(); 36

public DateTime StartZeit get return startzeit; public int Höhe get return ym; private bool GefressenVonKäfer(Ameise ameise) bool gefressen = false; foreach (Käfer käfer in käferliste) if (ameise.berührt(käfer)) gefressen = true; return gefressen; private void ZufallsGeschwindigkeitenErzeugen() Random zufall = new Random(); for (int i = 0; i < 4; ++i) z[i] = zufall.next(7); private void timer1_tick(object sender, EventArgs e) g.clear(color.white); int anzahlaktiverameisen = 0; foreach (Ameise ameise in ameisenliste) if (GefressenVonKäfer(ameise)) //gezeichnet wird nur, wenn Ameise nicht vom Käfer gefressen wird ameise.stirbt(); ameise.lebenszeit = "50"; //Nur für die Punktzahl: Strafe -500 Punkte... erreicht if (ameise.xpos <= 1000 && ameise.lebt) //rechter Rand noch nicht ameise.ameisenaktion(ameise); anzahlaktiverameisen++; SchwierigkeitsgradAngeben(); if (anzahlaktiverameisen == 0) spielbeendet = true; if (!spielbeendet)//käfer nur zeichnen, wenn mindest. eine Ameise lebt und noch nicht angekommen ist for (int i = 0; i < 4; i++) käferliste[i].laufen(ag + z[i]);//ergibt vier Zufalls- Geschwindigkeiten käferliste[i].käferaktion(käferliste[i]); 37