Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung Grafik-Programmierung In dieser Übung fahren wir mit den Grundlagen der Grafikprogrammierung aus Übung 6 fort und wenden dies auf Spiele an. Aufgabe 1: Zeichenbare Game-Objekte Zunächst definieren wir ein Interface PaintableGameObject mit der Methode paintComponent. Unsere Spielobjekte (vom Typ GameObject) sollen dieses Interface implementieren und dadurch grafisch darstellbar sein. Die eigentliche Darstellung erfolgt jeweils spezifisch für jedes Spielobjekt. In unserem Spiel brauchen wir uns dann nicht mehr weiter darum zu kümmern, wie die Spielobjekte gezeichnet werden, da aufgrund der dynamischen Bindung jedes Objekt „weiß“, wie es sich selbst zeichnen soll. Wir erweitern danach das Interface GraphicsObject um dieses Interface, damit alle Spielobjekte gezeichnet werden können. Da durch diese Interfaces-Erweiterung die implementierenden Klassen abstrakt geworden sind, müssen wir noch die Methode paintComponent jeweils spezifisch implementieren. Hinweis: Falls Sie Ihre Lösung von Übung 4 nicht vollständig realisiert haben, können Sie von der Webseite der Veranstaltung eine Beispielvorgabe der bereits in den Übungen realisierten Interfaces und Klassen herunterladen. a) Erstellen Sie ein Interface PaintableGameObject im Paket games.basic.gameObjects.interfaces. Diese soll die Methode public void paintComponent(Graphics g) bereitstellen. b) Leiten Sie das Interface games.basic.gameObjects.interfaces. GameObject (siehe Übung 4) neben SimpleGameObject auch von PaintableGameObject ab. c) Passen Sie dann die Klassen RectangularGameObject und CircularGameObject aus dem Paket games.basic.gameObjects entsprechend an, indem Sie dort jeweils die Methode paintComponent implementieren. S. 1 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung Dabei soll • die Farbe des Rechteckes bzw. Kreises jeweils per Attribut wählbar sein, • ebenso ob die Figuren ausgefüllt sein sollen oder nicht. Hinweis: Zum Zeichnen eines ausgefüllten Rechteckes bzw. Kreises können Sie die Methoden fillRect(x, y, width, height) bzw. fillOval(x, y, width, height) aus der Klasse java.awt.Graphics verwenden (siehe http://docs.oracle.com/javase/7/docs/api/index.html?java/awt/Graphics.html), während drawRect bzw. drawOval jeweils nur den Rahmen der Figuren zeichnet. Hinweis: Zum Festlegen der Strichdicke können Sie die Methode setStroke(new BasicStroke( <<Strichdicke>> ) aus der Klasse Graphics2D verwenden. d) Erstellen Sie eine (analog zu RectangularGameObject) eine von AbstractGameObject abgeleitete Klasse CrossGameObject, welche ein Kreuz (d.h. 2 Linien) darstellt. games.basic.gameObjects.interfaces.PaintableGameObject.java: package games.basic.gameObjects.interfaces; import java.awt.Graphics; public interface PaintableGameObject { public abstract void paintComponent(Graphics g); } games.basic.gameObjects.interfaces.GameObject.java: package games.basic.gameObjects.interfaces; public interface GameObject extends SimpleGameObject, PaintableGameObject { public public public public abstract abstract abstract abstract boolean boolean boolean boolean isLeftOf(GameObject other); isRightOf(GameObject other); isAboveOf(GameObject other); isBelowOf(GameObject other); public abstract boolean touches(GameObject other); } S. 2 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung games.basic.gameObjects.RectangularGameObject.java: package games.basic.gameObjects; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Stroke; import games.basic.position.interfaces.Positionable; public class RectangularGameObject extends AbstractGameObject { private int width; private int height; private Color color; boolean filled; final static float STROKE = 3.0f; // Strichdicke // Konstruktor public RectangularGameObject(Positionable pos, int width, int height, Color color, boolean filled) { super(pos); // Aufruf: AbstractGameObject(pos); this.width = width; this.height = height; this.color = color; this.filled = filled; } @Override public int getWidth() { return this.width; } @Override public int getHeight() { return this.height; } @Override public void paintComponent(Graphics g) { Color oldColor = g.getColor(); // alte Farbeeinstellung sichern // Rechteck zeichnen g.setColor(color); if (filled) { g.fillRect( getPos().getX(), getPos().getY(), width, height); } else { Graphics2D g2d = (Graphics2D) g; Stroke oldStroke = g2d.getStroke(); // alte Strichdicke sichern g2d.setStroke( new BasicStroke(STROKE) ); g.drawRect( getPos().getX(), getPos().getY(), width, height); g2d.setStroke( oldStroke ); // Strichdicke wiederherstellen S. 3 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung } g.setColor(oldColor); // Farbe wiederherstellen } public String toString() { // verwende toString-Methode aus Positionable return ("pos = " + this.getPos() + ", size = " + this.getWidth() + " x " + this.getHeight()+ ")"); } public boolean equals(Object other) { if (other == null || !(other instanceof RectangularGameObject)) return false; RectangularGameObject otherRect = (RectangularGameObject)other; Positionable thisPos = this.getPos(); // Beachte: this.pos funktioniert nicht, da Attribut pos // in Oberklasse private ist! // --> getPos() funktioniert, egal ob // Attribut pos hier oder in Oberklasse // definiert ist Positionable otherPos = otherRect.getPos(); return // vergleiche aktuelle Position // verwende equals-Methode von Positionable thisPos.equals( otherPos ) // vergleiche Breite/Höhe && this.getWidth() == otherRect.getWidth() && this.getHeight() == otherRect.getHeight() && this.color == otherRect.color && this.filled == otherRect.filled; } } games.basic.gameObjects.CircularGameObject.java: package games.basic.gameObjects; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Stroke; import games.basic.position.interfaces.Positionable; public class CircularGameObject extends AbstractGameObject { private int radius; private Color color; private boolean filled; final static float STROKE = 3.0f; // Strichdicke // Konstruktor public CircularGameObject(Positionable pos, int radius, Color color, boolean filled) { super(pos); // Aufruf: AbstractGameObject(pos); S. 4 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung this.radius = radius; this.color = color; this.filled = filled; } @Override public int getWidth() { return 2 * this.radius; } @Override public int getHeight() { return 2 * this.radius; } @Override public void paintComponent(Graphics g) { Color oldColor = g.getColor(); // alte Farbeeinstellung sichern // Kreis zeichnen g.setColor(color); if (filled) { g.fillOval( getPos().getX(), getPos().getY(), 2 * radius, 2 * radius); } else { Graphics2D g2d = (Graphics2D) g; Stroke oldStroke = g2d.getStroke(); // alte Strichdicke sichern g2d.setStroke( new BasicStroke(STROKE) ); g.drawOval( getPos().getX(), getPos().getY(), 2 * radius, 2 * radius); g2d.setStroke( oldStroke ); // Strichdicke wiederherstellen } g.setColor(oldColor); // Farbe wiederherstellen } public String toString() { // verwende toString-Methode aus Positionable return ("pos = " + this.getPos() + ", radius = " + this.radius + ")"); } public boolean equals(Object other) { if (other == null || !(other instanceof CircularGameObject)) return false; CircularGameObject otherCirc = (CircularGameObject)other; // Beachte: this.pos funktioniert nicht, da Attribut pos // in Oberklasse private ist! // --> getPos() funktioniert, egal ob // Attribut pos hier oder in Oberklasse // definiert ist Positionable thisPos = this.getPos(); Positionable otherPos = otherCirc.getPos(); S. 5 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik return Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung // vergleiche aktuelle Position // verwende equals-Methode von Positionable thisPos.equals( otherPos ) // vergleiche Breite/Höhe && this.radius == otherCirc.radius && this.color == otherCirc.color && this.filled == otherCirc.filled; } } games.basic.gameObjects.CrossGameObject.java: package games.basic.gameObjects; import games.basic.position.interfaces.Positionable; import import import import import java.awt.BasicStroke; java.awt.Color; java.awt.Graphics; java.awt.Graphics2D; java.awt.Stroke; public class CrossGameObject extends AbstractGameObject { private int width; private int height; private Color color; boolean filled; final static float STROKE = 3.0f; // Strichdicke public CrossGameObject(Positionable pos, int width, int height, Color color, boolean filled) { super(pos); this.width = width; this.height = height; this.color = color; this.filled = filled; } @Override public int getWidth() { return this.width; } @Override public int getHeight() { return this.height; } @Override public void paintComponent(Graphics g) { Color oldColor = g.getColor(); // alte Farbeeinstellung sichern // Kreuz zeichnen g.setColor(color); if (filled) { S. 6 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung g.fillRect( getPos().getX(), getPos().getY(), width, height); } else { Graphics2D g2d = (Graphics2D) g; Stroke oldStroke = g2d.getStroke(); // alte Strichdicke sichern g2d.setStroke( new BasicStroke(STROKE) ); g.drawLine( getPos().getX(), getPos().getY(), getPos().getX() + getWidth(), getPos().getY() + getHeight()); g.drawLine( getPos().getX() + getWidth(), getPos().getY(), getPos().getX(), getPos().getY() + getHeight()); g2d.setStroke( oldStroke ); // Strichdicke wiederherstellen } g.setColor(oldColor); // Farbe wiederherstellen } @Override public String toString() { return ("pos = (" + this.getPos().getX() + ", " + this.getPos().getY() + ", size = " + this.getWidth() + " x " + this.getHeight()+ ")"); } @Override public boolean equals(Object other) { if (other == null || !(other instanceof CrossGameObject)) return false; CrossGameObject otherCross = (CrossGameObject)other; Positionable thisPos = this.getPos(); Positionable otherPos = otherCross.getPos(); return (thisPos.getX() == otherPos.getX()) && (thisPos.getY() == otherPos.getY()) && this.getWidth() == otherCross.getWidth() && this.getHeight() == otherCross.getHeight() && this.color == otherCross.color && this.filled == otherCross.filled; } } Aufgabe 2: Grafik für Tic Tac Toe Tic Tac Toe (auch „Drei gewinnt“ oder „Kreis und Kreuz“) ist ein Spiel für zwei Personen. Dabei machen auf einem 3×3 Felder großen Spielfeld die beiden Spieler abwechselnd ihre Zeichen (ein Spieler Kreuze, der andere Kreise). Der Spieler, der als erstes drei seiner Zeichen in eine Reihe, Spalte oder eine der beiden Hauptdiagonalen setzen kann, gewinnt. [wikipedia] S. 7 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung Implementieren Sie die grafische Darstellung für das Spiel Tic Tac Toe. Dabei sollen geeignete Spielobjekte jeweils (rote) Kreuze sowie (blaue) Kreise zur Darstellung der Züge der beiden Gegner verwendet werden. a) Erstellen Sie eine von JPanel abgeleitete Klasse TicPanel im Paket games.examples.ticTacToe. • Übergeben Sie der Klasse eine Liste von darzustellenden Spielobjekten, d.h. von Objekten vom Typ games.basic.gameObjects.interfaces.GameObject. Hinweis: Für Listen bietet Java u.a. die generische Klasse ArrayList (java.util.ArrayList<GameObject>, siehe http://docs.oracle.com/javase/7/docs/api/index.html?java/util/ArrayList.html). Diese besitzt u.a. die folgenden für diese Aufgabe relevanten Funktionen: • new ArrayList<GameObject>() erzeugt eine leere Liste von GameObject-Objekten. • boolean add(GameObject elem) fügt ein Element zur Liste hinzu • for (GameObject elem : liste) { … } iteriert über alle Elemente einer Liste von GameObject-Elementen. Weitere wichtige Methoden aus ArrayList sind u.a. • GameObject get(int index) liest das Element an Position index aus • int size() liefert die Anzahl der Elemente in der Liste • boolean remove(GameObject elem) löscht das Element aus der Liste, sofern es enthalten ist. • Überschreiben Sie dann die Methode public void paintComponent(Graphics g) und zeichne Sie dort zunächst die Trennlinen für das 3 x 3-Feld . Zeichnen Sie ferner in das übergebene Graphics-Objekt g alle darzustellenden S. 8 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung Spielobjekte (aus der ArrayList). b) Erzeugen Sie schließlich (in einer Klasse games.examples.ticTacToe .TicTacToe) ein JFrame-Fenster, fügen Sie ein Objekt der Klasse TicPanel zu diesem JFrame hinzu und zeigen Sie das Fenster auf dem Bildschirm an. c) Erzeugen Sie zum Testen eine Liste von Spielobjekten (Kreuze und Kreise), welche Sie sich auf dem Panel darstellen lassen. Hinweis: Zum Neuzeichnen des Fensters können Sie die Methode frame.repaint() aufrufen. TicTacToe TicPanel objListe : ArrayList<GameObject> main(String[] args) { Jframe frame = … … TicPanel panel = new TicPanel( objListe); … } +paintComponent(Graphics g) GameObject +paintComponent(Graphics g) AbstractGameObject -pos: Positionable CrossGameObject CircularGameObject -width, height: int -color: java.awt.Color -radius: int -color: java.awt.Color -filled: boolean +paintComponent(Graphics g) +paintComponent(Graphics g) d) Nicht ausgelastet? Lassen Sie beim Zeichnen der Kreise und Kreuze einen Abstand zum Rand des jeweiligen Feldes, d.h. zu den Rahmenlinien des 3x3-Feldes. S. 9 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung games.examples.ticTacToe.Cross.java: package games.examples.ticTacToe; import java.awt.Color; import games.basic.gameObjects.CrossGameObject; import games.basic.position.interfaces.Positionable; import games.basic.position.Position; public class Cross extends CrossGameObject { public static final Color CROSS_COLOR = Color.red; private static final int GAP = TicTacToe.GAP; public Cross(int x, int y, int size) { super(new Position(x+GAP, y+GAP), size - GAP*2, size - GAP*2, CROSS_COLOR, false); } } games.examples.ticTacToe.Dot.java: package games.examples.ticTacToe; import games.basic.gameObjects.CircularGameObject; import games.basic.position.interfaces.Positionable; import games.basic.position.Position; import java.awt.Color; import java.awt.Graphics; public class Dot extends CircularGameObject { public static final Color CIRC_COLOR = Color.blue; private static final int GAP = TicTacToe.GAP; public Dot(int x, int y, int size) { super(new Position(x+GAP, y+GAP), size - GAP*2, CIRC_COLOR, false); } } games.examples.ticTacToe.TicPanel.java: package games.examples.ticTacToe; import java.util.ArrayList; import javax.swing.JPanel; S. 10 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung import java.awt.Color; import java.awt.Graphics; import games.basic.gameObjects.interfaces.*; class TicPanel extends JPanel { private ArrayList<GameObject> components; private int componentSize; public TicPanel(int componentSize, ArrayList<GameObject> components) { this.componentSize = componentSize; this.components = components; } @Override public void paintComponent(Graphics g) { // Zeichne Trennlinien für 3x3-Feld g.setColor(Color.black); g.drawLine(componentSize, 0, componentSize, componentSize * 3); g.drawLine(componentSize * 2, 0, componentSize * 2, componentSize * 3); g.drawLine(0, componentSize, componentSize * 3, componentSize); g.drawLine(0, componentSize * 2, componentSize * 3, componentSize * 2); // Zeichne Komponenten for (GameObject component : components) { component.paintComponent(g); } } } // class TicPanel games.examples.ticTacToe.TicTacToe.java: package games.examples.ticTacToe; import games.basic.gameObjects.interfaces.GameObject; import import import import java.awt.Color; java.awt.Graphics; java.util.ArrayList; java.util.Scanner; import javax.swing.JFrame; import javax.swing.JPanel; public class TicTacToe { public static final int GAP = 5; private ArrayList<GameObject> components = new ArrayList<GameObject>(); private int componentSize = 60; private int xframesize = componentSize*3 + 10; S. 11 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung private int yframesize = componentSize*3 + 30; JFrame frame; public static void main(String[] args) { TicTacToe prog = new TicTacToe(); prog.run(); } public void run() { frame = new JFrame("Tic Tac Toe"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); TicPanel panel = new TicPanel(); frame.add( panel ); frame.setSize(xframesize, yframesize); frame.setVisible(true); play(); } public void play() { Dot meinZug1 = new Dot(0, 0, componentSize); Cross gegnerZug1 = new Cross(componentSize, componentSize, componentSize); components.add(meinZug1); components.add(gegnerZug1); frame.repaint(); Dot meinZug2 = new Dot(0, componentSize*2, componentSize); Cross gegnerZug2 = new Cross(componentSize*2, componentSize*2, componentSize); components.add(meinZug2); components.add(gegnerZug2); frame.repaint(); Scanner scanner = new Scanner(System.in); System.out.print("x = "); int x = scanner.nextInt(); System.out.print("y = "); int y = scanner.nextInt(); Dot meinZug = new Dot(componentSize * x, componentSize * y, componentSize); components.add(meinZug); frame.repaint(); } class TicPanel extends JPanel { @Override public void paintComponent(Graphics g) { // Zeichne Trennlinien für 3x3-Feld g.setColor(Color.black); S. 12 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung g.drawLine(componentSize, 0, componentSize, componentSize * 3); g.drawLine(componentSize * 2, 0, componentSize * 2, componentSize * 3); g.drawLine(0, componentSize, componentSize * 3, componentSize); g.drawLine(0, componentSize * 2, componentSize * 3, componentSize * 2); // Zeichne Komponenten for (GameObject component : components) { component.paintComponent(g); } } } // inner class TicPanel } Zusatz-Aufgabe 3: Spielsteuerung für Tic Tac Toe Erweitern Sie die Klasse TicTacToe um die Spielsteuerung für das Spiel TicTacToe. Dazu können Sie von Tastatur jeweils das Feld einlesen und abwechselnd (für jeden Spieler) Kreuze bzw. Punkte in dieses Feld setzen. Wer alle Felder einer Zeile, Spalte oder Diagonale besetzt hat, hat das Spiel gewonnen. S. 13 / 14 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 7 – Beispiellösung Zusatz-Aufgabe 4 (für Java-Experten): Java-Rätsel Was gibt das folgende Java-Programm aus – und warum verhält es sich so seltsam? package click; package hack; import click.CodeTalk; public class CodeTalk { public void doIt() { printMessage(); } void printMessage() { System.out.println("Click"); } public class TypeIt { private static class ClickIt extends CodeTalk { void printMessage() { System.out.println("Hack"); } } public static void main(String[] args) { ClickIt clickit = new ClickIt(); clickit.doIt(); } } } Ausgabe: Click Lösung: • • • • • Die Methode printMessage ist nicht public, sondern (ohne expliziten Sichtbarkeitsmodifizierer) nur innerhalb des jeweiligen Paketes sichtbar. Die Klassen CodeTalk und dessen Unterklasse ClickIt (als innere Klasse von TypeIt) liegen jedoch in verschiedenen Paketen (click bzw. hack). Folglich ist die Methode printMessage in der Klasse ClickIt nicht überschrieben (da die geerbte Methode nicht sichtbar ist, handelt es sich hier aus Sicht des Aufrufers um eine neue Methode!). Die Methode doIt verwendet deshalb die Methode printMessage aus CodeTalk (mit der Ausgabe „Click“) und nicht die Methode aus der Unterklasse ClickIt (mit der Ausgabe „Hack“). Ausgabe: Click (und nicht wie vielleicht vermutet Hack) S. 14 / 14